@gravity-ui/dashkit 8.0.2-beta.3 → 8.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/build/cjs/components/ActionPanel/ActionPanel.d.ts +13 -1
  2. package/build/cjs/components/ActionPanel/ActionPanel.js +25 -6
  3. package/build/cjs/components/DashKit/DashKit.d.ts +9 -3
  4. package/build/cjs/components/DashKit/DashKit.js +1 -0
  5. package/build/cjs/components/DashKitDnDWrapper/DashKitDnDWrapper.d.ts +6 -0
  6. package/build/cjs/components/DashKitDnDWrapper/DashKitDnDWrapper.js +24 -0
  7. package/build/cjs/components/GridItem/GridItem.d.ts +2 -2
  8. package/build/cjs/components/GridItem/GridItem.js +3 -1
  9. package/build/cjs/components/GridLayout/GridLayout.d.ts +2 -0
  10. package/build/cjs/components/GridLayout/GridLayout.js +75 -6
  11. package/build/cjs/components/Item/Item.css +1 -1
  12. package/build/cjs/components/index.d.ts +1 -0
  13. package/build/cjs/components/index.js +1 -0
  14. package/build/cjs/constants/common.d.ts +2 -0
  15. package/build/cjs/constants/common.js +3 -1
  16. package/build/cjs/context/DashKitContext.d.ts +1 -0
  17. package/build/cjs/context/DashKitContext.js +3 -1
  18. package/build/cjs/hocs/withContext.js +85 -6
  19. package/build/cjs/plugins/Text/Text.js +3 -0
  20. package/build/cjs/shared/constants/common.d.ts +1 -0
  21. package/build/cjs/shared/constants/common.js +2 -1
  22. package/build/cjs/typings/config.d.ts +3 -0
  23. package/build/cjs/typings/plugin.d.ts +1 -0
  24. package/build/cjs/utils/update-manager.d.ts +2 -2
  25. package/build/cjs/utils/update-manager.js +25 -7
  26. package/build/esm/components/ActionPanel/ActionPanel.d.ts +13 -1
  27. package/build/esm/components/ActionPanel/ActionPanel.js +23 -5
  28. package/build/esm/components/DashKit/DashKit.d.ts +9 -3
  29. package/build/esm/components/DashKit/DashKit.js +1 -0
  30. package/build/esm/components/DashKitDnDWrapper/DashKitDnDWrapper.d.ts +6 -0
  31. package/build/esm/components/DashKitDnDWrapper/DashKitDnDWrapper.js +19 -0
  32. package/build/esm/components/GridItem/GridItem.d.ts +2 -2
  33. package/build/esm/components/GridItem/GridItem.js +3 -1
  34. package/build/esm/components/GridLayout/GridLayout.d.ts +2 -0
  35. package/build/esm/components/GridLayout/GridLayout.js +76 -7
  36. package/build/esm/components/Item/Item.css +1 -1
  37. package/build/esm/components/index.d.ts +1 -0
  38. package/build/esm/components/index.js +1 -0
  39. package/build/esm/constants/common.d.ts +2 -0
  40. package/build/esm/constants/common.js +2 -0
  41. package/build/esm/context/DashKitContext.d.ts +1 -0
  42. package/build/esm/context/DashKitContext.js +2 -1
  43. package/build/esm/hocs/withContext.js +86 -7
  44. package/build/esm/plugins/Text/Text.js +3 -0
  45. package/build/esm/shared/constants/common.d.ts +1 -0
  46. package/build/esm/shared/constants/common.js +1 -0
  47. package/build/esm/typings/config.d.ts +3 -0
  48. package/build/esm/typings/plugin.d.ts +1 -0
  49. package/build/esm/utils/update-manager.d.ts +2 -2
  50. package/build/esm/utils/update-manager.js +25 -7
  51. package/package.json +1 -1
@@ -1,3 +1,15 @@
1
- /// <reference types="react" />
1
+ import React from 'react';
2
2
  import { ActionPanelProps } from './types';
3
+ export type ActionPanelItem = {
4
+ id: string;
5
+ icon: React.ReactNode;
6
+ title: string;
7
+ onClick?: () => void;
8
+ className?: string;
9
+ qa?: string;
10
+ pluginType?: string;
11
+ };
12
+ export declare const ActionPanelItemContainer: ({ item }: {
13
+ item: ActionPanelItem;
14
+ }) => JSX.Element;
3
15
  export declare const ActionPanel: (props: ActionPanelProps) => JSX.Element | null;
@@ -1,21 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ActionPanel = void 0;
3
+ exports.ActionPanel = exports.ActionPanelItemContainer = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const react_transition_group_1 = require("react-transition-group");
7
+ const DashKitContext_1 = require("../../context/DashKitContext");
7
8
  const cn_1 = require("../../utils/cn");
8
9
  const b = (0, cn_1.cn)('dashkit-action-panel');
