diff --git a/package-lock.json b/package-lock.json index 80cd2ef..b3c57ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "hashids": "^2.2.8", "immutability-helper": "^3.1.1", "prop-types": "^15.8.1", - "react-grid-layout": "^1.3.4", + "react-grid-layout": "^1.4.4", "react-transition-group": "^4.4.5" }, "devDependencies": { @@ -35,7 +35,7 @@ "@types/jest": "^26.0.20", "@types/lodash": "^4.14.170", "@types/react": "^18.0.27", - "@types/react-grid-layout": "^1.3.2", + "@types/react-grid-layout": "^1.3.5", "babel-jest": "^26.6.3", "copyfiles": "^2.4.1", "enzyme": "^3.11.0", @@ -7070,9 +7070,10 @@ } }, "node_modules/@types/react-grid-layout": { - "version": "1.3.2", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz", + "integrity": "sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/react": "*" } @@ -9983,8 +9984,9 @@ } }, "node_modules/clsx": { - "version": "1.2.1", - "license": "MIT", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { "node": ">=6" } @@ -13238,6 +13240,11 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -18289,6 +18296,7 @@ }, "node_modules/lodash.isequal": { "version": "4.5.0", + "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { @@ -21248,8 +21256,9 @@ } }, "node_modules/react-draggable": { - "version": "4.4.5", - "license": "MIT", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", "dependencies": { "clsx": "^1.1.1", "prop-types": "^15.8.1" @@ -21259,6 +21268,14 @@ "react-dom": ">= 16.3.0" } }, + "node_modules/react-draggable/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/react-element-to-jsx-string": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", @@ -21295,14 +21312,16 @@ "license": "MIT" }, "node_modules/react-grid-layout": { - "version": "1.3.4", - "license": "MIT", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.4.4.tgz", + "integrity": "sha512-7+Lg8E8O8HfOH5FrY80GCIR1SHTn2QnAYKh27/5spoz+OHhMmEhU/14gIkRzJOtympDPaXcVRX/nT1FjmeOUmQ==", "dependencies": { - "clsx": "^1.1.1", - "lodash.isequal": "^4.0.0", + "clsx": "^2.0.0", + "fast-equals": "^4.0.3", "prop-types": "^15.8.1", - "react-draggable": "^4.0.0", - "react-resizable": "^3.0.4" + "react-draggable": "^4.4.5", + "react-resizable": "^3.0.5", + "resize-observer-polyfill": "^1.5.1" }, "peerDependencies": { "react": ">= 16.3.0", @@ -21421,8 +21440,9 @@ } }, "node_modules/react-resizable": { - "version": "3.0.4", - "license": "MIT", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", + "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", "dependencies": { "prop-types": "15.x", "react-draggable": "^4.0.3" @@ -21994,6 +22014,11 @@ "dev": true, "license": "ISC" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "node_modules/resolve": { "version": "1.22.1", "dev": true, diff --git a/package.json b/package.json index 8e157a0..d420911 100644 --- a/package.json +++ b/package.json @@ -60,12 +60,12 @@ "prepublishOnly": "npm run lint && npm run test && npm run build" }, "dependencies": { - "@gravity-ui/icons": "^2.8.1", "@bem-react/classname": "^1.6.0", + "@gravity-ui/icons": "^2.8.1", "hashids": "^2.2.8", "immutability-helper": "^3.1.1", "prop-types": "^15.8.1", - "react-grid-layout": "^1.3.4", + "react-grid-layout": "^1.4.4", "react-transition-group": "^4.4.5" }, "peerDependencies": { @@ -90,7 +90,7 @@ "@types/jest": "^26.0.20", "@types/lodash": "^4.14.170", "@types/react": "^18.0.27", - "@types/react-grid-layout": "^1.3.2", + "@types/react-grid-layout": "^1.3.5", "babel-jest": "^26.6.3", "copyfiles": "^2.4.1", "enzyme": "^3.11.0", diff --git a/src/components/DashKit/DashKit.tsx b/src/components/DashKit/DashKit.tsx index 3506258..392783d 100644 --- a/src/components/DashKit/DashKit.tsx +++ b/src/components/DashKit/DashKit.tsx @@ -13,6 +13,7 @@ import type { import { AddConfigItem, ContextProps, + DashKitGroup, Plugin, SetConfigItem, SetNewItemOptions, @@ -33,7 +34,11 @@ interface DashKitGeneralProps { interface DashKitDefaultProps { onItemEdit: (item: ConfigItem) => void; - onChange: (data: {config: Config; itemsStateAndParams: ItemsStateAndParams}) => void; + onChange: (data: { + config: Config; + itemsStateAndParams: ItemsStateAndParams; + groups?: DashKitGroup[]; + }) => void; onDrop: (dropProps: ItemDropProps) => void; defaultGlobalParams: GlobalParams; globalParams: GlobalParams; @@ -42,6 +47,7 @@ interface DashKitDefaultProps { context: ContextProps; noOverlay: boolean; focusable?: boolean; + groups?: DashKitGroup[]; } export interface DashKitProps extends DashKitGeneralProps, Partial {} @@ -123,14 +129,7 @@ export class DashKit extends React.PureComponent { metaRef = React.createRef(); render() { - return ( - - ); + return ; } getItemsMeta() { diff --git a/src/components/DashKit/__stories__/DashKit.stories.tsx b/src/components/DashKit/__stories__/DashKit.stories.tsx index b456c3d..6531763 100644 --- a/src/components/DashKit/__stories__/DashKit.stories.tsx +++ b/src/components/DashKit/__stories__/DashKit.stories.tsx @@ -9,6 +9,7 @@ import {DashKit, DashKitProps} from '../DashKit'; import {CssApiShowcase} from './CssApiShowcase'; import {DashKitDnDShowcase} from './DashKitDnDShowcase'; +import {DashKitGroupsShowcase} from './DashKitGroupsShowcase'; import {DashKitShowcase} from './DashKitShowcase'; import {getConfig} from './utils'; @@ -81,3 +82,6 @@ export const CSS_API = CssApiShowcaseTemplate.bind({}); const DndShowcaseTemplate: Story = () => ; export const DragNDrop = DndShowcaseTemplate.bind({}); + +const GroupsShowcaseTemplate: Story = () => ; +export const Groups = GroupsShowcaseTemplate.bind({}); diff --git a/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx b/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx new file mode 100644 index 0000000..e4d30a4 --- /dev/null +++ b/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx @@ -0,0 +1,224 @@ +import React from 'react'; + +import {ChartColumn, Copy, Heading, Sliders, TextAlignLeft} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; + +import { + ActionPanel, + DashKit, + DashKitDnDWrapper, + DashKitProps, + DashkitGroupRenderProps, +} from '../../..'; +import {DEFAULT_GROUP, MenuItems} from '../../../helpers'; +import {i18n} from '../../../i18n'; +import {CogIcon} from '../../../icons/CogIcon'; +import {CopyIcon} from '../../../icons/CopyIcon'; +import {DeleteIcon} from '../../../icons/DeleteIcon'; + +import {Demo, DemoRow} from './Demo'; +import {fixedGroup, getConfig} from './utils'; + +export const DashKitGroupsShowcase: React.FC = () => { + const [editMode, setEditMode] = React.useState(true); + + React.useEffect(() => { + DashKit.setSettings({ + menu: [ + { + id: 'settings', + title: 'Menu setting text', + icon: , + }, + { + id: MenuItems.Copy, + title: 'Menu setting copy', + icon: , + }, + { + id: MenuItems.Delete, + title: i18n('label_delete'), // for language change check + icon: , + className: 'dashkit-overlay-controls__item_danger', + }, + ], + }); + }, []); + + const onClick = () => { + console.log('click'); + }; + + const items = React.useMemo( + () => [ + { + id: 'chart', + icon: , + title: 'Chart', + className: 'test', + qa: 'chart', + dragProps: { + type: 'custom', + }, + onClick, + }, + { + id: 'selector', + icon: , + title: 'Selector', + qa: 'selector', + dragProps: { + type: 'custom', + }, + onClick, + }, + { + id: 'text', + icon: , + title: 'Text', + dragProps: { + type: 'text', + }, + onClick, + }, + { + id: 'header', + icon: , + title: 'Header', + dragProps: { + type: 'title', + }, + onClick, + }, + { + id: 'custom', + icon: , + title: 'Custom', + dragProps: { + type: 'title', + layout: { + h: 10, + w: 36, + }, + }, + onClick, + }, + ], + [], + ); + const [config, setConfig] = React.useState(getConfig(true)); + + const onChange = React.useCallback(({config}: {config: DashKitProps['config']}) => { + setConfig(config); + }, []); + + const onDrop = React.useCallback>( + (dropProps) => { + let data = null; + const type = dropProps.dragProps?.type; + if (type === 'custom') { + data = {}; + } else { + const text = prompt('Enter text'); + if (text) { + data = + type === 'title' + ? { + size: 'm', + text, + showInTOC: true, + } + : {text}; + } + } + + if (data) { + const newConfig = DashKit.setItem({ + item: { + data, + type, + namespace: 'default', + layout: dropProps.itemLayout, + }, + config, + options: { + updateLayout: dropProps.newLayout, + }, + }); + setConfig(newConfig); + } + + dropProps.commit(); + }, + [config], + ); + + const groups = React.useMemo( + () => [ + { + id: fixedGroup, + render: (id: string, children: React.ReactNode, props: DashkitGroupRenderProps) => { + const defaultStyles: React.CSSProperties = { + backgroundColor: '#ccc', + display: 'flex', + flexDirection: 'column', + }; + + const style: React.CSSProperties = props.editMode + ? { + position: 'static', + overflow: 'visible', + minHeight: 48, + } + : { + position: 'sticky', + overflow: 'auto', + top: 0, + zIndex: 3, + maxHeight: 300, + minHeight: 'unset', + }; + + return ( +
+ {children} +
+ ); + }, + }, + { + id: DEFAULT_GROUP, + }, + ], + [], + ); + + return ( + { + console.log('dragStarted'); + }} + onDragEnd={() => { + console.log('dragEnded'); + }} + > + + + + + + + + + + + ); +}; diff --git a/src/components/DashKit/__stories__/utils.ts b/src/components/DashKit/__stories__/utils.ts index 6e1fee9..a0ed30e 100644 --- a/src/components/DashKit/__stories__/utils.ts +++ b/src/components/DashKit/__stories__/utils.ts @@ -12,7 +12,9 @@ export function makeid(length: number) { export const titleId = 'nk'; -export const getConfig = (): DashKitProps['config'] => ({ +export const fixedGroup = 'fixedGroup'; + +export const getConfig = (withGroups?: boolean): DashKitProps['config'] => ({ salt: '0.46703554571365613', counter: 5, items: [ @@ -54,6 +56,29 @@ export const getConfig = (): DashKitProps['config'] => ({ namespace: 'default', orderId: 5, }, + ...(withGroups + ? [ + { + id: 'Fk', + data: { + size: 'm', + text: 'Title group widget', + showInTOC: true, + }, + type: 'title', + namespace: 'default', + orderId: 1, + }, + { + id: 'Fr', + data: { + text: 'special mode _editActive', + }, + type: 'text', + namespace: 'default', + }, + ] + : []), ], layout: [ { @@ -84,6 +109,26 @@ export const getConfig = (): DashKitProps['config'] => ({ x: 0, y: 8, }, + ...(withGroups + ? [ + { + h: 2, + i: 'Fk', + w: 36, + x: 0, + y: 0, + parent: fixedGroup, + }, + { + h: 6, + i: 'Fr', + w: 12, + x: 0, + y: 2, + parent: fixedGroup, + }, + ] + : []), ], aliases: {}, connections: [], diff --git a/src/components/DashKitView/DashKitView.tsx b/src/components/DashKitView/DashKitView.tsx index b9fbaf7..d16c4b0 100644 --- a/src/components/DashKitView/DashKitView.tsx +++ b/src/components/DashKitView/DashKitView.tsx @@ -35,6 +35,7 @@ const DashKitViewWithContext = withContext(DashKitView); const DashKitViewForwardedMeta = React.forwardRef((props: DashKitViewProps, ref) => { const layout = useCalcPropsLayout(props.config, props.registerManager); + return ; }); diff --git a/src/components/GridLayout/GridLayout.js b/src/components/GridLayout/GridLayout.js index 5b23545..4fabddd 100644 --- a/src/components/GridLayout/GridLayout.js +++ b/src/components/GridLayout/GridLayout.js @@ -1,6 +1,6 @@ import React from 'react'; -import {OVERLAY_CONTROLS_CLASS_NAME, TEMPORARY_ITEM_ID} from '../../constants'; +import {DEFAULT_GROUP, OVERLAY_CONTROLS_CLASS_NAME, TEMPORARY_ITEM_ID} from '../../constants'; import {DashKitContext} from '../../context/DashKitContext'; import GridItem from '../GridItem/GridItem'; @@ -70,6 +70,39 @@ export default class GridLayout extends React.PureComponent { return getItemsMeta(this.pluginsRefs); }; + getActiveLayout() { + const {layout, temporaryLayout} = this.context; + + return temporaryLayout?.data || layout; + } + + mergeGroupsLayout(group, newLayout, temporaryItem) { + const renderLayout = this.getActiveLayout(); + const itemsByGroup = renderLayout.reduce( + (memo, item) => { + memo[item.i] = item; + return memo; + }, + temporaryItem ? {[temporaryItem.i]: temporaryItem} : {}, + ); + + const newItemsLayoutById = newLayout.reduce((memo, item) => { + const parent = itemsByGroup[item.i].parent; + memo[item.i] = {...item, parent}; + return memo; + }, {}); + + return renderLayout.map((currentItem) => { + const itemParent = itemsByGroup[currentItem.i].parent || DEFAULT_GROUP; + + if (itemParent === group) { + return newItemsLayoutById[currentItem.i]; + } + + return currentItem; + }); + } + reloadItems() { const { editMode, @@ -100,16 +133,17 @@ export default class GridLayout extends React.PureComponent { this.setState({isDragging: true}); }; - _onStop = (newLayout) => { + _onStop = (group, newLayout) => { const {layoutChange, onDrop, temporaryLayout} = this.context; + const groupedLayout = this.mergeGroupsLayout(group, newLayout); if (temporaryLayout) { onDrop?.( - newLayout, - newLayout.find(({i}) => i === TEMPORARY_ITEM_ID), + groupedLayout, + groupedLayout.find(({i}) => i === TEMPORARY_ITEM_ID), ); } else { - layoutChange(newLayout); + layoutChange(groupedLayout); } this.setState({isDragging: false}); }; @@ -124,7 +158,7 @@ export default class GridLayout extends React.PureComponent { return onDropDragOver(e); }; - _onDrop = (layout, item, e) => { + _onDrop = (group, newLayout, item, e) => { if (!item) { return false; } @@ -134,7 +168,13 @@ export default class GridLayout extends React.PureComponent { return false; } - onDrop?.(layout, item, e); + if (group !== DEFAULT_GROUP) { + item.parent = group; + } + + const groupedLayout = this.mergeGroupsLayout(group, newLayout, item); + + onDrop?.(groupedLayout, item, e); }; renderTemporaryPlaceholder() { @@ -163,11 +203,8 @@ export default class GridLayout extends React.PureComponent { ); } - render() { + renderGroup(group, renderLayout, renderItems, offset = 0) { const { - layout, - temporaryLayout, - config, registerManager, editMode, noOverlay, @@ -175,18 +212,18 @@ export default class GridLayout extends React.PureComponent { draggableHandleClassName, outerDnDEnable, } = this.context; - this.pluginsRefs.length = config.items.length; return ( this._onStop(group, ...args)} onResizeStart={this._onStart} - onResizeStop={this._onStop} + onResizeStop={(...args) => this._onStop(group, ...args)} {...(draggableHandleClassName ? {draggableHandle: `.${draggableHandleClassName}`} : null)} @@ -194,21 +231,21 @@ export default class GridLayout extends React.PureComponent { ? { isDroppable: true, onDropDragOver: this._onDropDragOver, - onDrop: this._onDrop, + onDrop: (...args) => this._onDrop(group, ...args), } : null)} draggableCancel={`.${OVERLAY_CONTROLS_CLASS_NAME}`} > - {config.items.map((item, i) => { + {renderItems.map((item, i) => { return ( { - this.pluginsRefs[i] = pluginRef; + this.pluginsRefs[offset + i] = pluginRef; }} // forwarded ref to plugin key={item.id} id={item.id} item={item} - layout={temporaryLayout?.data || layout} + layout={renderLayout} adjustWidgetLayout={this.adjustWidgetLayout} isDragging={this.state.isDragging} noOverlay={noOverlay} @@ -222,4 +259,77 @@ export default class GridLayout extends React.PureComponent { ); } + + render() { + const {config, groups, editMode} = this.context; + + this.pluginsRefs.length = config.items.length; + + const defaultRenderLayout = []; + const defaultRenderItems = []; + const layoutMap = {}; + + const groupedLayout = this.getActiveLayout().reduce((memo, item) => { + if (item.parent) { + if (!memo[item.parent]) { + memo[item.parent] = []; + } + + memo[item.parent].push(item); + layoutMap[item.i] = item.parent; + } else { + defaultRenderLayout.push(item); + } + + return memo; + }, []); + + const itemsByGroup = config.items.reduce((memo, item) => { + const group = layoutMap[item.id]; + if (group) { + if (!memo[group]) { + memo[group] = []; + } + memo[group].push(item); + } else { + defaultRenderItems.push(item); + } + + return memo; + }, {}); + + let offset = 0; + + if (groups) { + return groups.map((group) => { + const id = group.id || DEFAULT_GROUP; + + let layout, items; + + if (id === DEFAULT_GROUP) { + layout = defaultRenderLayout; + items = defaultRenderItems; + } else { + layout = groupedLayout[id] || []; + items = itemsByGroup[id] || []; + } + + const element = this.renderGroup(id, layout, items, offset); + offset += items.length; + + if (group.render) { + return group.render(id, element, { + config, + editMode, + items, + layout, + }); + } + + return element; + }); + } else { + return this.renderGroup(DEFAULT_GROUP, defaultRenderLayout, defaultRenderItems, offset); + } + } } diff --git a/src/components/GridLayout/ReactGridLayout.js b/src/components/GridLayout/ReactGridLayout.js index 77f3358..8b09f8e 100644 --- a/src/components/GridLayout/ReactGridLayout.js +++ b/src/components/GridLayout/ReactGridLayout.js @@ -5,6 +5,17 @@ import ReactGridLayout, {WidthProvider} from 'react-grid-layout'; import {OVERLAY_CLASS_NAME} from '../../constants'; class DragOverLayout extends ReactGridLayout { + constructor(...args) { + super(...args); + + this.parentOnDrag = this.onDrag; + + this.onDrag = (i, x, y, sintE) => { + this.parentOnDrag(i, x, y, sintE); + this.mouseLeaveHandler(sintE.e); + }; + } + componentDidMount() { super.componentDidMount?.(); @@ -22,6 +33,27 @@ class DragOverLayout extends ReactGridLayout { } }; + mouseLeaveHandler = (e) => { + const rect = this.props.innerRef?.current.getBoundingClientRect() || {}; + + if ( + rect.bottom <= e.clientY || + rect.top >= e.clientY || + rect.left >= e.clientX || + rect.right <= e.clientX + ) { + this.dragReset(); + } + }; + + containerHeight() { + if (this.props.autoSize && this.state.layout.length === 0) { + return 'unset'; + } + + return super.containerHeight(); + } + processGridItem(child, isDroppingItem) { const gridItem = super.processGridItem?.(child, isDroppingItem); diff --git a/src/constants/common.ts b/src/constants/common.ts index dc36c1b..f00bd28 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -16,3 +16,5 @@ export const MenuItems = { export const DEFAULT_WIDGET_HEIGHT = 3; export const DEFAULT_WIDGET_WIDTH = 3; + +export const DEFAULT_GROUP = '__default'; diff --git a/src/hocs/withContext.js b/src/hocs/withContext.js index 6981014..12d0e1b 100644 --- a/src/hocs/withContext.js +++ b/src/hocs/withContext.js @@ -1,6 +1,7 @@ import React from 'react'; import isEqual from 'lodash/isEqual'; +import pick from 'lodash/pick'; import {DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, TEMPORARY_ITEM_ID} from '../constants/common'; import {DashKitContext, DashKitDnDContext} from '../context/DashKitContext'; @@ -29,14 +30,19 @@ function useMemoStateContext(props) { const [layoutUpdateCounter, forceUpdateLayout] = React.useState(0); const onChange = React.useCallback( - ({config = props.config, itemsStateAndParams = props.itemsStateAndParams}) => { + ({ + config = props.config, + itemsStateAndParams = props.itemsStateAndParams, + groups = props.groups, + }) => { if ( !( isEqual(config, props.config) && - isEqual(itemsStateAndParams, props.itemsStateAndParams) + isEqual(itemsStateAndParams, props.itemsStateAndParams) && + isEqual(groups, props.groups) ) ) { - props.onChange({config, itemsStateAndParams}); + props.onChange({config, itemsStateAndParams, groups}); } }, [props.config, props.itemsStateAndParams, props.onChange], @@ -242,6 +248,7 @@ function useMemoStateContext(props) { return false; }, [resetTemporaryLayout, temporaryLayout, dragOverPlugin, dragProps]); + const onDropProp = props.onDrop; const onDrop = React.useCallback( (newLayout, item) => { if (!dragProps) { @@ -252,21 +259,21 @@ function useMemoStateContext(props) { data: newLayout, dragProps, }); - const {i, w, h, x, y} = item; - props.onDrop({ + onDropProp({ newLayout: newLayout.reduce((memo, l) => { - if (l.i !== i) { - memo.push({i: l.i, w: l.w, h: l.h, x: l.x, y: l.y}); + if (l.i !== item.i) { + memo.push(pick(l, ['i', 'h', 'w', 'x', 'y', 'parent'])); } + return memo; }, []), - itemLayout: {w, h, x, y}, + itemLayout: pick(item, ['i', 'h', 'w', 'x', 'y', 'parent']), commit: resetTemporaryLayout, dragProps, }); }, - [dragProps, props.onDrop, setTemporaryLayout, resetTemporaryLayout], + [dragProps, onDropProp, setTemporaryLayout, resetTemporaryLayout], ); return React.useMemo( @@ -274,6 +281,7 @@ function useMemoStateContext(props) { layout: resultLayout, temporaryLayout, config: props.config, + groups: props.groups, context: props.context, noOverlay: props.noOverlay, focusable: props.focusable, @@ -303,6 +311,7 @@ function useMemoStateContext(props) { resultLayout, temporaryLayout, props.config, + props.groups, props.context, props.noOverlay, props.focusable, diff --git a/src/shared/types/config.ts b/src/shared/types/config.ts index 6c06985..8e0ab30 100644 --- a/src/shared/types/config.ts +++ b/src/shared/types/config.ts @@ -1,6 +1,10 @@ import {StringParams} from './common'; -export interface ConfigLayout { +export interface AdditionalWidgetLayout { + parent?: string; +} + +export interface ConfigLayout extends AdditionalWidgetLayout { i: string; h: number; w: number; diff --git a/src/typings/common.ts b/src/typings/common.ts index 320c2ae..051758a 100644 --- a/src/typings/common.ts +++ b/src/typings/common.ts @@ -2,6 +2,7 @@ import type ReactGridLayout from 'react-grid-layout'; import type {OverlayCustomControlItem} from '../components/OverlayControls/OverlayControls'; import {MenuItems} from '../constants'; +import {AdditionalWidgetLayout} from '../shared'; export interface Settings { gridLayout?: ReactGridLayout.ReactGridLayoutProps; @@ -21,7 +22,7 @@ export interface ContextProps { [key: string]: any; } -export interface WidgetLayout { +export interface WidgetLayout extends AdditionalWidgetLayout { i: string; w: number; h: number; diff --git a/src/typings/config.ts b/src/typings/config.ts index f177181..8878604 100644 --- a/src/typings/config.ts +++ b/src/typings/config.ts @@ -1,4 +1,4 @@ -import type {ConfigItem, ConfigLayout} from '../shared'; +import type {Config, ConfigItem, ConfigLayout} from '../shared'; export interface AddConfigItem extends Omit { id?: null; @@ -14,3 +14,18 @@ export type SetItemOptions = { export type SetNewItemOptions = SetItemOptions & { updateLayout?: ConfigLayout[]; }; +export interface DashkitGroupRenderProps { + config: Config; + editMode: boolean; + items: ConfigItem[]; + layout: ConfigLayout[]; +} + +export interface DashKitGroup { + id?: string; + render?: ( + id: string, + children: React.ReactNode, + props: DashkitGroupRenderProps, + ) => React.ReactNode; +} diff --git a/src/typings/plugin.ts b/src/typings/plugin.ts index d19019a..d2e2d1b 100644 --- a/src/typings/plugin.ts +++ b/src/typings/plugin.ts @@ -38,16 +38,7 @@ export interface PluginWidgetProps { }) => void; } -export interface PluginDefaultLayout { - w?: number; - h?: number; - x?: number; - y?: number; - minW?: number; - maxW?: number; - minH?: number; - maxH?: number; -} +export type PluginDefaultLayout = Partial>; export interface Plugin

= any, T = StringParams> extends PluginBase { defaultLayout?: PluginDefaultLayout; diff --git a/src/utils/update-manager.ts b/src/utils/update-manager.ts index 726b11c..a03a719 100644 --- a/src/utils/update-manager.ts +++ b/src/utils/update-manager.ts @@ -407,7 +407,7 @@ export class UpdateManager { counter = newIdData.counter; const newItem = {...item, id: newIdData.id, data: newItemData.data, namespace}; - const saveDefaultLayout = pick(layout, ['h', 'w', 'x', 'y']); + const saveDefaultLayout = pick(layout, ['h', 'w', 'x', 'y', 'parent']); if (options.updateLayout) { const byId = options.updateLayout.reduce>((memo, t) => { @@ -510,7 +510,7 @@ export class UpdateManager { static updateLayout({layout, config}: {layout: WidgetLayout[]; config: Config}) { return update(config, { layout: { - $set: layout.map(({x, y, w, h, i}) => ({x, y, w, h, i})), + $set: layout.map((item) => pick(item, ['i', 'h', 'w', 'x', 'y', 'parent'])), }, }); }