fix: Column tile reposition on focus state of Input element inside property pane configuration for Table and Tabs widget (#10046)
This commit is contained in:
parent
324b077ffa
commit
b7ebc7501e
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"tabWidget": ".t--draggable-tabswidget",
|
"tabWidget": ".t--draggable-tabswidget",
|
||||||
"tabInput": ".t--draggable-tabswidget span.t--widget-name",
|
"tabInput": ".t--draggable-tabswidget span.t--widget-name",
|
||||||
"tabName": ".t--property-control-tabs input",
|
"tabName": ".t--property-control-tabs input",
|
||||||
"tabDefault": ".t--property-control-defaulttab .CodeMirror-code",
|
"tabDefault": ".t--property-control-defaulttab .CodeMirror-code",
|
||||||
"tabButton": ".t--property-control-tabs button",
|
"tabButton": ".t--property-control-tabs button",
|
||||||
"tabDelete": ".t--property-control-tabs .t--delete-tab-btn",
|
"tabDelete": ".t--property-control-tabs .t--delete-column-btn",
|
||||||
"tabContainer": "div[type='TABS_WIDGET']",
|
"tabContainer": "div[type='TABS_WIDGET']",
|
||||||
"tabEdit": ".t--property-control-tabs .t--edit-column-btn",
|
"tabEdit": ".t--property-control-tabs .t--edit-column-btn",
|
||||||
"tabVisibility": ".t--property-control-visible .bp3-control-indicator"
|
"tabVisibility": ".t--property-control-visible .bp3-control-indicator"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,27 @@ const DraggableListWrapper = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function DraggableList(props: any) {
|
function DraggableList(props: any) {
|
||||||
const { itemHeight, ItemRenderer, items, onUpdate } = props;
|
const {
|
||||||
|
fixedHeight,
|
||||||
|
focusedIndex,
|
||||||
|
itemHeight,
|
||||||
|
ItemRenderer,
|
||||||
|
items,
|
||||||
|
onUpdate,
|
||||||
|
updateDragging,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const listContainerHeight =
|
||||||
|
fixedHeight && fixedHeight < items.length * itemHeight
|
||||||
|
? fixedHeight
|
||||||
|
: items.length * itemHeight;
|
||||||
const shouldReRender = get(props, "shouldReRender", true);
|
const shouldReRender = get(props, "shouldReRender", true);
|
||||||
// order of items in the list
|
// order of items in the list
|
||||||
const order = useRef<any>(items.map((_: any, index: any) => index));
|
const order = useRef<any>(items.map((_: any, index: any) => index));
|
||||||
|
const displacement = useRef<number>(0);
|
||||||
|
const dragging = useRef<boolean>(false);
|
||||||
|
|
||||||
|
const listRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const onDrop = (originalIndex: number, newIndex: number) => {
|
const onDrop = (originalIndex: number, newIndex: number) => {
|
||||||
onUpdate(order.current, originalIndex, newIndex);
|
onUpdate(order.current, originalIndex, newIndex);
|
||||||
|
|
@ -82,6 +99,29 @@ function DraggableList(props: any) {
|
||||||
}
|
}
|
||||||
}, [items]);
|
}, [items]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (focusedIndex && listRef && listRef.current) {
|
||||||
|
const container = listRef.current;
|
||||||
|
|
||||||
|
if (focusedIndex * itemHeight < container.scrollTop) {
|
||||||
|
listRef.current.scrollTo({
|
||||||
|
top: (focusedIndex - 1) * itemHeight,
|
||||||
|
left: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
(focusedIndex + 1) * itemHeight >
|
||||||
|
listRef.current.scrollTop + listRef.current.clientHeight
|
||||||
|
) {
|
||||||
|
listRef.current.scrollTo({
|
||||||
|
top: (focusedIndex + 1) * itemHeight - listRef.current.clientHeight,
|
||||||
|
left: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [focusedIndex]);
|
||||||
|
|
||||||
const [springs, setSprings] = useSprings<any>(
|
const [springs, setSprings] = useSprings<any>(
|
||||||
items.length,
|
items.length,
|
||||||
updateSpringStyles(order.current, itemHeight),
|
updateSpringStyles(order.current, itemHeight),
|
||||||
|
|
@ -90,60 +130,122 @@ function DraggableList(props: any) {
|
||||||
const bind: any = useDrag<any>((props: any) => {
|
const bind: any = useDrag<any>((props: any) => {
|
||||||
const originalIndex = props.args[0];
|
const originalIndex = props.args[0];
|
||||||
const curIndex = order.current.indexOf(originalIndex);
|
const curIndex = order.current.indexOf(originalIndex);
|
||||||
const curRow = clamp(
|
const pointerFromTop = props.xy[1];
|
||||||
Math.round((curIndex * itemHeight + props.movement[1]) / itemHeight),
|
if (listRef && listRef.current) {
|
||||||
0,
|
const containerCoordinates = listRef?.current.getBoundingClientRect();
|
||||||
items.length - 1,
|
const container = listRef.current;
|
||||||
);
|
if (containerCoordinates) {
|
||||||
const newOrder = swap(order.current, curIndex, curRow);
|
const containerDistanceFromTop = containerCoordinates.top;
|
||||||
setSprings(
|
if (props.dragging) {
|
||||||
dragIdleSpringStyles(newOrder, {
|
if (pointerFromTop < containerDistanceFromTop + itemHeight / 2) {
|
||||||
down: props.down,
|
// Scroll inside container till first element in list is completely visible
|
||||||
originalIndex,
|
if (container.scrollTop > 0) {
|
||||||
curIndex,
|
container.scrollTop -= itemHeight / 10;
|
||||||
y: props.movement[1],
|
}
|
||||||
itemHeight,
|
} else if (
|
||||||
}),
|
pointerFromTop >=
|
||||||
);
|
containerDistanceFromTop + container.clientHeight - itemHeight / 2
|
||||||
if (curRow !== curIndex) {
|
) {
|
||||||
// Feed springs new style data, they'll animate the view without causing a single render
|
// Scroll inside container till container cannnot be scrolled more towards bottom
|
||||||
if (!props.down) {
|
if (
|
||||||
order.current = newOrder;
|
container.scrollTop <=
|
||||||
setSprings(updateSpringStyles(order.current, itemHeight));
|
springs.length * itemHeight -
|
||||||
debounce(onDrop, 400)(curIndex, curRow);
|
container.clientHeight -
|
||||||
|
itemHeight / 2
|
||||||
|
) {
|
||||||
|
container.scrollTop += itemHeight / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// finding distance of current pointer from the top of the container to find the final position
|
||||||
|
// currIndex * itemHeight for the initial position
|
||||||
|
// subtraction formar with latter for displacement
|
||||||
|
displacement.current =
|
||||||
|
pointerFromTop -
|
||||||
|
containerDistanceFromTop +
|
||||||
|
container.scrollTop -
|
||||||
|
curIndex * itemHeight -
|
||||||
|
itemHeight / 2;
|
||||||
|
|
||||||
|
if (!dragging.current && Math.abs(displacement.current) > 10) {
|
||||||
|
dragging.current = props.dragging;
|
||||||
|
updateDragging(dragging.current);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (dragging.current) {
|
||||||
|
dragging.current = props.dragging;
|
||||||
|
updateDragging(dragging.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const curRow = clamp(
|
||||||
|
Math.round(
|
||||||
|
(curIndex * itemHeight + displacement.current) / itemHeight,
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
items.length - 1,
|
||||||
|
);
|
||||||
|
const newOrder = swap(order.current, curIndex, curRow);
|
||||||
|
setSprings(
|
||||||
|
dragIdleSpringStyles(newOrder, {
|
||||||
|
down: props.down,
|
||||||
|
originalIndex,
|
||||||
|
curIndex,
|
||||||
|
y: Math.abs(displacement.current) > 10 ? displacement.current : 0,
|
||||||
|
itemHeight,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (curRow !== curIndex) {
|
||||||
|
// Feed springs new style data, they'll animate the view without causing a single render
|
||||||
|
if (!props.down) {
|
||||||
|
order.current = newOrder;
|
||||||
|
setSprings(updateSpringStyles(order.current, itemHeight));
|
||||||
|
debounce(onDrop, 400)(curIndex, curRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<DraggableListWrapper
|
<div
|
||||||
className="content"
|
ref={listRef}
|
||||||
onMouseDown={() => {
|
style={{
|
||||||
// set events to null to stop other parent draggable elements execution(ex: Property pane)
|
height: listContainerHeight,
|
||||||
document.onmouseup = null;
|
overflowY: "auto",
|
||||||
document.onmousemove = null;
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
style={{ height: items.length * itemHeight }}
|
|
||||||
>
|
>
|
||||||
{springs.map(({ scale, y, zIndex }, i) => (
|
<DraggableListWrapper
|
||||||
<animated.div
|
className="content"
|
||||||
{...bind(i)}
|
onMouseDown={() => {
|
||||||
data-rbd-draggable-id={items[i].id}
|
// set events to null to stop other parent draggable elements execution(ex: Property pane)
|
||||||
key={i}
|
document.onmouseup = null;
|
||||||
style={{
|
document.onmousemove = null;
|
||||||
zIndex,
|
}}
|
||||||
width: "100%",
|
style={{
|
||||||
transform: to(
|
height: "100%",
|
||||||
[y, scale],
|
}}
|
||||||
(y, s) => `translate3d(0,${y}px,0) scale(${s})`,
|
>
|
||||||
),
|
{springs.map(({ scale, y, zIndex }, i) => (
|
||||||
}}
|
<animated.div
|
||||||
>
|
{...bind(i)}
|
||||||
<div>
|
data-rbd-draggable-id={items[i].id}
|
||||||
<ItemRenderer index={i} item={items[i]} />
|
key={i}
|
||||||
</div>
|
style={{
|
||||||
</animated.div>
|
zIndex,
|
||||||
))}
|
width: "100%",
|
||||||
</DraggableListWrapper>
|
transform: to(
|
||||||
|
[y, scale],
|
||||||
|
(y, s) => `translate3d(0,${y}px,0) scale(${s})`,
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<ItemRenderer index={i} item={items[i]} />
|
||||||
|
</div>
|
||||||
|
</animated.div>
|
||||||
|
))}
|
||||||
|
</DraggableListWrapper>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DraggableList.displayName = "DraggableList";
|
DraggableList.displayName = "DraggableList";
|
||||||
|
|
|
||||||
162
app/client/src/components/ads/DraggableListCard.tsx
Normal file
162
app/client/src/components/ads/DraggableListCard.tsx
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
import React, { useCallback, useState, useRef, useEffect } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import _ from "lodash";
|
||||||
|
import {
|
||||||
|
StyledDragIcon,
|
||||||
|
StyledOptionControlInputGroup,
|
||||||
|
StyledEditIcon,
|
||||||
|
StyledDeleteIcon,
|
||||||
|
StyledVisibleIcon,
|
||||||
|
StyledHiddenIcon,
|
||||||
|
} from "components/propertyControls/StyledControls";
|
||||||
|
import { Colors } from "constants/Colors";
|
||||||
|
|
||||||
|
const ItemWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
&.has-duplicate-label > div:nth-child(2) {
|
||||||
|
border: 1px solid ${Colors.DANGER_SOLID};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type RenderComponentProps = {
|
||||||
|
focusedIndex: number | null | undefined;
|
||||||
|
index: number;
|
||||||
|
item: {
|
||||||
|
label: string;
|
||||||
|
isDerived?: boolean;
|
||||||
|
isVisible?: boolean;
|
||||||
|
isDuplicateLabel?: boolean;
|
||||||
|
};
|
||||||
|
isDelete?: boolean;
|
||||||
|
isDragging: boolean;
|
||||||
|
placeholder: string;
|
||||||
|
updateFocus?: (index: number, isFocused: boolean) => void;
|
||||||
|
updateOption: (index: number, value: string) => void;
|
||||||
|
onEdit?: (index: number) => void;
|
||||||
|
deleteOption: (index: number) => void;
|
||||||
|
toggleVisibility?: (index: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DraggableListCard(props: RenderComponentProps) {
|
||||||
|
const [value, setValue] = useState(props.item.label);
|
||||||
|
const [isEditing, setEditing] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
deleteOption,
|
||||||
|
focusedIndex,
|
||||||
|
index,
|
||||||
|
isDelete,
|
||||||
|
isDragging,
|
||||||
|
item,
|
||||||
|
onEdit,
|
||||||
|
placeholder,
|
||||||
|
toggleVisibility,
|
||||||
|
updateFocus,
|
||||||
|
updateOption,
|
||||||
|
} = props;
|
||||||
|
const [visibility, setVisibility] = useState(item.isVisible);
|
||||||
|
const ref = useRef<HTMLInputElement | null>(null);
|
||||||
|
const debouncedUpdate = _.debounce(updateOption, 1000);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEditing && item && item.label) setValue(item.label);
|
||||||
|
}, [item?.label, isEditing]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (focusedIndex !== null && focusedIndex === index && !isDragging) {
|
||||||
|
if (ref && ref.current) {
|
||||||
|
ref?.current.focus();
|
||||||
|
}
|
||||||
|
} else if (isDragging && focusedIndex === index) {
|
||||||
|
if (ref && ref.current) {
|
||||||
|
ref?.current.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [focusedIndex, isDragging]);
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(index: number, value: string) => {
|
||||||
|
setValue(value);
|
||||||
|
debouncedUpdate(index, value);
|
||||||
|
},
|
||||||
|
[updateOption],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onFocus = () => {
|
||||||
|
setEditing(false);
|
||||||
|
if (updateFocus) {
|
||||||
|
updateFocus(index, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBlur = () => {
|
||||||
|
if (!isDragging) {
|
||||||
|
setEditing(false);
|
||||||
|
if (updateFocus) {
|
||||||
|
updateFocus(index, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemWrapper
|
||||||
|
className={props.item.isDuplicateLabel ? "has-duplicate-label" : ""}
|
||||||
|
>
|
||||||
|
<StyledDragIcon height={20} width={20} />
|
||||||
|
<StyledOptionControlInputGroup
|
||||||
|
autoFocus={index === focusedIndex}
|
||||||
|
dataType="text"
|
||||||
|
onBlur={onBlur}
|
||||||
|
onChange={(value: string) => {
|
||||||
|
onChange(index, value);
|
||||||
|
}}
|
||||||
|
onFocus={onFocus}
|
||||||
|
placeholder={placeholder}
|
||||||
|
ref={ref}
|
||||||
|
value={value}
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
<StyledEditIcon
|
||||||
|
className="t--edit-column-btn"
|
||||||
|
height={20}
|
||||||
|
onClick={() => {
|
||||||
|
onEdit && onEdit(index);
|
||||||
|
}}
|
||||||
|
width={20}
|
||||||
|
/>
|
||||||
|
{!!item.isDerived || isDelete ? (
|
||||||
|
<StyledDeleteIcon
|
||||||
|
className="t--delete-column-btn"
|
||||||
|
height={20}
|
||||||
|
onClick={() => {
|
||||||
|
deleteOption && deleteOption(index);
|
||||||
|
}}
|
||||||
|
width={20}
|
||||||
|
/>
|
||||||
|
) : visibility ? (
|
||||||
|
<StyledVisibleIcon
|
||||||
|
className="t--show-column-btn"
|
||||||
|
height={20}
|
||||||
|
onClick={() => {
|
||||||
|
setVisibility(!visibility);
|
||||||
|
toggleVisibility && toggleVisibility(index);
|
||||||
|
}}
|
||||||
|
width={20}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<StyledHiddenIcon
|
||||||
|
className="t--show-column-btn"
|
||||||
|
height={20}
|
||||||
|
onClick={() => {
|
||||||
|
setVisibility(!visibility);
|
||||||
|
toggleVisibility && toggleVisibility(index);
|
||||||
|
}}
|
||||||
|
width={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ItemWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,13 @@ import React from "react";
|
||||||
import DraggableList from "./DraggableList";
|
import DraggableList from "./DraggableList";
|
||||||
|
|
||||||
type RenderComponentProps = {
|
type RenderComponentProps = {
|
||||||
|
focusedIndex: number | null | undefined;
|
||||||
index: number;
|
index: number;
|
||||||
item: {
|
item: {
|
||||||
label: string;
|
label: string;
|
||||||
isDerived?: boolean;
|
isDerived?: boolean;
|
||||||
};
|
};
|
||||||
|
isDragging: boolean;
|
||||||
deleteOption: (index: number) => void;
|
deleteOption: (index: number) => void;
|
||||||
updateOption: (index: number, value: string) => void;
|
updateOption: (index: number, value: string) => void;
|
||||||
toggleVisibility?: (index: number) => void;
|
toggleVisibility?: (index: number) => void;
|
||||||
|
|
@ -16,6 +18,8 @@ type RenderComponentProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DroppableComponentProps {
|
interface DroppableComponentProps {
|
||||||
|
fixedHeight?: number | boolean;
|
||||||
|
focusedIndex?: number | null | undefined;
|
||||||
items: Array<Record<string, unknown>>;
|
items: Array<Record<string, unknown>>;
|
||||||
itemHeight: number;
|
itemHeight: number;
|
||||||
renderComponent: (props: RenderComponentProps) => JSX.Element;
|
renderComponent: (props: RenderComponentProps) => JSX.Element;
|
||||||
|
|
@ -34,11 +38,18 @@ export class DroppableComponent extends React.Component<
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(prevProps: DroppableComponentProps) {
|
public readonly state = {
|
||||||
|
isDragging: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate(prevProps: DroppableComponentProps, prevState: any) {
|
||||||
const presentOrder = this.props.items.map(this.getVisibleObject);
|
const presentOrder = this.props.items.map(this.getVisibleObject);
|
||||||
const previousOrder = prevProps.items.map(this.getVisibleObject);
|
const previousOrder = prevProps.items.map(this.getVisibleObject);
|
||||||
|
return (
|
||||||
return !isEqual(presentOrder, previousOrder);
|
!isEqual(presentOrder, previousOrder) ||
|
||||||
|
this.props.focusedIndex !== prevProps.focusedIndex ||
|
||||||
|
prevState.isDragging !== this.state.isDragging
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getVisibleObject(item: Record<string, unknown>) {
|
getVisibleObject(item: Record<string, unknown>) {
|
||||||
|
|
@ -52,14 +63,26 @@ export class DroppableComponent extends React.Component<
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate = (itemsOrder: number[]) => {
|
onUpdate = (
|
||||||
|
itemsOrder: number[],
|
||||||
|
originalIndex: number,
|
||||||
|
newIndex: number,
|
||||||
|
) => {
|
||||||
const newOrderedItems = itemsOrder.map((each) => this.props.items[each]);
|
const newOrderedItems = itemsOrder.map((each) => this.props.items[each]);
|
||||||
this.props.updateItems(newOrderedItems);
|
this.props.updateItems(newOrderedItems);
|
||||||
|
if (this.props.updateFocus && originalIndex !== newIndex) {
|
||||||
|
this.props.updateFocus(newIndex, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDragging = (isDragging: boolean) => {
|
||||||
|
this.setState({ isDragging });
|
||||||
};
|
};
|
||||||
|
|
||||||
renderItem = ({ index, item }: any) => {
|
renderItem = ({ index, item }: any) => {
|
||||||
const {
|
const {
|
||||||
deleteOption,
|
deleteOption,
|
||||||
|
focusedIndex,
|
||||||
onEdit,
|
onEdit,
|
||||||
renderComponent,
|
renderComponent,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
|
|
@ -73,8 +96,10 @@ export class DroppableComponent extends React.Component<
|
||||||
updateOption,
|
updateOption,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
focusedIndex,
|
||||||
item,
|
item,
|
||||||
index,
|
index,
|
||||||
|
isDragging: this.state.isDragging,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,10 +107,13 @@ export class DroppableComponent extends React.Component<
|
||||||
return (
|
return (
|
||||||
<DraggableList
|
<DraggableList
|
||||||
ItemRenderer={this.renderItem}
|
ItemRenderer={this.renderItem}
|
||||||
|
fixedHeight={this.props.fixedHeight}
|
||||||
|
focusedIndex={this.props.focusedIndex}
|
||||||
itemHeight={45}
|
itemHeight={45}
|
||||||
items={this.props.items}
|
items={this.props.items}
|
||||||
onUpdate={this.onUpdate}
|
onUpdate={this.onUpdate}
|
||||||
shouldReRender={false}
|
shouldReRender={false}
|
||||||
|
updateDragging={this.updateDragging}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ type RenderComponentProps = {
|
||||||
isDerived?: boolean;
|
isDerived?: boolean;
|
||||||
};
|
};
|
||||||
deleteOption: (index: number) => void;
|
deleteOption: (index: number) => void;
|
||||||
|
updateCurrentFocusedInput: (index: number | null) => void;
|
||||||
updateOption: (index: number, value: string) => void;
|
updateOption: (index: number, value: string) => void;
|
||||||
toggleVisibility?: (index: number) => void;
|
toggleVisibility?: (index: number) => void;
|
||||||
onEdit?: (index: number) => void;
|
onEdit?: (index: number) => void;
|
||||||
|
|
@ -26,6 +27,7 @@ interface DroppableComponentProps {
|
||||||
items: Array<Record<string, unknown>>;
|
items: Array<Record<string, unknown>>;
|
||||||
renderComponent: (props: RenderComponentProps) => JSX.Element;
|
renderComponent: (props: RenderComponentProps) => JSX.Element;
|
||||||
deleteOption: (index: number) => void;
|
deleteOption: (index: number) => void;
|
||||||
|
updateCurrentFocusedInput: (index: number | null) => void;
|
||||||
updateOption: (index: number, value: string) => void;
|
updateOption: (index: number, value: string) => void;
|
||||||
toggleVisibility?: (index: number) => void;
|
toggleVisibility?: (index: number) => void;
|
||||||
updateItems: (items: Array<Record<string, unknown>>) => void;
|
updateItems: (items: Array<Record<string, unknown>>) => void;
|
||||||
|
|
@ -85,6 +87,7 @@ export class DroppableComponent extends React.Component<
|
||||||
onEdit,
|
onEdit,
|
||||||
renderComponent,
|
renderComponent,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
|
updateCurrentFocusedInput,
|
||||||
updateOption,
|
updateOption,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
|
|
@ -116,6 +119,7 @@ export class DroppableComponent extends React.Component<
|
||||||
>
|
>
|
||||||
{renderComponent({
|
{renderComponent({
|
||||||
deleteOption,
|
deleteOption,
|
||||||
|
updateCurrentFocusedInput,
|
||||||
updateOption,
|
updateOption,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React from "react";
|
||||||
import BaseControl, { ControlProps } from "./BaseControl";
|
import BaseControl, { ControlProps } from "./BaseControl";
|
||||||
import {
|
import { StyledPropertyPaneButton } from "./StyledControls";
|
||||||
StyledPropertyPaneButton,
|
|
||||||
StyledDragIcon,
|
|
||||||
StyledDeleteIcon,
|
|
||||||
StyledEditIcon,
|
|
||||||
StyledOptionControlInputGroup,
|
|
||||||
} from "./StyledControls";
|
|
||||||
import styled from "constants/DefaultTheme";
|
import styled from "constants/DefaultTheme";
|
||||||
import { generateReactKey } from "utils/generators";
|
import { generateReactKey } from "utils/generators";
|
||||||
import { DroppableComponent } from "components/ads/DraggableListComponent";
|
import { DroppableComponent } from "components/ads/DraggableListComponent";
|
||||||
import { getNextEntityName } from "utils/AppsmithUtils";
|
import { getNextEntityName } from "utils/AppsmithUtils";
|
||||||
import _, { debounce } from "lodash";
|
import _ from "lodash";
|
||||||
import { Category, Size } from "components/ads/Button";
|
import { Category, Size } from "components/ads/Button";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
import { ButtonPlacementTypes } from "components/constants";
|
import { ButtonPlacementTypes } from "components/constants";
|
||||||
|
import { DraggableListCard } from "components/ads/DraggableListCard";
|
||||||
|
|
||||||
const StyledPropertyPaneButtonWrapper = styled.div`
|
const StyledPropertyPaneButtonWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -23,12 +18,6 @@ const StyledPropertyPaneButtonWrapper = styled.div`
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ButtonWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ButtonListWrapper = styled.div`
|
const ButtonListWrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -40,77 +29,28 @@ const AddNewButton = styled(StyledPropertyPaneButton)`
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RenderComponentProps = {
|
type State = {
|
||||||
index: number;
|
focusedIndex: number | null;
|
||||||
item: {
|
|
||||||
label: string;
|
|
||||||
isVisible?: boolean;
|
|
||||||
};
|
|
||||||
deleteOption: (index: number) => void;
|
|
||||||
updateOption: (index: number, value: string) => void;
|
|
||||||
toggleVisibility?: (index: number) => void;
|
|
||||||
onEdit?: (props: any) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function GroupButtonComponent(props: RenderComponentProps) {
|
class ButtonListControl extends BaseControl<ControlProps, State> {
|
||||||
const { deleteOption, index, item, updateOption } = props;
|
constructor(props: ControlProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
const [value, setValue] = useState(item.label);
|
this.state = {
|
||||||
const [isEditing, setEditing] = useState(false);
|
focusedIndex: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
componentDidUpdate(prevProps: ControlProps): void {
|
||||||
if (!isEditing && item && item.label) setValue(item.label);
|
//on adding a new column last column should get focused
|
||||||
}, [item?.label, isEditing]);
|
if (
|
||||||
|
Object.keys(prevProps.propertyValue).length + 1 ===
|
||||||
const debouncedUpdate = debounce(updateOption, 1000);
|
Object.keys(this.props.propertyValue).length
|
||||||
const onChange = useCallback(
|
) {
|
||||||
(index: number, value: string) => {
|
this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true);
|
||||||
setValue(value);
|
}
|
||||||
debouncedUpdate(index, value);
|
}
|
||||||
},
|
|
||||||
[updateOption],
|
|
||||||
);
|
|
||||||
const handleChange = useCallback(() => props.onEdit && props.onEdit(index), [
|
|
||||||
index,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const onFocus = () => setEditing(true);
|
|
||||||
const onBlur = () => setEditing(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ButtonWrapper>
|
|
||||||
<StyledDragIcon height={20} width={20} />
|
|
||||||
<StyledOptionControlInputGroup
|
|
||||||
dataType="text"
|
|
||||||
onBlur={onBlur}
|
|
||||||
onChange={(value: string) => {
|
|
||||||
onChange(index, value);
|
|
||||||
}}
|
|
||||||
onFocus={onFocus}
|
|
||||||
placeholder="Button label"
|
|
||||||
trimValue={false}
|
|
||||||
value={value}
|
|
||||||
/>
|
|
||||||
<StyledDeleteIcon
|
|
||||||
className="t--delete-tab-btn"
|
|
||||||
height={20}
|
|
||||||
marginRight={12}
|
|
||||||
onClick={() => {
|
|
||||||
deleteOption(index);
|
|
||||||
}}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
<StyledEditIcon
|
|
||||||
className="t--edit-column-btn"
|
|
||||||
height={20}
|
|
||||||
onClick={handleChange}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
</ButtonWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ButtonListControl extends BaseControl<ControlProps> {
|
|
||||||
updateItems = (items: Array<Record<string, any>>) => {
|
updateItems = (items: Array<Record<string, any>>) => {
|
||||||
const menuItems = items.reduce((obj: any, each: any, index: number) => {
|
const menuItems = items.reduce((obj: any, each: any, index: number) => {
|
||||||
obj[each.id] = {
|
obj[each.id] = {
|
||||||
|
|
@ -146,11 +86,20 @@ class ButtonListControl extends BaseControl<ControlProps> {
|
||||||
<ButtonListWrapper>
|
<ButtonListWrapper>
|
||||||
<DroppableComponent
|
<DroppableComponent
|
||||||
deleteOption={this.deleteOption}
|
deleteOption={this.deleteOption}
|
||||||
|
fixedHeight={370}
|
||||||
|
focusedIndex={this.state.focusedIndex}
|
||||||
itemHeight={45}
|
itemHeight={45}
|
||||||
items={menuItems}
|
items={menuItems}
|
||||||
onEdit={this.onEdit}
|
onEdit={this.onEdit}
|
||||||
renderComponent={GroupButtonComponent}
|
renderComponent={(props) =>
|
||||||
|
DraggableListCard({
|
||||||
|
...props,
|
||||||
|
isDelete: true,
|
||||||
|
placeholder: "Button label",
|
||||||
|
})
|
||||||
|
}
|
||||||
toggleVisibility={this.toggleVisibility}
|
toggleVisibility={this.toggleVisibility}
|
||||||
|
updateFocus={this.updateFocus}
|
||||||
updateItems={this.updateItems}
|
updateItems={this.updateItems}
|
||||||
updateOption={this.updateOption}
|
updateOption={this.updateOption}
|
||||||
/>
|
/>
|
||||||
|
|
@ -246,6 +195,10 @@ class ButtonListControl extends BaseControl<ControlProps> {
|
||||||
this.updateProperty(this.props.propertyName, groupButtons);
|
this.updateProperty(this.props.propertyName, groupButtons);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateFocus = (index: number, isFocused: boolean) => {
|
||||||
|
this.setState({ focusedIndex: isFocused ? index : null });
|
||||||
|
};
|
||||||
|
|
||||||
static getControlType() {
|
static getControlType() {
|
||||||
return "GROUP_BUTTONS";
|
return "GROUP_BUTTONS";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React from "react";
|
||||||
import BaseControl, { ControlProps } from "./BaseControl";
|
import BaseControl, { ControlProps } from "./BaseControl";
|
||||||
import {
|
import { StyledPropertyPaneButton } from "./StyledControls";
|
||||||
StyledPropertyPaneButton,
|
|
||||||
StyledDragIcon,
|
|
||||||
StyledDeleteIcon,
|
|
||||||
StyledEditIcon,
|
|
||||||
StyledOptionControlInputGroup,
|
|
||||||
} from "./StyledControls";
|
|
||||||
import styled from "constants/DefaultTheme";
|
import styled from "constants/DefaultTheme";
|
||||||
import { generateReactKey } from "utils/generators";
|
import { generateReactKey } from "utils/generators";
|
||||||
import { DroppableComponent } from "components/ads/DraggableListComponent";
|
import { DroppableComponent } from "components/ads/DraggableListComponent";
|
||||||
import { getNextEntityName } from "utils/AppsmithUtils";
|
import { getNextEntityName } from "utils/AppsmithUtils";
|
||||||
import _, { debounce, orderBy } from "lodash";
|
import _, { orderBy } from "lodash";
|
||||||
import { Category, Size } from "components/ads/Button";
|
import { Category, Size } from "components/ads/Button";
|
||||||
|
import { DraggableListCard } from "components/ads/DraggableListCard";
|
||||||
|
|
||||||
const StyledPropertyPaneButtonWrapper = styled.div`
|
const StyledPropertyPaneButtonWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -21,12 +16,6 @@ const StyledPropertyPaneButtonWrapper = styled.div`
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ItemWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MenuItemsWrapper = styled.div`
|
const MenuItemsWrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -38,77 +27,30 @@ const AddMenuItemButton = styled(StyledPropertyPaneButton)`
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RenderComponentProps = {
|
type State = {
|
||||||
index: number;
|
focusedIndex: number | null;
|
||||||
item: {
|
|
||||||
label: string;
|
|
||||||
isVisible?: boolean;
|
|
||||||
};
|
|
||||||
deleteOption: (index: number) => void;
|
|
||||||
updateOption: (index: number, value: string) => void;
|
|
||||||
toggleVisibility?: (index: number) => void;
|
|
||||||
onEdit?: (props: any) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function MenuItemComponent(props: RenderComponentProps) {
|
class MenuItemsControl extends BaseControl<ControlProps, State> {
|
||||||
const { deleteOption, index, item, updateOption } = props;
|
constructor(props: ControlProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
const [value, setValue] = useState(item.label);
|
this.state = {
|
||||||
const [isEditing, setEditing] = useState(false);
|
focusedIndex: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
componentDidUpdate(prevProps: ControlProps): void {
|
||||||
if (!isEditing && item && item.label) setValue(item.label);
|
//on adding a new column last column should get focused
|
||||||
}, [item?.label, isEditing]);
|
if (
|
||||||
|
prevProps.propertyValue &&
|
||||||
const debouncedUpdate = debounce(updateOption, 1000);
|
this.props.propertyValue &&
|
||||||
const onChange = useCallback(
|
Object.keys(prevProps.propertyValue).length + 1 ===
|
||||||
(index: number, value: string) => {
|
Object.keys(this.props.propertyValue).length
|
||||||
setValue(value);
|
) {
|
||||||
debouncedUpdate(index, value);
|
this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true);
|
||||||
},
|
}
|
||||||
[updateOption],
|
}
|
||||||
);
|
|
||||||
const handleChange = useCallback(() => props.onEdit && props.onEdit(index), [
|
|
||||||
index,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const onFocus = () => setEditing(true);
|
|
||||||
const onBlur = () => setEditing(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ItemWrapper>
|
|
||||||
<StyledDragIcon height={20} width={20} />
|
|
||||||
<StyledOptionControlInputGroup
|
|
||||||
dataType="text"
|
|
||||||
onBlur={onBlur}
|
|
||||||
onChange={(value: string) => {
|
|
||||||
onChange(index, value);
|
|
||||||
}}
|
|
||||||
onFocus={onFocus}
|
|
||||||
placeholder="Menu item label"
|
|
||||||
trimValue={false}
|
|
||||||
value={value}
|
|
||||||
/>
|
|
||||||
<StyledDeleteIcon
|
|
||||||
className="t--delete-tab-btn"
|
|
||||||
height={20}
|
|
||||||
marginRight={12}
|
|
||||||
onClick={() => {
|
|
||||||
deleteOption(index);
|
|
||||||
}}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
<StyledEditIcon
|
|
||||||
className="t--edit-column-btn"
|
|
||||||
height={20}
|
|
||||||
onClick={handleChange}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
</ItemWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MenuItemsControl extends BaseControl<ControlProps> {
|
|
||||||
updateItems = (items: Array<Record<string, any>>) => {
|
updateItems = (items: Array<Record<string, any>>) => {
|
||||||
const menuItems = items.reduce((obj: any, each: any, index: number) => {
|
const menuItems = items.reduce((obj: any, each: any, index: number) => {
|
||||||
obj[each.id] = {
|
obj[each.id] = {
|
||||||
|
|
@ -146,11 +88,20 @@ class MenuItemsControl extends BaseControl<ControlProps> {
|
||||||
<MenuItemsWrapper>
|
<MenuItemsWrapper>
|
||||||
<DroppableComponent
|
<DroppableComponent
|
||||||
deleteOption={this.deleteOption}
|
deleteOption={this.deleteOption}
|
||||||
|
fixedHeight={370}
|
||||||
|
focusedIndex={this.state.focusedIndex}
|
||||||
itemHeight={45}
|
itemHeight={45}
|
||||||
items={orderBy(menuItems, ["index"], ["asc"])}
|
items={orderBy(menuItems, ["index"], ["asc"])}
|
||||||
onEdit={this.onEdit}
|
onEdit={this.onEdit}
|
||||||
renderComponent={MenuItemComponent}
|
renderComponent={(props) =>
|
||||||
|
DraggableListCard({
|
||||||
|
...props,
|
||||||
|
isDelete: true,
|
||||||
|
placeholder: "Menu item label",
|
||||||
|
})
|
||||||
|
}
|
||||||
toggleVisibility={this.toggleVisibility}
|
toggleVisibility={this.toggleVisibility}
|
||||||
|
updateFocus={this.updateFocus}
|
||||||
updateItems={this.updateItems}
|
updateItems={this.updateItems}
|
||||||
updateOption={this.updateOption}
|
updateOption={this.updateOption}
|
||||||
/>
|
/>
|
||||||
|
|
@ -243,6 +194,10 @@ class MenuItemsControl extends BaseControl<ControlProps> {
|
||||||
this.updateProperty(this.props.propertyName, menuItems);
|
this.updateProperty(this.props.propertyName, menuItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateFocus = (index: number, isFocused: boolean) => {
|
||||||
|
this.setState({ focusedIndex: isFocused ? index : null });
|
||||||
|
};
|
||||||
|
|
||||||
static getControlType() {
|
static getControlType() {
|
||||||
return "MENU_ITEMS";
|
return "MENU_ITEMS";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,11 @@
|
||||||
import React, { useCallback, useEffect, useState, Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Placement } from "popper.js";
|
import { Placement } from "popper.js";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import BaseControl, { ControlProps } from "./BaseControl";
|
import BaseControl, { ControlProps } from "./BaseControl";
|
||||||
import {
|
import { StyledPropertyPaneButton } from "./StyledControls";
|
||||||
StyledDragIcon,
|
|
||||||
StyledEditIcon,
|
|
||||||
StyledDeleteIcon,
|
|
||||||
StyledVisibleIcon,
|
|
||||||
StyledHiddenIcon,
|
|
||||||
StyledPropertyPaneButton,
|
|
||||||
StyledOptionControlInputGroup,
|
|
||||||
} from "./StyledControls";
|
|
||||||
import styled from "constants/DefaultTheme";
|
import styled from "constants/DefaultTheme";
|
||||||
import { Indices } from "constants/Layers";
|
import { Indices } from "constants/Layers";
|
||||||
import { DroppableComponent } from "components/ads/DraggableListComponent";
|
import { DroppableComponent } from "components/ads/DraggableListComponent";
|
||||||
|
|
@ -37,17 +29,7 @@ import {
|
||||||
PropertyEvaluationErrorType,
|
PropertyEvaluationErrorType,
|
||||||
} from "utils/DynamicBindingUtils";
|
} from "utils/DynamicBindingUtils";
|
||||||
import { getNextEntityName } from "utils/AppsmithUtils";
|
import { getNextEntityName } from "utils/AppsmithUtils";
|
||||||
import { Colors } from "constants/Colors";
|
import { DraggableListCard } from "components/ads/DraggableListCard";
|
||||||
import { noop } from "utils/AppsmithUtils";
|
|
||||||
|
|
||||||
const ItemWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
&.has-duplicate-label > div:nth-child(2) {
|
|
||||||
border: 1px solid ${Colors.DANGER_SOLID};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TabsWrapper = styled.div`
|
const TabsWrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -83,21 +65,6 @@ type EvaluatedValuePopupWrapperProps = ReduxStateProps & {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RenderComponentProps = {
|
|
||||||
index: number;
|
|
||||||
item: {
|
|
||||||
label: string;
|
|
||||||
isDerived?: boolean;
|
|
||||||
isVisible?: boolean;
|
|
||||||
isDuplicateLabel?: boolean;
|
|
||||||
};
|
|
||||||
updateFocus?: (index: number, isFocused: boolean) => void;
|
|
||||||
updateOption: (index: number, value: string) => void;
|
|
||||||
onEdit?: (index: number) => void;
|
|
||||||
deleteOption: (index: number) => void;
|
|
||||||
toggleVisibility?: (index: number) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOriginalColumn = (
|
const getOriginalColumn = (
|
||||||
columns: Record<string, ColumnProperties>,
|
columns: Record<string, ColumnProperties>,
|
||||||
index: number,
|
index: number,
|
||||||
|
|
@ -110,107 +77,6 @@ const getOriginalColumn = (
|
||||||
return column;
|
return column;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ColumnControlComponent(props: RenderComponentProps) {
|
|
||||||
const [value, setValue] = useState(props.item.label);
|
|
||||||
const [isEditing, setEditing] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isEditing && props.item && props.item.label)
|
|
||||||
setValue(props.item.label);
|
|
||||||
}, [props.item?.label, isEditing]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
deleteOption,
|
|
||||||
index,
|
|
||||||
item,
|
|
||||||
onEdit,
|
|
||||||
toggleVisibility,
|
|
||||||
updateFocus,
|
|
||||||
updateOption,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [visibility, setVisibility] = useState(item.isVisible);
|
|
||||||
useEffect(() => {
|
|
||||||
setVisibility(item.isVisible);
|
|
||||||
}, [item.isVisible]);
|
|
||||||
const debouncedUpdate = _.debounce(updateOption, 1000);
|
|
||||||
const debouncedFocus = updateFocus ? _.debounce(updateFocus, 400) : noop;
|
|
||||||
const onChange = useCallback(
|
|
||||||
(index: number, value: string) => {
|
|
||||||
setValue(value);
|
|
||||||
debouncedUpdate(index, value);
|
|
||||||
},
|
|
||||||
[updateOption],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onFocus = () => {
|
|
||||||
setEditing(false);
|
|
||||||
debouncedFocus(index, true);
|
|
||||||
};
|
|
||||||
const onBlur = () => {
|
|
||||||
setEditing(false);
|
|
||||||
debouncedFocus(index, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ItemWrapper
|
|
||||||
className={props.item.isDuplicateLabel ? "has-duplicate-label" : ""}
|
|
||||||
>
|
|
||||||
<StyledDragIcon height={20} width={20} />
|
|
||||||
<StyledOptionControlInputGroup
|
|
||||||
dataType="text"
|
|
||||||
onBlur={onBlur}
|
|
||||||
onChange={(value: string) => {
|
|
||||||
onChange(index, value);
|
|
||||||
}}
|
|
||||||
onFocus={onFocus}
|
|
||||||
placeholder="Column Title"
|
|
||||||
trimValue={false}
|
|
||||||
value={value}
|
|
||||||
width="100%"
|
|
||||||
/>
|
|
||||||
<StyledEditIcon
|
|
||||||
className="t--edit-column-btn"
|
|
||||||
height={20}
|
|
||||||
onClick={() => {
|
|
||||||
onEdit && onEdit(index);
|
|
||||||
}}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
{!!item.isDerived ? (
|
|
||||||
<StyledDeleteIcon
|
|
||||||
className="t--delete-column-btn"
|
|
||||||
height={20}
|
|
||||||
onClick={() => {
|
|
||||||
deleteOption && deleteOption(index);
|
|
||||||
}}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
) : visibility ? (
|
|
||||||
<StyledVisibleIcon
|
|
||||||
className="t--show-column-btn"
|
|
||||||
height={20}
|
|
||||||
onClick={() => {
|
|
||||||
setVisibility(!visibility);
|
|
||||||
toggleVisibility && toggleVisibility(index);
|
|
||||||
}}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<StyledHiddenIcon
|
|
||||||
className="t--show-column-btn"
|
|
||||||
height={20}
|
|
||||||
onClick={() => {
|
|
||||||
setVisibility(!visibility);
|
|
||||||
toggleVisibility && toggleVisibility(index);
|
|
||||||
}}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ItemWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
focusedIndex: number | null;
|
focusedIndex: number | null;
|
||||||
duplicateColumnIds: string[];
|
duplicateColumnIds: string[];
|
||||||
|
|
@ -241,6 +107,16 @@ class PrimaryColumnsControl extends BaseControl<ControlProps, State> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: ControlProps): void {
|
||||||
|
//on adding a new column last column should get focused
|
||||||
|
if (
|
||||||
|
Object.keys(prevProps.propertyValue).length + 1 ===
|
||||||
|
Object.keys(this.props.propertyValue).length
|
||||||
|
) {
|
||||||
|
this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// Get columns from widget properties
|
// Get columns from widget properties
|
||||||
const columns: Record<string, ColumnProperties> =
|
const columns: Record<string, ColumnProperties> =
|
||||||
|
|
@ -291,10 +167,18 @@ class PrimaryColumnsControl extends BaseControl<ControlProps, State> {
|
||||||
<EvaluatedValuePopupWrapper {...this.props} isFocused={isFocused}>
|
<EvaluatedValuePopupWrapper {...this.props} isFocused={isFocused}>
|
||||||
<DroppableComponent
|
<DroppableComponent
|
||||||
deleteOption={this.deleteOption}
|
deleteOption={this.deleteOption}
|
||||||
|
fixedHeight={370}
|
||||||
|
focusedIndex={this.state.focusedIndex}
|
||||||
itemHeight={45}
|
itemHeight={45}
|
||||||
items={draggableComponentColumns}
|
items={draggableComponentColumns}
|
||||||
onEdit={this.onEdit}
|
onEdit={this.onEdit}
|
||||||
renderComponent={ColumnControlComponent}
|
renderComponent={(props) =>
|
||||||
|
DraggableListCard({
|
||||||
|
...props,
|
||||||
|
isDelete: false,
|
||||||
|
placeholder: "Column Title",
|
||||||
|
})
|
||||||
|
}
|
||||||
toggleVisibility={this.toggleVisibility}
|
toggleVisibility={this.toggleVisibility}
|
||||||
updateFocus={this.updateFocus}
|
updateFocus={this.updateFocus}
|
||||||
updateItems={this.updateItems}
|
updateItems={this.updateItems}
|
||||||
|
|
@ -445,6 +329,8 @@ class PrimaryColumnsControl extends BaseControl<ControlProps, State> {
|
||||||
this.setState({ focusedIndex: isFocused ? index : null });
|
this.setState({ focusedIndex: isFocused ? index : null });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// updateCurrentFocusedInput = (index: number | null) => {};
|
||||||
|
|
||||||
static getControlType() {
|
static getControlType() {
|
||||||
return "PRIMARY_COLUMNS";
|
return "PRIMARY_COLUMNS";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,16 @@
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React from "react";
|
||||||
import BaseControl, { ControlProps } from "./BaseControl";
|
import BaseControl, { ControlProps } from "./BaseControl";
|
||||||
import {
|
import { StyledPropertyPaneButton } from "./StyledControls";
|
||||||
StyledPropertyPaneButton,
|
|
||||||
StyledDragIcon,
|
|
||||||
StyledDeleteIcon,
|
|
||||||
StyledEditIcon,
|
|
||||||
StyledOptionControlInputGroup,
|
|
||||||
} from "./StyledControls";
|
|
||||||
import styled from "constants/DefaultTheme";
|
import styled from "constants/DefaultTheme";
|
||||||
import { generateReactKey } from "utils/generators";
|
import { generateReactKey } from "utils/generators";
|
||||||
import { DroppableComponent } from "components/ads/DraggableListComponent";
|
import { DroppableComponent } from "components/ads/DraggableListComponent";
|
||||||
import { getNextEntityName, noop } from "utils/AppsmithUtils";
|
import { getNextEntityName, noop } from "utils/AppsmithUtils";
|
||||||
import _, { debounce, orderBy } from "lodash";
|
import _, { orderBy } from "lodash";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { Category, Size } from "components/ads/Button";
|
import { Category, Size } from "components/ads/Button";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
|
import { DraggableListCard } from "components/ads/DraggableListCard";
|
||||||
|
|
||||||
const StyledPropertyPaneButtonWrapper = styled.div`
|
const StyledPropertyPaneButtonWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -24,12 +19,6 @@ const StyledPropertyPaneButtonWrapper = styled.div`
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ItemWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TabsWrapper = styled.div`
|
const TabsWrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -37,12 +26,15 @@ const TabsWrapper = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RenderComponentProps = {
|
type RenderComponentProps = {
|
||||||
|
focusedIndex: number | null | undefined;
|
||||||
index: number;
|
index: number;
|
||||||
|
isDragging: boolean;
|
||||||
item: {
|
item: {
|
||||||
label: string;
|
label: string;
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
};
|
};
|
||||||
deleteOption: (index: number) => void;
|
deleteOption: (index: number) => void;
|
||||||
|
updateFocus?: (index: number, isFocused: boolean) => void;
|
||||||
updateOption: (index: number, value: string) => void;
|
updateOption: (index: number, value: string) => void;
|
||||||
toggleVisibility?: (index: number) => void;
|
toggleVisibility?: (index: number) => void;
|
||||||
onEdit?: (props: any) => void;
|
onEdit?: (props: any) => void;
|
||||||
|
|
@ -74,7 +66,7 @@ function AddTabButtonComponent({ widgetId }: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabControlComponent(props: RenderComponentProps) {
|
function TabControlComponent(props: RenderComponentProps) {
|
||||||
const { index, item, updateOption } = props;
|
const { index, item } = props;
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const deleteOption = () => {
|
const deleteOption = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
@ -83,65 +75,42 @@ function TabControlComponent(props: RenderComponentProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const [value, setValue] = useState(item.label);
|
|
||||||
const [isEditing, setEditing] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isEditing && item && item.label) setValue(item.label);
|
|
||||||
}, [item?.label, isEditing]);
|
|
||||||
|
|
||||||
const debouncedUpdate = debounce(updateOption, 1000);
|
|
||||||
const handleChange = useCallback(() => props.onEdit && props.onEdit(index), [
|
|
||||||
index,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const onChange = useCallback(
|
|
||||||
(index: number, value: string) => {
|
|
||||||
setValue(value);
|
|
||||||
debouncedUpdate(index, value);
|
|
||||||
},
|
|
||||||
[updateOption],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onFocus = () => setEditing(true);
|
|
||||||
const onBlur = () => setEditing(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemWrapper>
|
<DraggableListCard
|
||||||
<StyledDragIcon height={20} width={20} />
|
{...props}
|
||||||
<StyledOptionControlInputGroup
|
deleteOption={deleteOption}
|
||||||
dataType="text"
|
isDelete
|
||||||
onBlur={onBlur}
|
placeholder="Tab Title"
|
||||||
onChange={(value: string) => {
|
/>
|
||||||
onChange(index, value);
|
|
||||||
}}
|
|
||||||
onFocus={onFocus}
|
|
||||||
placeholder="Tab Title"
|
|
||||||
trimValue={false}
|
|
||||||
value={value}
|
|
||||||
/>
|
|
||||||
<StyledDeleteIcon
|
|
||||||
className="t--delete-tab-btn"
|
|
||||||
height={20}
|
|
||||||
marginRight={12}
|
|
||||||
onClick={deleteOption}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
<StyledEditIcon
|
|
||||||
className="t--edit-column-btn"
|
|
||||||
height={20}
|
|
||||||
onClick={handleChange}
|
|
||||||
width={20}
|
|
||||||
/>
|
|
||||||
</ItemWrapper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabControl extends BaseControl<ControlProps> {
|
type State = {
|
||||||
|
focusedIndex: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TabControl extends BaseControl<ControlProps, State> {
|
||||||
|
constructor(props: ControlProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
focusedIndex: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.migrateTabData(this.props.propertyValue);
|
this.migrateTabData(this.props.propertyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: ControlProps): void {
|
||||||
|
//on adding a new column last column should get focused
|
||||||
|
if (
|
||||||
|
Object.keys(prevProps.propertyValue).length + 1 ===
|
||||||
|
Object.keys(this.props.propertyValue).length
|
||||||
|
) {
|
||||||
|
this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
migrateTabData(
|
migrateTabData(
|
||||||
tabData: Array<{
|
tabData: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -204,11 +173,14 @@ class TabControl extends BaseControl<ControlProps> {
|
||||||
<TabsWrapper>
|
<TabsWrapper>
|
||||||
<DroppableComponent
|
<DroppableComponent
|
||||||
deleteOption={noop}
|
deleteOption={noop}
|
||||||
|
fixedHeight={370}
|
||||||
|
focusedIndex={this.state.focusedIndex}
|
||||||
itemHeight={45}
|
itemHeight={45}
|
||||||
items={orderBy(tabs, ["index"], ["asc"])}
|
items={orderBy(tabs, ["index"], ["asc"])}
|
||||||
onEdit={this.onEdit}
|
onEdit={this.onEdit}
|
||||||
renderComponent={TabControlComponent}
|
renderComponent={TabControlComponent}
|
||||||
toggleVisibility={this.toggleVisibility}
|
toggleVisibility={this.toggleVisibility}
|
||||||
|
updateFocus={this.updateFocus}
|
||||||
updateItems={this.updateItems}
|
updateItems={this.updateItems}
|
||||||
updateOption={this.updateOption}
|
updateOption={this.updateOption}
|
||||||
/>
|
/>
|
||||||
|
|
@ -269,6 +241,10 @@ class TabControl extends BaseControl<ControlProps> {
|
||||||
this.updateProperty(this.props.propertyName, tabs);
|
this.updateProperty(this.props.propertyName, tabs);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateFocus = (index: number, isFocused: boolean) => {
|
||||||
|
this.setState({ focusedIndex: isFocused ? index : null });
|
||||||
|
};
|
||||||
|
|
||||||
static getControlType() {
|
static getControlType() {
|
||||||
return "TABS_INPUT";
|
return "TABS_INPUT";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user