10
+ const ActionPanelItemContainer = ({ item }) => {
11
+ const dragContext = react_1.default.useContext(DashKitContext_1.DashKitDnDContext);
12
+ const onDragStart = react_1.default.useCallback((e) => {
13
+ dragContext.onDragStart(e, item.pluginType);
14
+ }, [dragContext === null || dragContext === void 0 ? void 0 : dragContext.onDragStart, item.pluginType]);
15
+ const onDragEnd = react_1.default.useCallback((e) => {
16
+ dragContext.onDragEnd(e);
17
+ }, [dragContext === null || dragContext === void 0 ? void 0 : dragContext.onDragEnd]);
18
+ let dndProps = {};
19
+ if (item.pluginType) {
20
+ dndProps = {
21
+ draggable: true,
22
+ unselectable: 'on',
23
+ onDragStart,
24
+ onDragEnd,
25
+ };
26
+ }
27
+ return (react_1.default.createElement("div", Object.assign({ role: "button", className: b('item', item.className), key: `dk-action-panel-${item.id}`, onClick: item.onClick, "data-qa": item.qa }, dndProps),
28
+ react_1.default.createElement("div", { className: b('icon') }, item.icon),
29
+ react_1.default.createElement("div", { className: b('title'), title: item.title }, item.title)));
30
+ };
31
+ exports.ActionPanelItemContainer = ActionPanelItemContainer;
9
32
  const ActionPanel = (props) => {
10
33
  var _a, _b;
11
34
  const isDisabled = (_a = props.disable) !== null && _a !== void 0 ? _a : false;
12
35
  const isAnimated = (_b = props.toggleAnimation) !== null && _b !== void 0 ? _b : false;
13
36
  const nodeRef = react_1.default.useRef(null);
14
- const content = (react_1.default.createElement("div", { ref: nodeRef, className: b(null, props.className) }, props.items.map((item) => {
15
- return (react_1.default.createElement("div", { role: "button", className: b('item', item.className), key: `dk-action-panel-${item.id}`, onClick: item.onClick, "data-qa": item.qa },
16
- react_1.default.createElement("div", { className: b('icon') }, item.icon),
17
- react_1.default.createElement("div", { className: b('title'), title: item.title }, item.title)));
18
- })));
37
+ const content = (react_1.default.createElement("div", { ref: nodeRef, className: b(null, props.className) }, props.items.map((item, i) => (react_1.default.createElement(exports.ActionPanelItemContainer, { key: `item_${i}`, item: item })))));
19
38
  if (isAnimated) {
20
39
  return (react_1.default.createElement(react_transition_group_1.CSSTransition, { in: !isDisabled, nodeRef: nodeRef, classNames: b(null), timeout: 300, unmountOnExit: true }, content));
21
40
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import type { Config, ConfigItem, GlobalParams, ItemsStateAndParams } from '../../shared';
3
- import { ContextProps, Plugin, SetConfigItem, SetItemOptions, Settings, SettingsProps } from '../../typings';
2
+ import type { Config, ConfigItem, ConfigLayout, GlobalParams, ItemsStateAndParams } from '../../shared';
3
+ import { ContextProps, Plugin, SetConfigItem, SetNewItemOptions, Settings, SettingsProps } from '../../typings';
4
4
  import GridLayout from '../GridLayout/GridLayout';
5
5
  import { OverlayControlItem } from '../OverlayControls/OverlayControls';
6
6
  interface DashKitGeneralProps {
@@ -15,6 +15,12 @@ interface DashKitDefaultProps {
15
15
  config: Config;
16
16
  itemsStateAndParams: ItemsStateAndParams;
17
17
  }) => void;
18
+ onDrop: (dropProps: {
19
+ commit: () => void;
20
+ pluginType: string;
21
+ itemLayout: ConfigLayout;
22
+ newLayout: ConfigLayout[];
23
+ }) => void;
18
24
  defaultGlobalParams: GlobalParams;
19
25
  globalParams: GlobalParams;
20
26
  itemsStateAndParams: ItemsStateAndParams;
@@ -34,7 +40,7 @@ export declare class DashKit extends React.PureComponent<DashKitInnerProps> {
34
40
  item: SetConfigItem;
35
41
  namespace?: string;
36
42
  config: Config;
37
- options?: SetItemOptions;
43
+ options?: SetNewItemOptions;
38
44
  }): Config;
39
45
  static removeItem({ id, config, itemsStateAndParams, }: {
40
46
  id: string;
@@ -59,6 +59,7 @@ exports.DashKit = DashKit;
59
59
  DashKit.defaultProps = {
60
60
  onItemEdit: noop_1.default,
61
61
  onChange: noop_1.default,
62
+ onDrop: noop_1.default,
62
63
  defaultGlobalParams: {},
63
64
  globalParams: {},
64
65
  itemsStateAndParams: {},
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type DashKitDnDWrapperProps = {
3
+ children: React.ReactElement;
4
+ };
5
+ export declare const DashKitDnDWrapper: React.FC<DashKitDnDWrapperProps>;
6
+ export {};
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DashKitDnDWrapper = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importDefault(require("react"));
6
+ const DashKitContext_1 = require("../../context/DashKitContext");
7
+ const DashKitDnDWrapper = (props) => {
8
+ const [dragPluginType, setDragPluginType] = react_1.default.useState(null);
9
+ const onDragStart = react_1.default.useCallback((_, type) => {
10
+ setDragPluginType(type);
11
+ }, [setDragPluginType]);
12
+ const onDragEnd = react_1.default.useCallback((_) => {
13
+ setDragPluginType(null);
14
+ }, [setDragPluginType]);
15
+ const contextValue = react_1.default.useMemo(() => {
16
+ return {
17
+ dragPluginType,
18
+ onDragStart,
19
+ onDragEnd,
20
+ };
21
+ }, [dragPluginType, onDragStart, onDragEnd]);
22
+ return (react_1.default.createElement(DashKitContext_1.DashKitDnDContext.Provider, { value: contextValue }, props.children));
23
+ };
24
+ exports.DashKitDnDWrapper = DashKitDnDWrapper;
@@ -1,3 +1,3 @@
1
- declare const _default: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
2
- export default _default;
1
+ export default GridItemForwarderRef;
2
+ declare const GridItemForwarderRef: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
3
3
  import React from 'react';
@@ -124,6 +124,8 @@ GridItem.propTypes = {
124
124
  onTouchStart: prop_types_1.default.func,
125
125
  };
126
126
  GridItem.contextType = DashKitContext_1.DashKitContext;
127
- exports.default = react_1.default.forwardRef((props, ref) => {
127
+ const GridItemForwarderRef = react_1.default.forwardRef((props, ref) => {
128
128
  return react_1.default.createElement(GridItem, Object.assign({}, props, { forwardedRef: ref }));
129
129
  });
130
+ GridItemForwarderRef.displayName = 'forwardRef(GridItem)';
131
+ exports.default = GridItemForwarderRef;
@@ -21,6 +21,8 @@ export default class GridLayout extends React.PureComponent<any, any, any> {
21
21
  reloadItems(): void;
22
22
  _onStart: () => void;
23
23
  _onStop: (newLayout: any) => void;
24
+ _onDropDragOver: (e: any) => any;
25
+ _onDrop: (layout: any, item: any, e: any) => false | undefined;
24
26
  render(): JSX.Element;
25
27
  }
26
28
  import React from 'react';
@@ -6,7 +6,45 @@ const react_grid_layout_1 = tslib_1.__importStar(require("react-grid-layout"));
6
6
  const constants_1 = require("../../constants");
7
7
  const DashKitContext_1 = require("../../context/DashKitContext");
8
8
  const GridItem_1 = tslib_1.__importDefault(require("../GridItem/GridItem"));
9
- const Layout = (0, react_grid_layout_1.WidthProvider)(react_grid_layout_1.default); // eslint-disable-line new-cap
9
+ class DragOverLayout extends react_grid_layout_1.default {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.dragReset = () => {
13
+ if (this.dragEnterCounter) {
14
+ this.dragEnterCounter = 0;
15
+ this.removeDroppingPlaceholder();
16
+ }
17
+ };
18
+ }
19
+ componentDidMount() {
20
+ super.componentDidMount();
21
+ window.addEventListener('dragend', this.dragReset);
22
+ }
23
+ componentWillUnmount() {
24
+ window.removeEventListener('dragend', this.dragReset);
25
+ }
26
+ processGridItem(child, isDroppingItem) {
27
+ if (isDroppingItem) {
28
+ // Drop item from outside gets 0,0 droppingPosition
29
+ // centering cursor on newly creted grid item
30
+ // And cause grid-layout using it's own GridItem to make it look
31
+ // like overlay adding className
32
+ const gridItem = super.processGridItem(child, isDroppingItem);
33
+ if (!gridItem)
34
+ return null;
35
+ const { props } = gridItem;
36
+ const { containerWidth, cols, w, h, rowHeight, margin, transformScale } = props;
37
+ const leftOffset = (((containerWidth / cols) * w) / 2 || 0) * transformScale;
38
+ const topOffset = ((h * rowHeight + (h - 1) * margin[1]) / 2 || 0) * transformScale;
39
+ return react_1.default.cloneElement(gridItem, {
40
+ className: constants_1.OVERLAY_CLASS_NAME,
41
+ droppingPosition: Object.assign(Object.assign({}, props.droppingPosition), { left: props.droppingPosition.left - leftOffset, top: props.droppingPosition.top - topOffset }),
42
+ });
43
+ }
44
+ return super.processGridItem(child, isDroppingItem);
45
+ }
46
+ }
47
+ const Layout = (0, react_grid_layout_1.WidthProvider)(DragOverLayout); // eslint-disable-line new-cap
10
48
  class GridLayout extends react_1.default.PureComponent {
11
49
  constructor(props, context) {
12
50
  super(props, context);
@@ -32,13 +70,38 @@ class GridLayout extends react_1.default.PureComponent {
32
70
  return getItemsMeta(this.pluginsRefs);
33
71
  };
34
72
  this._onStart = () => {
73
+ if (this.temporaryLayout)
74
+ return;
35
75
  this.setState({ isDragging: true });
36
76
  };
37
77
  this._onStop = (newLayout) => {
38
- const { layoutChange } = this.context;
39
- layoutChange(newLayout);
78
+ const { layoutChange, onDrop, temporaryLayout } = this.context;
79
+ if (temporaryLayout) {
80
+ onDrop === null || onDrop === void 0 ? void 0 : onDrop(newLayout, newLayout.find(({ i }) => i === constants_1.TEMPORARY_ITEM_ID));
81
+ }
82
+ else {
83
+ layoutChange(newLayout);
84
+ }
40
85
  this.setState({ isDragging: false });
41
86
  };
87
+ this._onDropDragOver = (e) => {
88
+ const { editMode, onDropDragOver, temporaryLayout } = this.context;
89
+ if (!editMode) {
90
+ return false;
91
+ }
92
+ const result = onDropDragOver(e);
93
+ if (temporaryLayout) {
94
+ return false;
95
+ }
96
+ return result;
97
+ };
98
+ this._onDrop = (layout, item, e) => {
99
+ const { editMode, temporaryLayout, onDrop } = this.context;
100
+ if (!editMode && temporaryLayout) {
101
+ return false;
102
+ }
103
+ onDrop === null || onDrop === void 0 ? void 0 : onDrop(layout, item, e);
104
+ };
42
105
  this.pluginsRefs = [];
43
106
  this.state = {
44
107
  isDragging: false,
@@ -72,14 +135,20 @@ class GridLayout extends react_1.default.PureComponent {
72
135
  }
73
136
  }
74
137
  render() {
75
- const { layout, config, registerManager, editMode, noOverlay, focusable, draggableHandleClassName, } = this.context;
138
+ const { layout, temporaryLayout, config, registerManager, editMode, noOverlay, focusable, draggableHandleClassName, outerDnDEnable, } = this.context;
76
139
  this.pluginsRefs.length = config.items.length;
77
- return (react_1.default.createElement(Layout, Object.assign({}, registerManager.gridLayout, { layout: layout, isDraggable: editMode, isResizable: editMode, onDragStart: this._onStart, onDragStop: this._onStop, onResizeStart: this._onStart, onResizeStop: this._onStop }, (draggableHandleClassName
140
+ return (react_1.default.createElement(Layout, Object.assign({}, registerManager.gridLayout, { layout: temporaryLayout || layout, isDraggable: editMode, isResizable: editMode, onDragStart: this._onStart, onDragStop: this._onStop, onResizeStart: this._onStart, onResizeStop: this._onStop }, (draggableHandleClassName
78
141
  ? { draggableHandle: `.${draggableHandleClassName}` }
142
+ : null), (outerDnDEnable
143
+ ? {
144
+ isDroppable: true,
145
+ onDropDragOver: this._onDropDragOver,
146
+ onDrop: this._onDrop,
147
+ }
79
148
  : null), { draggableCancel: `.${constants_1.OVERLAY_CONTROLS_CLASS_NAME}` }), config.items.map((item, i) => {
80
149
  return (react_1.default.createElement(GridItem_1.default, { forwardedPluginRef: (pluginRef) => {
81
150
  this.pluginsRefs[i] = pluginRef;
82
- }, key: item.id, id: item.id, item: item, layout: layout, adjustWidgetLayout: this.adjustWidgetLayout, isDragging: this.state.isDragging, noOverlay: noOverlay, focusable: focusable, withCustomHandle: Boolean(draggableHandleClassName), overlayControls: this.props.overlayControls }));
151
+ }, key: item.id, id: item.id, item: item, layout: temporaryLayout || layout, adjustWidgetLayout: this.adjustWidgetLayout, isDragging: this.state.isDragging, noOverlay: noOverlay, focusable: focusable, withCustomHandle: Boolean(draggableHandleClassName), overlayControls: this.props.overlayControls }));
83
152
  })));
84
153
  }
85
154
  }
@@ -1,4 +1,4 @@
1
- .dashkit-item__renderer {
1
+ .dashkit-item__placeholder, .dashkit-item__renderer {
2
2
  position: absolute;
3
3
  inset: 0;
4
4
  }
@@ -2,3 +2,4 @@ export * from './DashKit';
2
2
  export type { OverlayControlItem, PreparedCopyItemOptions } from './OverlayControls/OverlayControls';
3
3
  export * from './ActionPanel/ActionPanel';
4
4
  export type { ActionPanelItem, ActionPanelProps } from './ActionPanel/types';
5
+ export * from './DashKitDnDWrapper/DashKitDnDWrapper';
@@ -3,3 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./DashKit"), exports);
5
5
  tslib_1.__exportStar(require("./ActionPanel/ActionPanel"), exports);
6
+ tslib_1.__exportStar(require("./DashKitDnDWrapper/DashKitDnDWrapper"), exports);
@@ -1,6 +1,8 @@
1
1
  export declare const COPIED_WIDGET_STORE_KEY = "dashCopiedItem";
2
2
  export declare const DEFAULT_NAMESPACE = "default";
3
3
  export declare const OVERLAY_CONTROLS_CLASS_NAME = "dashkit-overlay-controls";
4
+ export declare const OVERLAY_CLASS_NAME = "dashkit-grid-item__overlay";
5
+ export declare const TEMPORARY_ITEM_ID = "__dropping-elem__";
4
6
  export declare const MenuItems: {
5
7
  readonly Copy: "copy";
6
8
  readonly Delete: "delete";
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MenuItems = exports.OVERLAY_CONTROLS_CLASS_NAME = exports.DEFAULT_NAMESPACE = exports.COPIED_WIDGET_STORE_KEY = void 0;
3
+ exports.MenuItems = exports.TEMPORARY_ITEM_ID = exports.OVERLAY_CLASS_NAME = exports.OVERLAY_CONTROLS_CLASS_NAME = exports.DEFAULT_NAMESPACE = exports.COPIED_WIDGET_STORE_KEY = void 0;
4
4
  exports.COPIED_WIDGET_STORE_KEY = 'dashCopiedItem';
5
5
  exports.DEFAULT_NAMESPACE = 'default';
6
6
  exports.OVERLAY_CONTROLS_CLASS_NAME = 'dashkit-overlay-controls';
7
+ exports.OVERLAY_CLASS_NAME = 'dashkit-grid-item__overlay';
8
+ exports.TEMPORARY_ITEM_ID = '__dropping-elem__';
7
9
  exports.MenuItems = {
8
10
  Copy: 'copy',
9
11
  Delete: 'delete',
@@ -1,2 +1,3 @@
1
1
  export const DashKitContext: React.Context<any>;
2
+ export const DashKitDnDContext: React.Context<any>;
2
3
  import React from 'react';
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DashKitContext = void 0;
3
+ exports.DashKitDnDContext = exports.DashKitContext = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const DashKitContext = react_1.default.createContext();
7
7
  exports.DashKitContext = DashKitContext;
8
+ const DashKitDnDContext = react_1.default.createContext();
9
+ exports.DashKitDnDContext = DashKitDnDContext;
@@ -4,6 +4,7 @@ exports.withContext = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const isEqual_1 = tslib_1.__importDefault(require("lodash/isEqual"));
7
+ const common_1 = require("../constants/common");
7
8
  const DashKitContext_1 = require("../context/DashKitContext");
8
9
  const shared_1 = require("../shared");
9
10
  const utils_1 = require("../utils");
@@ -15,6 +16,10 @@ function useMemoStateContext(props) {
15
16
  // (высота) из конфига будет запоминаться в originalLayouts, новое значение высоты в adjustedLayouts
16
17
  const originalLayouts = react_1.default.useRef({});
17
18
  const adjustedLayouts = react_1.default.useRef({});
19
+ const [temporaryLayout, setTemporaryLayout] = react_1.default.useState(null);
20
+ const resetTemporaryLayout = react_1.default.useCallback(() => setTemporaryLayout(null), [setTemporaryLayout]);
21
+ const dndContext = react_1.default.useContext(DashKitContext_1.DashKitDnDContext);
22
+ const outerDnDEnable = Boolean(dndContext);
18
23
  // TODO: need move originalLayouts, adjustedLayouts to state
19
24
  const [layoutUpdateCounter, forceUpdateLayout] = react_1.default.useState(0);
20
25
  const onChange = react_1.default.useCallback(({ config = props.config, itemsStateAndParams = props.itemsStateAndParams }) => {
@@ -45,12 +50,27 @@ function useMemoStateContext(props) {
45
50
  }
46
51
  }, [props.config, onChange]);
47
52
  const onItemRemove = react_1.default.useCallback((id) => {
48
- onChange(utils_1.UpdateManager.removeItem({
49
- id,
50
- config: props.config,
51
- itemsStateAndParams: props.itemsStateAndParams,
52
- }));
53
- }, [props.config, props.itemsStateAndParams, onChange]);
53
+ if (id === common_1.TEMPORARY_ITEM_ID) {
54
+ resetTemporaryLayout();
55
+ }
56
+ else {
57
+ if (temporaryLayout) {
58
+ setTemporaryLayout(temporaryLayout.filter(({ i }) => i !== id));
59
+ }
60
+ onChange(utils_1.UpdateManager.removeItem({
61
+ id,
62
+ config: props.config,
63
+ itemsStateAndParams: props.itemsStateAndParams,
64
+ }));
65
+ }
66
+ }, [
67
+ props.config,
68
+ props.itemsStateAndParams,
69
+ temporaryLayout,
70
+ onChange,
71
+ setTemporaryLayout,
72
+ resetTemporaryLayout,
73
+ ]);
54
74
  const onItemStateAndParamsChange = react_1.default.useCallback((id, stateAndParams, options) => {
55
75
  onChange({
56
76
  itemsStateAndParams: utils_1.UpdateManager.changeStateAndParams({
@@ -124,8 +144,58 @@ function useMemoStateContext(props) {
124
144
  const reloadItems = react_1.default.useCallback((pluginsRefs, data) => {
125
145
  pluginsRefs.forEach((ref) => ref && ref.reload && ref.reload(data));
126
146
  }, []);
147
+ const draggedPluginRef = react_1.default.useRef(dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType);
148
+ react_1.default.useEffect(() => {
149
+ if (!(dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType) && !temporaryLayout) {
150
+ draggedPluginRef.current = null;
151
+ }
152
+ else if (dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType) {
153
+ draggedPluginRef.current = dndContext.dragPluginType;
154
+ }
155
+ }, [draggedPluginRef, dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType, temporaryLayout]);
156
+ const dndPluginType = draggedPluginRef.current || (dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType);
157
+ const dragOverPlugin = react_1.default.useMemo(() => {
158
+ const pluginType = dndPluginType;
159
+ if (pluginType === null && temporaryLayout === null)
160
+ return null;
161
+ if (props.registerManager.check(pluginType)) {
162
+ return props.registerManager.getItem(pluginType);
163
+ }
164
+ else {
165
+ // eslint-disable-next-line no-console
166
+ console.error(`Uknown pluginType: ${pluginType}`);
167
+ return null;
168
+ }
169
+ }, [dndPluginType, props.registerManager, temporaryLayout]);
170
+ const onDropDragOver = react_1.default.useCallback(() => {
171
+ if (temporaryLayout) {
172
+ resetTemporaryLayout();
173
+ return false;
174
+ }
175
+ if (dragOverPlugin === null || dragOverPlugin === void 0 ? void 0 : dragOverPlugin.defaultLayout) {
176
+ const { h = 3, w = 3 } = dragOverPlugin.defaultLayout;
177
+ return { h, w };
178
+ }
179
+ return false;
180
+ }, [resetTemporaryLayout, temporaryLayout, dragOverPlugin]);
181
+ const onDrop = react_1.default.useCallback((newLayout, item) => {
182
+ setTemporaryLayout(newLayout);
183
+ const { i, w, h, x, y } = item;
184
+ props.onDrop({
185
+ newLayout: newLayout.reduce((memo, l) => {
186
+ if (l.i !== i) {
187
+ memo.push({ i: l.i, w: l.w, h: l.h, x: l.x, y: l.y });
188
+ }
189
+ return memo;
190
+ }, []),
191
+ itemLayout: { w, h, x, y },
192
+ pluginType: dndPluginType,
193
+ commit: resetTemporaryLayout,
194
+ });
195
+ }, [dndPluginType, props.onDrop]);
127
196
  return react_1.default.useMemo(() => ({
128
197
  layout: resultLayout,
198
+ temporaryLayout,
129
199
  config: props.config,
130
200
  context: props.context,
131
201
  noOverlay: props.noOverlay,
@@ -139,6 +209,8 @@ function useMemoStateContext(props) {
139
209
  registerManager: props.registerManager,
140
210
  onItemStateAndParamsChange,
141
211
  removeItem: onItemRemove,
212
+ onDrop,
213
+ onDropDragOver,
142
214
  editItem: props.onItemEdit,
143
215
  layoutChange: onLayoutChange,
144
216
  getItemsMeta,
@@ -147,8 +219,11 @@ function useMemoStateContext(props) {
147
219
  revertToOriginalLayout,
148
220
  forwardedMetaRef: props.forwardedMetaRef,
149
221
  draggableHandleClassName: props.draggableHandleClassName,
222
+ outerDnDEnable,
223
+ dragOverPlugin,
150
224
  }), [
151
225
  resultLayout,
226
+ temporaryLayout,
152
227
  props.config,
153
228
  props.context,
154
229
  props.noOverlay,
@@ -161,6 +236,8 @@ function useMemoStateContext(props) {
161
236
  props.registerManager,
162
237
  onItemStateAndParamsChange,
163
238
  onItemRemove,
239
+ onDrop,
240
+ onDropDragOver,
164
241
  props.onItemEdit,
165
242
  onLayoutChange,
166
243
  getItemsMeta,
@@ -169,6 +246,8 @@ function useMemoStateContext(props) {
169
246
  revertToOriginalLayout,
170
247
  props.forwardedMetaRef,
171
248
  props.draggableHandleClassName,
249
+ outerDnDEnable,
250
+ dragOverPlugin,
172
251
  ]);
173
252
  }
174
253
  function withContext(Component) {
@@ -117,5 +117,8 @@ const plugin = {
117
117
  renderer(props, forwardedRef) {
118
118
  return react_1.default.createElement(PluginText, Object.assign({}, props, { apiHandler: plugin._apiHandler, ref: forwardedRef }));
119
119
  },
120
+ placeholderRenderer(_, forwardedRef) {
121
+ return react_1.default.createElement("div", { ref: forwardedRef }, "Text placeholder");
122
+ },
120
123
  };
121
124
  exports.default = plugin;
@@ -1,3 +1,4 @@
1
1
  export declare const CURRENT_VERSION = 2;
2
2
  export declare const META_KEY = "__meta__";
3
3
  export declare const ACTION_PARAM_PREFIX = "_ap_";
4
+ export declare const TERMORARY_ITEM_ID = "__dropping-elem__";
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ACTION_PARAM_PREFIX = exports.META_KEY = exports.CURRENT_VERSION = void 0;
3
+ exports.TERMORARY_ITEM_ID = exports.ACTION_PARAM_PREFIX = exports.META_KEY = exports.CURRENT_VERSION = void 0;
4
4
  exports.CURRENT_VERSION = 2;
5
5
  exports.META_KEY = '__meta__';
6
6
  exports.ACTION_PARAM_PREFIX = '_ap_';
7
+ exports.TERMORARY_ITEM_ID = '__dropping-elem__';
@@ -8,3 +8,6 @@ export type SetConfigItem = ConfigItem | AddConfigItem;
8
8
  export type SetItemOptions = {
9
9
  excludeIds?: string[];
10
10
  };
11
+ export type SetNewItemOptions = SetItemOptions & {
12
+ updateLayout?: ConfigLayout[];
13
+ };
@@ -36,4 +36,5 @@ export interface PluginDefaultLayout {
36
36
  export interface Plugin<P extends PluginWidgetProps<T> = any, T = StringParams> extends PluginBase {
37
37
  defaultLayout?: PluginDefaultLayout;
38
38
  renderer: (props: P, forwardedRef: React.RefObject<any>) => React.ReactNode;
39
+ placeholderRenderer?: (props: P, forwardedRef: React.RefObject<any>) => React.ReactNode;
39
40
  }
@@ -1,5 +1,5 @@
1
1
  import type { Config, ConfigItem, ItemStateAndParams, ItemStateAndParamsChangeOptions, ItemsStateAndParams } from '../shared';
2
- import type { AddConfigItem, SetItemOptions, WidgetLayout } from '../typings';
2
+ import type { AddConfigItem, SetItemOptions, SetNewItemOptions, WidgetLayout } from '../typings';
3
3
  import { RegisterManagerPluginLayout } from './register-manager';
4
4
  interface RemoveItemArg {
5
5
  id: string;
@@ -19,7 +19,7 @@ export declare class UpdateManager {
19
19
  namespace: string;
20
20
  layout: RegisterManagerPluginLayout;
21
21
  config: Config;
22
- options: SetItemOptions;
22
+ options: SetNewItemOptions;
23
23
  }): Config;
24
24
  static editItem({ item, namespace, config, options, }: {
25
25
  item: ConfigItem;
@@ -216,19 +216,37 @@ function changeGroupParams({ groupItemIds, initiatorId, initiatorItem, itemsStat
216
216
  }
217
217
  class UpdateManager {
218
218
  static addItem({ item, namespace = constants_1.DEFAULT_NAMESPACE, layout, config, options, }) {
219
- const layoutY = Math.max(0, ...config.layout.map(({ h, y }) => h + y));
220
- const saveDefaultLayout = (0, pick_1.default)(layout, ['h', 'w', 'x', 'y']);
221
219
  const salt = config.salt;
222
220
  const newItemData = getNewItemData({ item, config, salt, counter: config.counter, options });
223
221
  let counter = newItemData.counter;
224
222
  const newIdData = (0, get_new_id_1.getNewId)({ config, salt, counter, excludeIds: newItemData.excludeIds });
225
223
  counter = newIdData.counter;
226
224
  const newItem = Object.assign(Object.assign({}, item), { id: newIdData.id, data: newItemData.data, namespace });
227
- return (0, immutability_helper_1.default)(config, {
228
- items: { $push: [newItem] },
229
- layout: { $push: [Object.assign(Object.assign({}, saveDefaultLayout), { y: layoutY, i: newItem.id })] },
230
- counter: { $set: counter },
231
- });
225
+ const saveDefaultLayout = (0, pick_1.default)(layout, ['h', 'w', 'x', 'y']);
226
+ if (options.updateLayout) {
227
+ const byId = options.updateLayout.reduce((memo, t) => {
228
+ memo[t.i] = t;
229
+ return memo;
230
+ }, {});
231
+ const newLayout = [
232
+ ...config.layout.map((t) => (Object.assign(Object.assign({}, t), (byId[t.i] || {})))),
233
+ Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id }),
234
+ ];
235
+ return (0, immutability_helper_1.default)(config, {
236
+ items: { $push: [newItem] },
237
+ layout: { $set: newLayout },
238
+ counter: { $set: counter },
239
+ });
240
+ }
241
+ else {
242
+ const layoutY = Math.max(0, ...config.layout.map(({ h, y }) => h + y));
243
+ const newLayoutItem = Object.assign(Object.assign({}, saveDefaultLayout), { y: layoutY, i: newItem.id });
244
+ return (0, immutability_helper_1.default)(config, {
245
+ items: { $push: [newItem] },
246
+ layout: { $push: [newLayoutItem] },
247
+ counter: { $set: counter },
248
+ });
249
+ }
232
250
  }
233
251
  static editItem({ item, namespace = constants_1.DEFAULT_NAMESPACE, config, options, }) {
234
252
  const itemIndex = config.items.findIndex(({ id }) => item.id === id);
@@ -1,4 +1,16 @@
1
- /// <reference types="react" />
1
+ import React from 'react';
2
2
  import { ActionPanelProps } from './types';
3
3
  import './ActionPanel.css';
4
+ export type ActionPanelItem = {
5
+ id: string;
6
+ icon: React.ReactNode;
7
+ title: string;
8
+ onClick?: () => void;
9
+ className?: string;
10
+ qa?: string;
11
+ pluginType?: string;
12
+ };
13
+ export declare const ActionPanelItemContainer: ({ item }: {
14
+ item: ActionPanelItem;
15
+ }) => JSX.Element;
4
16
  export declare const ActionPanel: (props: ActionPanelProps) => JSX.Element | null;
@@ -1,18 +1,36 @@
1
1
  import React from 'react';
2
2
  import { CSSTransition } from 'react-transition-group';
3
+ import { DashKitDnDContext } from '../../context/DashKitContext';
3
4
  import { cn } from '../../utils/cn';
4
5
  import './ActionPanel.css';
5
6
  const b = cn('dashkit-action-panel');
7
+ export const ActionPanelItemContainer = ({ item }) => {
8
+ const dragContext = React.useContext(DashKitDnDContext);
9
+ const onDragStart = React.useCallback((e) => {
10
+ dragContext.onDragStart(e, item.pluginType);
11
+ }, [dragContext === null || dragContext === void 0 ? void 0 : dragContext.onDragStart, item.pluginType]);
12
+ const onDragEnd = React.useCallback((e) => {
13
+ dragContext.onDragEnd(e);
14
+ }, [dragContext === null || dragContext === void 0 ? void 0 : dragContext.onDragEnd]);
15
+ let dndProps = {};
16
+ if (item.pluginType) {
17
+ dndProps = {
18
+ draggable: true,
19
+ unselectable: 'on',
20
+ onDragStart,
21
+ onDragEnd,
22
+ };
23
+ }
24
+ return (React.createElement("div", Object.assign({ role: "button", className: b('item', item.className), key: `dk-action-panel-${item.id}`, onClick: item.onClick, "data-qa": item.qa }, dndProps),
25
+ React.createElement("div", { className: b('icon') }, item.icon),
26
+ React.createElement("div", { className: b('title'), title: item.title }, item.title)));
27
+ };
6
28
  export const ActionPanel = (props) => {
7
29
  var _a, _b;
8
30
  const isDisabled = (_a = props.disable) !== null && _a !== void 0 ? _a : false;
9
31
  const isAnimated = (_b = props.toggleAnimation) !== null && _b !== void 0 ? _b : false;
10
32
  const nodeRef = React.useRef(null);
11
- const content = (React.createElement("div", { ref: nodeRef, className: b(null, props.className) }, props.items.map((item) => {
12
- return (React.createElement("div", { role: "button", className: b('item', item.className), key: `dk-action-panel-${item.id}`, onClick: item.onClick, "data-qa": item.qa },
13
- React.createElement("div", { className: b('icon') }, item.icon),
14
- React.createElement("div", { className: b('title'), title: item.title }, item.title)));
15
- })));
33
+ const content = (React.createElement("div", { ref: nodeRef, className: b(null, props.className) }, props.items.map((item, i) => (React.createElement(ActionPanelItemContainer, { key: `item_${i}`, item: item })))));
16
34
  if (isAnimated) {
17
35
  return (React.createElement(CSSTransition, { in: !isDisabled, nodeRef: nodeRef, classNames: b(null), timeout: 300, unmountOnExit: true }, content));
18
36
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import type { Config, ConfigItem, GlobalParams, ItemsStateAndParams } from '../../shared';
3
- import { ContextProps, Plugin, SetConfigItem, SetItemOptions, Settings, SettingsProps } from '../../typings';
2
+ import type { Config, ConfigItem, ConfigLayout, GlobalParams, ItemsStateAndParams } from '../../shared';
3
+ import { ContextProps, Plugin, SetConfigItem, SetNewItemOptions, Settings, SettingsProps } from '../../typings';
4
4
  import GridLayout from '../GridLayout/GridLayout';
5
5
  import { OverlayControlItem } from '../OverlayControls/OverlayControls';
6
6
  interface DashKitGeneralProps {
@@ -15,6 +15,12 @@ interface DashKitDefaultProps {
15
15
  config: Config;
16
16
  itemsStateAndParams: ItemsStateAndParams;
17
17
  }) => void;
18
+ onDrop: (dropProps: {
19
+ commit: () => void;
20
+ pluginType: string;
21
+ itemLayout: ConfigLayout;
22
+ newLayout: ConfigLayout[];
23
+ }) => void;
18
24
  defaultGlobalParams: GlobalParams;
19
25
  globalParams: GlobalParams;
20
26
  itemsStateAndParams: ItemsStateAndParams;
@@ -34,7 +40,7 @@ export declare class DashKit extends React.PureComponent<DashKitInnerProps> {
34
40
  item: SetConfigItem;
35
41
  namespace?: string;
36
42
  config: Config;
37
- options?: SetItemOptions;
43
+ options?: SetNewItemOptions;
38
44
  }): Config;
39
45
  static removeItem({ id, config, itemsStateAndParams, }: {
40
46
  id: string;
@@ -54,6 +54,7 @@ export class DashKit extends React.PureComponent {
54
54
  DashKit.defaultProps = {
55
55
  onItemEdit: noop,
56
56
  onChange: noop,
57
+ onDrop: noop,
57
58
  defaultGlobalParams: {},
58
59
  globalParams: {},
59
60
  itemsStateAndParams: {},
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type DashKitDnDWrapperProps = {
3
+ children: React.ReactElement;
4
+ };
5
+ export declare const DashKitDnDWrapper: React.FC<DashKitDnDWrapperProps>;
6
+ export {};
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { DashKitDnDContext } from '../../context/DashKitContext';
3
+ export const DashKitDnDWrapper = (props) => {
4
+ const [dragPluginType, setDragPluginType] = React.useState(null);
5
+ const onDragStart = React.useCallback((_, type) => {
6
+ setDragPluginType(type);
7
+ }, [setDragPluginType]);
8
+ const onDragEnd = React.useCallback((_) => {
9
+ setDragPluginType(null);
10
+ }, [setDragPluginType]);
11
+ const contextValue = React.useMemo(() => {
12
+ return {
13
+ dragPluginType,
14
+ onDragStart,
15
+ onDragEnd,
16
+ };
17
+ }, [dragPluginType, onDragStart, onDragEnd]);
18
+ return (React.createElement(DashKitDnDContext.Provider, { value: contextValue }, props.children));
19
+ };
@@ -1,3 +1,3 @@
1
- declare const _default: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
2
- export default _default;
1
+ export default GridItemForwarderRef;
2
+ declare const GridItemForwarderRef: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
3
3
  import React from 'react';
@@ -122,6 +122,8 @@ GridItem.propTypes = {
122
122
  onTouchStart: PropTypes.func,
123
123
  };
124
124
  GridItem.contextType = DashKitContext;
125
- export default React.forwardRef((props, ref) => {
125
+ const GridItemForwarderRef = React.forwardRef((props, ref) => {
126
126
  return React.createElement(GridItem, Object.assign({}, props, { forwardedRef: ref }));
127
127
  });
128
+ GridItemForwarderRef.displayName = 'forwardRef(GridItem)';
129
+ export default GridItemForwarderRef;
@@ -21,6 +21,8 @@ export default class GridLayout extends React.PureComponent<any, any, any> {
21
21
  reloadItems(): void;
22
22
  _onStart: () => void;
23
23
  _onStop: (newLayout: any) => void;
24
+ _onDropDragOver: (e: any) => any;
25
+ _onDrop: (layout: any, item: any, e: any) => false | undefined;
24
26
  render(): JSX.Element;
25
27
  }
26
28
  import React from 'react';
@@ -1,9 +1,47 @@
1
1
  import React from 'react';
2
2
  import ReactGridLayout, { WidthProvider } from 'react-grid-layout';
3
- import { OVERLAY_CONTROLS_CLASS_NAME } from '../../constants';
3
+ import { OVERLAY_CLASS_NAME, OVERLAY_CONTROLS_CLASS_NAME, TEMPORARY_ITEM_ID } from '../../constants';
4
4
  import { DashKitContext } from '../../context/DashKitContext';
5
5
  import GridItem from '../GridItem/GridItem';
6
- const Layout = WidthProvider(ReactGridLayout); // eslint-disable-line new-cap
6
+ class DragOverLayout extends ReactGridLayout {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.dragReset = () => {
10
+ if (this.dragEnterCounter) {
11
+ this.dragEnterCounter = 0;
12
+ this.removeDroppingPlaceholder();
13
+ }
14
+ };
15
+ }
16
+ componentDidMount() {
17
+ super.componentDidMount();
18
+ window.addEventListener('dragend', this.dragReset);
19
+ }
20
+ componentWillUnmount() {
21
+ window.removeEventListener('dragend', this.dragReset);
22
+ }
23
+ processGridItem(child, isDroppingItem) {
24
+ if (isDroppingItem) {
25
+ // Drop item from outside gets 0,0 droppingPosition
26
+ // centering cursor on newly creted grid item
27
+ // And cause grid-layout using it's own GridItem to make it look
28
+ // like overlay adding className
29
+ const gridItem = super.processGridItem(child, isDroppingItem);
30
+ if (!gridItem)
31
+ return null;
32
+ const { props } = gridItem;
33
+ const { containerWidth, cols, w, h, rowHeight, margin, transformScale } = props;
34
+ const leftOffset = (((containerWidth / cols) * w) / 2 || 0) * transformScale;
35
+ const topOffset = ((h * rowHeight + (h - 1) * margin[1]) / 2 || 0) * transformScale;
36
+ return React.cloneElement(gridItem, {
37
+ className: OVERLAY_CLASS_NAME,
38
+ droppingPosition: Object.assign(Object.assign({}, props.droppingPosition), { left: props.droppingPosition.left - leftOffset, top: props.droppingPosition.top - topOffset }),
39
+ });
40
+ }
41
+ return super.processGridItem(child, isDroppingItem);
42
+ }
43
+ }
44
+ const Layout = WidthProvider(DragOverLayout); // eslint-disable-line new-cap
7
45
  class GridLayout extends React.PureComponent {
8
46
  constructor(props, context) {
9
47
  super(props, context);
@@ -29,13 +67,38 @@ class GridLayout extends React.PureComponent {
29
67
  return getItemsMeta(this.pluginsRefs);
30
68
  };
31
69
  this._onStart = () => {
70
+ if (this.temporaryLayout)
71
+ return;
32
72
  this.setState({ isDragging: true });
33
73
  };
34
74
  this._onStop = (newLayout) => {
35
- const { layoutChange } = this.context;
36
- layoutChange(newLayout);
75
+ const { layoutChange, onDrop, temporaryLayout } = this.context;
76
+ if (temporaryLayout) {
77
+ onDrop === null || onDrop === void 0 ? void 0 : onDrop(newLayout, newLayout.find(({ i }) => i === TEMPORARY_ITEM_ID));
78
+ }
79
+ else {
80
+ layoutChange(newLayout);
81
+ }
37
82
  this.setState({ isDragging: false });
38
83
  };
84
+ this._onDropDragOver = (e) => {
85
+ const { editMode, onDropDragOver, temporaryLayout } = this.context;
86
+ if (!editMode) {
87
+ return false;
88
+ }
89
+ const result = onDropDragOver(e);
90
+ if (temporaryLayout) {
91
+ return false;
92
+ }
93
+ return result;
94
+ };
95
+ this._onDrop = (layout, item, e) => {
96
+ const { editMode, temporaryLayout, onDrop } = this.context;
97
+ if (!editMode && temporaryLayout) {
98
+ return false;
99
+ }
100
+ onDrop === null || onDrop === void 0 ? void 0 : onDrop(layout, item, e);
101
+ };
39
102
  this.pluginsRefs = [];
40
103
  this.state = {
41
104
  isDragging: false,
@@ -69,14 +132,20 @@ class GridLayout extends React.PureComponent {
69
132
  }
70
133
  }
71
134
  render() {
72
- const { layout, config, registerManager, editMode, noOverlay, focusable, draggableHandleClassName, } = this.context;
135
+ const { layout, temporaryLayout, config, registerManager, editMode, noOverlay, focusable, draggableHandleClassName, outerDnDEnable, } = this.context;
73
136
  this.pluginsRefs.length = config.items.length;
74
- return (React.createElement(Layout, Object.assign({}, registerManager.gridLayout, { layout: layout, isDraggable: editMode, isResizable: editMode, onDragStart: this._onStart, onDragStop: this._onStop, onResizeStart: this._onStart, onResizeStop: this._onStop }, (draggableHandleClassName
137
+ return (React.createElement(Layout, Object.assign({}, registerManager.gridLayout, { layout: temporaryLayout || layout, isDraggable: editMode, isResizable: editMode, onDragStart: this._onStart, onDragStop: this._onStop, onResizeStart: this._onStart, onResizeStop: this._onStop }, (draggableHandleClassName
75
138
  ? { draggableHandle: `.${draggableHandleClassName}` }
139
+ : null), (outerDnDEnable
140
+ ? {
141
+ isDroppable: true,
142
+ onDropDragOver: this._onDropDragOver,
143
+ onDrop: this._onDrop,
144
+ }
76
145
  : null), { draggableCancel: `.${OVERLAY_CONTROLS_CLASS_NAME}` }), config.items.map((item, i) => {
77
146
  return (React.createElement(GridItem, { forwardedPluginRef: (pluginRef) => {
78
147
  this.pluginsRefs[i] = pluginRef;
79
- }, key: item.id, id: item.id, item: item, layout: layout, adjustWidgetLayout: this.adjustWidgetLayout, isDragging: this.state.isDragging, noOverlay: noOverlay, focusable: focusable, withCustomHandle: Boolean(draggableHandleClassName), overlayControls: this.props.overlayControls }));
148
+ }, key: item.id, id: item.id, item: item, layout: temporaryLayout || layout, adjustWidgetLayout: this.adjustWidgetLayout, isDragging: this.state.isDragging, noOverlay: noOverlay, focusable: focusable, withCustomHandle: Boolean(draggableHandleClassName), overlayControls: this.props.overlayControls }));
80
149
  })));
81
150
  }
82
151
  }
@@ -1,4 +1,4 @@
1
- .dashkit-item__renderer {
1
+ .dashkit-item__placeholder, .dashkit-item__renderer {
2
2
  position: absolute;
3
3
  inset: 0;
4
4
  }
@@ -2,3 +2,4 @@ export * from './DashKit';
2
2
  export type { OverlayControlItem, PreparedCopyItemOptions } from './OverlayControls/OverlayControls';
3
3
  export * from './ActionPanel/ActionPanel';
4
4
  export type { ActionPanelItem, ActionPanelProps } from './ActionPanel/types';
5
+ export * from './DashKitDnDWrapper/DashKitDnDWrapper';
@@ -1,2 +1,3 @@
1
1
  export * from './DashKit';
2
2
  export * from './ActionPanel/ActionPanel';
3
+ export * from './DashKitDnDWrapper/DashKitDnDWrapper';
@@ -1,6 +1,8 @@
1
1
  export declare const COPIED_WIDGET_STORE_KEY = "dashCopiedItem";
2
2
  export declare const DEFAULT_NAMESPACE = "default";
3
3
  export declare const OVERLAY_CONTROLS_CLASS_NAME = "dashkit-overlay-controls";
4
+ export declare const OVERLAY_CLASS_NAME = "dashkit-grid-item__overlay";
5
+ export declare const TEMPORARY_ITEM_ID = "__dropping-elem__";
4
6
  export declare const MenuItems: {
5
7
  readonly Copy: "copy";
6
8
  readonly Delete: "delete";
@@ -1,6 +1,8 @@
1
1
  export const COPIED_WIDGET_STORE_KEY = 'dashCopiedItem';
2
2
  export const DEFAULT_NAMESPACE = 'default';
3
3
  export const OVERLAY_CONTROLS_CLASS_NAME = 'dashkit-overlay-controls';
4
+ export const OVERLAY_CLASS_NAME = 'dashkit-grid-item__overlay';
5
+ export const TEMPORARY_ITEM_ID = '__dropping-elem__';
4
6
  export const MenuItems = {
5
7
  Copy: 'copy',
6
8
  Delete: 'delete',
@@ -1,2 +1,3 @@
1
1
  export const DashKitContext: React.Context<any>;
2
+ export const DashKitDnDContext: React.Context<any>;
2
3
  import React from 'react';
@@ -1,3 +1,4 @@
1
1
  import React from 'react';
2
2
  const DashKitContext = React.createContext();
3
- export { DashKitContext };
3
+ const DashKitDnDContext = React.createContext();
4
+ export { DashKitContext, DashKitDnDContext };
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import isEqual from 'lodash/isEqual';
3
- import { DashKitContext } from '../context/DashKitContext';
3
+ import { TEMPORARY_ITEM_ID } from '../constants/common';
4
+ import { DashKitContext, DashKitDnDContext } from '../context/DashKitContext';
4
5
  import { getItemsParams, getItemsState } from '../shared';
5
6
  import { UpdateManager } from '../utils';
6
7
  function useMemoStateContext(props) {
@@ -11,6 +12,10 @@ function useMemoStateContext(props) {
11
12
  // (высота) из конфига будет запоминаться в originalLayouts, новое значение высоты в adjustedLayouts
12
13
  const originalLayouts = React.useRef({});
13
14
  const adjustedLayouts = React.useRef({});
15
+ const [temporaryLayout, setTemporaryLayout] = React.useState(null);
16
+ const resetTemporaryLayout = React.useCallback(() => setTemporaryLayout(null), [setTemporaryLayout]);
17
+ const dndContext = React.useContext(DashKitDnDContext);
18
+ const outerDnDEnable = Boolean(dndContext);
14
19
  // TODO: need move originalLayouts, adjustedLayouts to state
15
20
  const [layoutUpdateCounter, forceUpdateLayout] = React.useState(0);
16
21
  const onChange = React.useCallback(({ config = props.config, itemsStateAndParams = props.itemsStateAndParams }) => {
@@ -41,12 +46,27 @@ function useMemoStateContext(props) {
41
46
  }
42
47
  }, [props.config, onChange]);
43
48
  const onItemRemove = React.useCallback((id) => {
44
- onChange(UpdateManager.removeItem({
45
- id,
46
- config: props.config,
47
- itemsStateAndParams: props.itemsStateAndParams,
48
- }));
49
- }, [props.config, props.itemsStateAndParams, onChange]);
49
+ if (id === TEMPORARY_ITEM_ID) {
50
+ resetTemporaryLayout();
51
+ }
52
+ else {
53
+ if (temporaryLayout) {
54
+ setTemporaryLayout(temporaryLayout.filter(({ i }) => i !== id));
55
+ }
56
+ onChange(UpdateManager.removeItem({
57
+ id,
58
+ config: props.config,
59
+ itemsStateAndParams: props.itemsStateAndParams,
60
+ }));
61
+ }
62
+ }, [
63
+ props.config,
64
+ props.itemsStateAndParams,
65
+ temporaryLayout,
66
+ onChange,
67
+ setTemporaryLayout,
68
+ resetTemporaryLayout,
69
+ ]);
50
70
  const onItemStateAndParamsChange = React.useCallback((id, stateAndParams, options) => {
51
71
  onChange({
52
72
  itemsStateAndParams: UpdateManager.changeStateAndParams({
@@ -120,8 +140,58 @@ function useMemoStateContext(props) {
120
140
  const reloadItems = React.useCallback((pluginsRefs, data) => {
121
141
  pluginsRefs.forEach((ref) => ref && ref.reload && ref.reload(data));
122
142
  }, []);
143
+ const draggedPluginRef = React.useRef(dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType);
144
+ React.useEffect(() => {
145
+ if (!(dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType) && !temporaryLayout) {
146
+ draggedPluginRef.current = null;
147
+ }
148
+ else if (dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType) {
149
+ draggedPluginRef.current = dndContext.dragPluginType;
150
+ }
151
+ }, [draggedPluginRef, dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType, temporaryLayout]);
152
+ const dndPluginType = draggedPluginRef.current || (dndContext === null || dndContext === void 0 ? void 0 : dndContext.dragPluginType);
153
+ const dragOverPlugin = React.useMemo(() => {
154
+ const pluginType = dndPluginType;
155
+ if (pluginType === null && temporaryLayout === null)
156
+ return null;
157
+ if (props.registerManager.check(pluginType)) {
158
+ return props.registerManager.getItem(pluginType);
159
+ }
160
+ else {
161
+ // eslint-disable-next-line no-console
162
+ console.error(`Uknown pluginType: ${pluginType}`);
163
+ return null;
164
+ }
165
+ }, [dndPluginType, props.registerManager, temporaryLayout]);
166
+ const onDropDragOver = React.useCallback(() => {
167
+ if (temporaryLayout) {
168
+ resetTemporaryLayout();
169
+ return false;
170
+ }
171
+ if (dragOverPlugin === null || dragOverPlugin === void 0 ? void 0 : dragOverPlugin.defaultLayout) {
172
+ const { h = 3, w = 3 } = dragOverPlugin.defaultLayout;
173
+ return { h, w };
174
+ }
175
+ return false;
176
+ }, [resetTemporaryLayout, temporaryLayout, dragOverPlugin]);
177
+ const onDrop = React.useCallback((newLayout, item) => {
178
+ setTemporaryLayout(newLayout);
179
+ const { i, w, h, x, y } = item;
180
+ props.onDrop({
181
+ newLayout: newLayout.reduce((memo, l) => {
182
+ if (l.i !== i) {
183
+ memo.push({ i: l.i, w: l.w, h: l.h, x: l.x, y: l.y });
184
+ }
185
+ return memo;
186
+ }, []),
187
+ itemLayout: { w, h, x, y },
188
+ pluginType: dndPluginType,
189
+ commit: resetTemporaryLayout,
190
+ });
191
+ }, [dndPluginType, props.onDrop]);
123
192
  return React.useMemo(() => ({
124
193
  layout: resultLayout,
194
+ temporaryLayout,
125
195
  config: props.config,
126
196
  context: props.context,
127
197
  noOverlay: props.noOverlay,
@@ -135,6 +205,8 @@ function useMemoStateContext(props) {
135
205
  registerManager: props.registerManager,
136
206
  onItemStateAndParamsChange,
137
207
  removeItem: onItemRemove,
208
+ onDrop,
209
+ onDropDragOver,
138
210
  editItem: props.onItemEdit,
139
211
  layoutChange: onLayoutChange,
140
212
  getItemsMeta,
@@ -143,8 +215,11 @@ function useMemoStateContext(props) {
143
215
  revertToOriginalLayout,
144
216
  forwardedMetaRef: props.forwardedMetaRef,
145
217
  draggableHandleClassName: props.draggableHandleClassName,
218
+ outerDnDEnable,
219
+ dragOverPlugin,
146
220
  }), [
147
221
  resultLayout,
222
+ temporaryLayout,
148
223
  props.config,
149
224
  props.context,
150
225
  props.noOverlay,
@@ -157,6 +232,8 @@ function useMemoStateContext(props) {
157
232
  props.registerManager,
158
233
  onItemStateAndParamsChange,
159
234
  onItemRemove,
235
+ onDrop,
236
+ onDropDragOver,
160
237
  props.onItemEdit,
161
238
  onLayoutChange,
162
239
  getItemsMeta,
@@ -165,6 +242,8 @@ function useMemoStateContext(props) {
165
242
  revertToOriginalLayout,
166
243
  props.forwardedMetaRef,
167
244
  props.draggableHandleClassName,
245
+ outerDnDEnable,
246
+ dragOverPlugin,
168
247
  ]);
169
248
  }
170
249
  export function withContext(Component) {
@@ -113,5 +113,8 @@ const plugin = {
113
113
  renderer(props, forwardedRef) {
114
114
  return React.createElement(PluginText, Object.assign({}, props, { apiHandler: plugin._apiHandler, ref: forwardedRef }));
115
115
  },
116
+ placeholderRenderer(_, forwardedRef) {
117
+ return React.createElement("div", { ref: forwardedRef }, "Text placeholder");
118
+ },
116
119
  };
117
120
  export default plugin;
@@ -1,3 +1,4 @@
1
1
  export declare const CURRENT_VERSION = 2;
2
2
  export declare const META_KEY = "__meta__";
3
3
  export declare const ACTION_PARAM_PREFIX = "_ap_";
4
+ export declare const TERMORARY_ITEM_ID = "__dropping-elem__";
@@ -1,3 +1,4 @@
1
1
  export const CURRENT_VERSION = 2;
2
2
  export const META_KEY = '__meta__';
3
3
  export const ACTION_PARAM_PREFIX = '_ap_';
4
+ export const TERMORARY_ITEM_ID = '__dropping-elem__';
@@ -8,3 +8,6 @@ export type SetConfigItem = ConfigItem | AddConfigItem;
8
8
  export type SetItemOptions = {
9
9
  excludeIds?: string[];
10
10
  };
11
+ export type SetNewItemOptions = SetItemOptions & {
12
+ updateLayout?: ConfigLayout[];
13
+ };
@@ -36,4 +36,5 @@ export interface PluginDefaultLayout {
36
36
  export interface Plugin<P extends PluginWidgetProps<T> = any, T = StringParams> extends PluginBase {
37
37
  defaultLayout?: PluginDefaultLayout;
38
38
  renderer: (props: P, forwardedRef: React.RefObject<any>) => React.ReactNode;
39
+ placeholderRenderer?: (props: P, forwardedRef: React.RefObject<any>) => React.ReactNode;
39
40
  }
@@ -1,5 +1,5 @@
1
1
  import type { Config, ConfigItem, ItemStateAndParams, ItemStateAndParamsChangeOptions, ItemsStateAndParams } from '../shared';
2
- import type { AddConfigItem, SetItemOptions, WidgetLayout } from '../typings';
2
+ import type { AddConfigItem, SetItemOptions, SetNewItemOptions, WidgetLayout } from '../typings';
3
3
  import { RegisterManagerPluginLayout } from './register-manager';
4
4
  interface RemoveItemArg {
5
5
  id: string;
@@ -19,7 +19,7 @@ export declare class UpdateManager {
19
19
  namespace: string;
20
20
  layout: RegisterManagerPluginLayout;
21
21
  config: Config;
22
- options: SetItemOptions;
22
+ options: SetNewItemOptions;
23
23
  }): Config;
24
24
  static editItem({ item, namespace, config, options, }: {
25
25
  item: ConfigItem;
@@ -212,19 +212,37 @@ function changeGroupParams({ groupItemIds, initiatorId, initiatorItem, itemsStat
212
212
  }
213
213
  export class UpdateManager {
214
214
  static addItem({ item, namespace = DEFAULT_NAMESPACE, layout, config, options, }) {
215
- const layoutY = Math.max(0, ...config.layout.map(({ h, y }) => h + y));
216
- const saveDefaultLayout = pick(layout, ['h', 'w', 'x', 'y']);
217
215
  const salt = config.salt;
218
216
  const newItemData = getNewItemData({ item, config, salt, counter: config.counter, options });
219
217
  let counter = newItemData.counter;
220
218
  const newIdData = getNewId({ config, salt, counter, excludeIds: newItemData.excludeIds });
221
219
  counter = newIdData.counter;
222
220
  const newItem = Object.assign(Object.assign({}, item), { id: newIdData.id, data: newItemData.data, namespace });
223
- return update(config, {
224
- items: { $push: [newItem] },
225
- layout: { $push: [Object.assign(Object.assign({}, saveDefaultLayout), { y: layoutY, i: newItem.id })] },
226
- counter: { $set: counter },
227
- });
221
+ const saveDefaultLayout = pick(layout, ['h', 'w', 'x', 'y']);
222
+ if (options.updateLayout) {
223
+ const byId = options.updateLayout.reduce((memo, t) => {
224
+ memo[t.i] = t;
225
+ return memo;
226
+ }, {});
227
+ const newLayout = [
228
+ ...config.layout.map((t) => (Object.assign(Object.assign({}, t), (byId[t.i] || {})))),
229
+ Object.assign(Object.assign({}, saveDefaultLayout), { i: newItem.id }),
230
+ ];
231
+ return update(config, {
232
+ items: { $push: [newItem] },
233
+ layout: { $set: newLayout },
234
+ counter: { $set: counter },
235
+ });
236
+ }
237
+ else {
238
+ const layoutY = Math.max(0, ...config.layout.map(({ h, y }) => h + y));
239
+ const newLayoutItem = Object.assign(Object.assign({}, saveDefaultLayout), { y: layoutY, i: newItem.id });
240
+ return update(config, {
241
+ items: { $push: [newItem] },
242
+ layout: { $push: [newLayoutItem] },
243
+ counter: { $set: counter },
244
+ });
245
+ }
228
246
  }
229
247
  static editItem({ item, namespace = DEFAULT_NAMESPACE, config, options, }) {
230
248
  const itemIndex = config.items.findIndex(({ id }) => item.id === id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dashkit",
3
- "version": "8.0.2-beta.3",
3
+ "version": "8.1.0-beta.0",
4
4
  "description": "Library for rendering dashboard grid layout",
5
5
  "license": "MIT",
6
6
  "repository": {