@gravity-ui/dashkit 8.5.0 → 8.6.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.
package/README.md CHANGED
@@ -475,6 +475,7 @@ const CustomThemeWrapper = (props: {
475
475
  By default, storybook runs on `http://localhost:7120/`.
476
476
  New changes from a project aren't always picked up when storybook is running, so it's better to rebuild a project manually and restart storybook.
477
477
 
478
+
478
479
  ### Example of an nginx config for development on a dev machine
479
480
 
480
481
  ```bash
@@ -9,6 +9,10 @@ export default class GridLayout extends React.PureComponent<any, any, any> {
9
9
  componentDidMount(): void;
10
10
  componentDidUpdate(): void;
11
11
  componentWillUnmount(): void;
12
+ _memoCallbacksForGroups: {};
13
+ _memoGroupsProps: {};
14
+ _memoGroupsLayouts: {};
15
+ _memoForwardedPluginRef: any[];
12
16
  _timeout: any;
13
17
  _lastReloadAt: any;
14
18
  onVisibilityChange: () => void;
@@ -19,11 +23,22 @@ export default class GridLayout extends React.PureComponent<any, any, any> {
19
23
  }) => void;
20
24
  getItemsMeta: () => any;
21
25
  getActiveLayout(): any;
26
+ getMemoGroupLayout: (group: any, layout: any) => any;
27
+ getMemoGroupCallbacks: (group: any) => any;
28
+ getMemoGroupProps: (group: any, renderLayout: any, properties: any) => {
29
+ layout: any;
30
+ callbacks: any;
31
+ };
32
+ getLayoutAndPropsByGroup: (group: any) => {
33
+ properties: any;
34
+ layout: any;
35
+ };
36
+ getMemoForwardRefCallback: (refIndex: any) => any;
22
37
  mergeGroupsLayout(group: any, newLayout: any, temporaryItem: any): any;
23
38
  reloadItems(): void;
24
39
  _onStart: () => void;
25
40
  _onStop: (group: any, newLayout: any) => void;
26
- _onDropDragOver: (e: any) => any;
41
+ _onDropDragOver: (group: any, e: any) => any;
27
42
  _onDrop: (group: any, newLayout: any, item: any, e: any) => false | undefined;
28
43
  renderTemporaryPlaceholder(): JSX.Element | null;
29
44
  renderGroup(group: any, renderLayout: any, renderItems: any, offset: number | undefined, groupGridProperties: any): JSX.Element;
@@ -9,6 +9,10 @@ const ReactGridLayout_1 = require("./ReactGridLayout");
9
9
  class GridLayout extends react_1.default.PureComponent {
10
10
  constructor(props, context) {
11
11
  super(props, context);
12
+ this._memoForwardedPluginRef = [];
13
+ this._memoGroupsProps = {};
14
+ this._memoGroupsLayouts = {};
15
+ this._memoCallbacksForGroups = {};
12
16
  this.onVisibilityChange = () => {
13
17
  this.setState({
14
18
  isPageHidden: document.hidden,
@@ -30,6 +34,65 @@ class GridLayout extends react_1.default.PureComponent {
30
34
  const { getItemsMeta } = this.context;
31
35
  return getItemsMeta(this.pluginsRefs);
32
36
  };
37
+ this.getMemoGroupLayout = (group, layout) => {
38
+ // fastest possible way to match json
39
+ const key = JSON.stringify(layout);
40
+ if (this._memoGroupsLayouts[group]) {
41
+ if (this._memoGroupsLayouts[group].key === key) {
42
+ return this._memoGroupsLayouts[group];
43
+ }
44
+ else {
45
+ this._memoGroupsLayouts[group].key = key;
46
+ this._memoGroupsLayouts[group].layout = layout;
47
+ }
48
+ }
49
+ else {
50
+ this._memoGroupsLayouts[group] = {
51
+ key,
52
+ layout,
53
+ };
54
+ }
55
+ return this._memoGroupsLayouts[group];
56
+ };
57
+ this.getMemoGroupCallbacks = (group) => {
58
+ if (!this._memoCallbacksForGroups[group]) {
59
+ const onStart = this._onStart;
60
+ const onStop = this._onStop.bind(this, group);
61
+ const onDrop = this._onDrop.bind(this, group);
62
+ const onDropDragOver = this._onDropDragOver.bind(this, group);
63
+ this._memoCallbacksForGroups[group] = {
64
+ onDragStart: onStart,
65
+ onResizeStart: onStart,
66
+ onDragStop: onStop,
67
+ onResizeStop: onStop,
68
+ onDrop: onDrop,
69
+ onDropDragOver,
70
+ };
71
+ }
72
+ return this._memoCallbacksForGroups[group];
73
+ };
74
+ this.getMemoGroupProps = (group, renderLayout, properties) => {
75
+ // Needed for _onDropDragOver
76
+ this._memoGroupsProps[group] = properties;
77
+ return {
78
+ layout: this.getMemoGroupLayout(group, renderLayout).layout,
79
+ callbacks: this.getMemoGroupCallbacks(group),
80
+ };
81
+ };
82
+ this.getLayoutAndPropsByGroup = (group) => {
83
+ return {
84
+ properties: this._memoGroupsProps[group],
85
+ layout: this._memoGroupsLayouts[group].layout,
86
+ };
87
+ };
88
+ this.getMemoForwardRefCallback = (refIndex) => {
89
+ if (!this._memoForwardedPluginRef[refIndex]) {
90
+ this._memoForwardedPluginRef[refIndex] = (pluginRef) => {
91
+ this.pluginsRefs[refIndex] = pluginRef;
92
+ };
93
+ }
94
+ return this._memoForwardedPluginRef[refIndex];
95
+ };
33
96
  this._onStart = () => {
34
97
  if (this.temporaryLayout)
35
98
  return;
@@ -46,12 +109,13 @@ class GridLayout extends react_1.default.PureComponent {
46
109
  }
47
110
  this.setState({ isDragging: false });
48
111
  };
49
- this._onDropDragOver = (e) => {
50
- const { editMode, dragOverPlugin, onDropDragOver } = this.context;
112
+ this._onDropDragOver = (group, e) => {
113
+ const { editMode, dragOverPlugin } = this.context;
51
114
  if (!editMode || !dragOverPlugin) {
52
115
  return false;
53
116
  }
54
- return onDropDragOver(e);
117
+ const { properties, layout } = this.getLayoutAndPropsByGroup(group);
118
+ return this.context.onDropDragOver(e, properties, layout);
55
119
  };
56
120
  this._onDrop = (group, newLayout, item, e) => {
57
121
  if (!item) {
@@ -84,6 +148,10 @@ class GridLayout extends react_1.default.PureComponent {
84
148
  componentWillUnmount() {
85
149
  clearTimeout(this._timeout);
86
150
  document.removeEventListener('visibilitychange', this.onVisibilityChange);
151
+ this._memoCallbacksForGroups = {};
152
+ this._memoGroupsProps = {};
153
+ this._memoGroupsLayouts = {};
154
+ this._memoForwardedPluginRef = [];
87
155
  }
88
156
  getActiveLayout() {
89
157
  const { layout, temporaryLayout } = this.context;
@@ -136,19 +204,22 @@ class GridLayout extends react_1.default.PureComponent {
136
204
  const properties = groupGridProperties
137
205
  ? groupGridProperties(Object.assign({}, registerManager.gridLayout))
138
206
  : registerManager.gridLayout;
139
- return (react_1.default.createElement(ReactGridLayout_1.Layout, Object.assign({}, properties, { layout: renderLayout, key: `group_${group}`, isDraggable: editMode, isResizable: editMode, onDragStart: this._onStart, onDragStop: (...args) => this._onStop(group, ...args), onResizeStart: this._onStart, onResizeStop: (...args) => this._onStop(group, ...args) }, (draggableHandleClassName
207
+ let { compactType } = properties;
208
+ if (compactType === constants_1.COMPACT_TYPE_HORIZONTAL_NOWRAP) {
209
+ compactType = 'horizontal';
210
+ }
211
+ const { callbacks, layout } = this.getMemoGroupProps(group, renderLayout, properties);
212
+ return (react_1.default.createElement(ReactGridLayout_1.Layout, Object.assign({}, properties, { compactType: compactType, layout: layout, key: `group_${group}`, isDraggable: editMode, isResizable: editMode, onDragStart: callbacks.onDragStart, onDragStop: callbacks.onDragStop, onResizeStart: callbacks.onResizeStart, onResizeStop: callbacks.onResizeStop }, (draggableHandleClassName
140
213
  ? { draggableHandle: `.${draggableHandleClassName}` }
141
214
  : null), (outerDnDEnable
142
215
  ? {
143
216
  isDroppable: true,
144
- onDropDragOver: this._onDropDragOver,
145
- onDrop: (...args) => this._onDrop(group, ...args),
217
+ onDropDragOver: callbacks.onDropDragOver,
218
+ onDrop: callbacks.onDrop,
146
219
  }
147
220
  : null), { draggableCancel: `.${constants_1.OVERLAY_CONTROLS_CLASS_NAME}` }),
148
221
  renderItems.map((item, i) => {
149
- return (react_1.default.createElement(GridItem_1.default, { forwardedPluginRef: (pluginRef) => {
150
- this.pluginsRefs[offset + i] = pluginRef;
151
- }, key: item.id, id: item.id, item: item, layout: renderLayout, adjustWidgetLayout: this.adjustWidgetLayout, isDragging: this.state.isDragging, noOverlay: noOverlay, focusable: focusable, withCustomHandle: Boolean(draggableHandleClassName), overlayControls: this.props.overlayControls }));
222
+ return (react_1.default.createElement(GridItem_1.default, { forwardedPluginRef: this.getMemoForwardRefCallback(offset + i), 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 }));
152
223
  }),
153
224
  this.renderTemporaryPlaceholder()));
154
225
  }
@@ -2,6 +2,8 @@ declare const _default: {
2
2
  new (props: any): {
3
3
  shouldComponentUpdate(nextProps: any): boolean;
4
4
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
5
+ _currentRenderProps: {};
6
+ getRenderProps: () => {};
5
7
  render(): JSX.Element;
6
8
  context: unknown;
7
9
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -26,6 +28,8 @@ declare const _default: {
26
28
  new (props: any, context: any): {
27
29
  shouldComponentUpdate(nextProps: any): boolean;
28
30
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
31
+ _currentRenderProps: {};
32
+ getRenderProps: () => {};
29
33
  render(): JSX.Element;
30
34
  context: unknown;
31
35
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -11,3 +11,4 @@ export declare const MenuItems: {
11
11
  export declare const DEFAULT_WIDGET_HEIGHT = 3;
12
12
  export declare const DEFAULT_WIDGET_WIDTH = 3;
13
13
  export declare const DEFAULT_GROUP = "__default";
14
+ export declare const COMPACT_TYPE_HORIZONTAL_NOWRAP = "horizontal-nowrap";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_GROUP = exports.DEFAULT_WIDGET_WIDTH = exports.DEFAULT_WIDGET_HEIGHT = 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;
3
+ exports.COMPACT_TYPE_HORIZONTAL_NOWRAP = exports.DEFAULT_GROUP = exports.DEFAULT_WIDGET_WIDTH = exports.DEFAULT_WIDGET_HEIGHT = 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';
@@ -14,3 +14,4 @@ exports.MenuItems = {
14
14
  exports.DEFAULT_WIDGET_HEIGHT = 3;
15
15
  exports.DEFAULT_WIDGET_WIDTH = 3;
16
16
  exports.DEFAULT_GROUP = '__default';
17
+ exports.COMPACT_TYPE_HORIZONTAL_NOWRAP = 'horizontal-nowrap';
@@ -2,6 +2,8 @@ export function prepareItem(Component: any): {
2
2
  new (props: any): {
3
3
  shouldComponentUpdate(nextProps: any): boolean;
4
4
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
5
+ _currentRenderProps: {};
6
+ getRenderProps: () => {};
5
7
  render(): JSX.Element;
6
8
  context: unknown;
7
9
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -26,6 +28,8 @@ export function prepareItem(Component: any): {
26
28
  new (props: any, context: any): {
27
29
  shouldComponentUpdate(nextProps: any): boolean;
28
30
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
31
+ _currentRenderProps: {};
32
+ getRenderProps: () => {};
29
33
  render(): JSX.Element;
30
34
  context: unknown;
31
35
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -13,6 +13,35 @@ function prepareItem(Component) {
13
13
  this._onStateAndParamsChange = (stateAndParams, options) => {
14
14
  this.context.onItemStateAndParamsChange(this.props.id, stateAndParams, options);
15
15
  };
16
+ this._currentRenderProps = {};
17
+ this.getRenderProps = () => {
18
+ const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder } = this.props;
19
+ const { itemsState, itemsParams, registerManager, settings, context, editMode } = this.context;
20
+ const { data, defaults, namespace } = item;
21
+ const rendererProps = {
22
+ data,
23
+ editMode,
24
+ params: itemsParams[id],
25
+ state: itemsState[id],
26
+ onStateAndParamsChange: this._onStateAndParamsChange,
27
+ width,
28
+ height,
29
+ id,
30
+ defaults,
31
+ namespace,
32
+ settings,
33
+ context,
34
+ layout,
35
+ gridLayout: registerManager.gridLayout,
36
+ adjustWidgetLayout,
37
+ isPlaceholder,
38
+ };
39
+ const changedProp = Object.entries(rendererProps).find(([key, value]) => this._currentRenderProps[key] !== value);
40
+ if (changedProp) {
41
+ this._currentRenderProps = rendererProps;
42
+ }
43
+ return this._currentRenderProps;
44
+ };
16
45
  }
17
46
  shouldComponentUpdate(nextProps) {
18
47
  const { width, height, transform } = this.props;
@@ -26,28 +55,10 @@ function prepareItem(Component) {
26
55
  return true;
27
56
  }
28
57
  render() {
29
- const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder } = this.props;
30
- const { itemsState, itemsParams, registerManager, settings, context, editMode } = this.context;
31
- const { type, data, defaults, namespace } = item;
32
- const rendererProps = {
33
- data,
34
- editMode,
35
- params: itemsParams[id],
36
- state: itemsState[id],
37
- onStateAndParamsChange: this._onStateAndParamsChange,
38
- width,
39
- height,
40
- id,
41
- defaults,
42
- namespace,
43
- settings,
44
- context,
45
- layout,
46
- gridLayout: registerManager.gridLayout,
47
- adjustWidgetLayout,
48
- isPlaceholder,
49
- };
50
- return (react_1.default.createElement(Component, { forwardedPluginRef: this.props.forwardedPluginRef, rendererProps: rendererProps, registerManager: registerManager, type: type, isPlaceholder: isPlaceholder }));
58
+ const { item, isPlaceholder } = this.props;
59
+ const { registerManager } = this.context;
60
+ const { type } = item;
61
+ return (react_1.default.createElement(Component, { forwardedPluginRef: this.props.forwardedPluginRef, rendererProps: this.getRenderProps(), registerManager: registerManager, type: type, isPlaceholder: isPlaceholder }));
51
62
  }
52
63
  },
53
64
  _a.propTypes = {
@@ -7,8 +7,10 @@ const isEqual_1 = tslib_1.__importDefault(require("lodash/isEqual"));
7
7
  const pick_1 = tslib_1.__importDefault(require("lodash/pick"));
8
8
  const common_1 = require("../constants/common");
9
9
  const DashKitContext_1 = require("../context/DashKitContext");
10
+ const useDeepEqualMemo_1 = require("../hooks/useDeepEqualMemo");
10
11
  const shared_1 = require("../shared");
11
12
  const utils_1 = require("../utils");
13
+ const ITEM_PROPS = ['i', 'h', 'w', 'x', 'y', 'parent'];
12
14
  function useMemoStateContext(props) {
13
15
  // так как мы не хотим хранить параметры виджета с активированной автовысотой в сторе и на сервере, актуальный
14
16
  // (видимый юзером в конкретный момент времени) лэйаут (массив объектов с данными о ширине, высоте,
@@ -17,6 +19,7 @@ function useMemoStateContext(props) {
17
19
  // (высота) из конфига будет запоминаться в originalLayouts, новое значение высоты в adjustedLayouts
18
20
  const originalLayouts = react_1.default.useRef({});
19
21
  const adjustedLayouts = react_1.default.useRef({});
22
+ const nowrapAdjustedLayouts = react_1.default.useRef({});
20
23
  const [temporaryLayout, setTemporaryLayout] = react_1.default.useState(null);
21
24
  const resetTemporaryLayout = react_1.default.useCallback(() => setTemporaryLayout(null), [setTemporaryLayout]);
22
25
  const dndContext = react_1.default.useContext(DashKitContext_1.DashKitDnDContext);
@@ -52,6 +55,11 @@ function useMemoStateContext(props) {
52
55
  }
53
56
  }, [props.config, onChange]);
54
57
  const onItemRemove = react_1.default.useCallback((id) => {
58
+ if (nowrapAdjustedLayouts.current[id]) {
59
+ delete nowrapAdjustedLayouts.current[id];
60
+ delete adjustedLayouts.current[id];
61
+ delete originalLayouts.current[id];
62
+ }
55
63
  if (id === common_1.TEMPORARY_ITEM_ID) {
56
64
  resetTemporaryLayout();
57
65
  }
@@ -106,7 +114,55 @@ function useMemoStateContext(props) {
106
114
  forceUpdateLayout((prev) => prev + 1);
107
115
  }
108
116
  }, []);
109
- const itemsParams = react_1.default.useMemo(() => (0, shared_1.getItemsParams)({
117
+ react_1.default.useMemo(() => {
118
+ const groups = props.groups;
119
+ const layout = props.layout;
120
+ const defaultProps = props.registerManager._gridLayout || {};
121
+ const nowrapGroups = {};
122
+ let hasNowrapGroups = false;
123
+ if (defaultProps.compactType === common_1.COMPACT_TYPE_HORIZONTAL_NOWRAP) {
124
+ nowrapGroups[common_1.DEFAULT_GROUP] = {
125
+ items: [],
126
+ leftSpace: defaultProps.cols,
127
+ };
128
+ hasNowrapGroups = true;
129
+ }
130
+ if (groups) {
131
+ groups.forEach((group) => {
132
+ var _a;
133
+ const resultProps = ((_a = group.gridProperties) === null || _a === void 0 ? void 0 : _a.call(group, defaultProps)) || {};
134
+ if (resultProps.compactType === common_1.COMPACT_TYPE_HORIZONTAL_NOWRAP) {
135
+ nowrapGroups[group.id] = {
136
+ items: [],
137
+ leftSpace: resultProps.cols,
138
+ };
139
+ hasNowrapGroups = true;
140
+ }
141
+ });
142
+ }
143
+ if (hasNowrapGroups) {
144
+ layout.forEach((item) => {
145
+ const widgetId = item.i;
146
+ const parentId = item.parent || common_1.DEFAULT_GROUP;
147
+ if (nowrapGroups[parentId]) {
148
+ // Collecting nowrap elements
149
+ nowrapGroups[parentId].items.push(item);
150
+ nowrapGroups[parentId].leftSpace -= item.w;
151
+ }
152
+ else if (nowrapAdjustedLayouts.current[widgetId]) {
153
+ // If element is not in horizontal-nowrap cleaning up and reverting adjustLayout values
154
+ delete nowrapAdjustedLayouts.current[widgetId];
155
+ }
156
+ });
157
+ Object.entries(nowrapGroups).forEach(([, { items, leftSpace }]) => {
158
+ items.forEach((item) => {
159
+ // setting maxW with adjustLayout fields and saving previous
160
+ nowrapAdjustedLayouts.current[item.i] = item.w + leftSpace;
161
+ });
162
+ });
163
+ }
164
+ }, [props.registerManager, props.groups, props.layout]);
165
+ const itemsParams = (0, useDeepEqualMemo_1.useDeepEqualMemo)(() => (0, shared_1.getItemsParams)({
110
166
  defaultGlobalParams: props.defaultGlobalParams,
111
167
  globalParams: props.globalParams,
112
168
  config: props.config,
@@ -119,7 +175,7 @@ function useMemoStateContext(props) {
119
175
  props.itemsStateAndParams,
120
176
  props.registerManager,
121
177
  ]);
122
- const itemsState = react_1.default.useMemo(() => (0, shared_1.getItemsState)({
178
+ const itemsState = (0, useDeepEqualMemo_1.useDeepEqualMemo)(() => (0, shared_1.getItemsState)({
123
179
  config: props.config,
124
180
  itemsStateAndParams: props.itemsStateAndParams,
125
181
  }), [props.config, props.itemsStateAndParams]);
@@ -134,12 +190,31 @@ function useMemoStateContext(props) {
134
190
  .filter(Boolean);
135
191
  }, []);
136
192
  const resultLayout = react_1.default.useMemo(() => {
193
+ const adjusted = adjustedLayouts.current;
194
+ const original = originalLayouts.current;
195
+ const nowrapAdjust = nowrapAdjustedLayouts.current;
137
196
  return props.layout.map((item) => {
138
- if (item.i in adjustedLayouts.current) {
139
- return Object.assign(Object.assign({}, adjustedLayouts.current[item.i]), { w: item.w, x: item.x, y: item.y });
197
+ const widgetId = item.i;
198
+ if (adjusted[widgetId] || nowrapAdjust[widgetId]) {
199
+ original[widgetId] = item;
200
+ // eslint-disable-next-line no-unused-vars
201
+ const _a = adjusted[widgetId] || item, { parent } = _a, adjustedItem = tslib_1.__rest(_a, ["parent"]);
202
+ adjustedItem.w = item.w;
203
+ adjustedItem.x = item.x;
204
+ adjustedItem.y = item.y;
205
+ if (item.parent) {
206
+ adjustedItem.parent = item.parent;
207
+ }
208
+ if (nowrapAdjust[widgetId]) {
209
+ adjustedItem.maxW = nowrapAdjust[widgetId];
210
+ }
211
+ return adjustedItem;
140
212
  }
141
213
  else {
142
- return Object.assign({}, item);
214
+ if (original[widgetId]) {
215
+ delete original[widgetId];
216
+ }
217
+ return item;
143
218
  }
144
219
  });
145
220
  }, [props.layout, layoutUpdateCounter]);
@@ -161,17 +236,31 @@ function useMemoStateContext(props) {
161
236
  return null;
162
237
  }
163
238
  }, [dragProps, props.registerManager]);
164
- const onDropDragOver = react_1.default.useCallback(() => {
239
+ const onDropDragOver = react_1.default.useCallback((_e, gridProps, groupLayout) => {
165
240
  if (temporaryLayout) {
166
241
  resetTemporaryLayout();
167
242
  return false;
168
243
  }
169
- if (dragOverPlugin) {
170
- const { defaultLayout } = dragOverPlugin;
171
- const { h = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.h) || common_1.DEFAULT_WIDGET_HEIGHT, w = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.w) || common_1.DEFAULT_WIDGET_WIDTH, } = dragProps.layout || {};
172
- return { h, w };
244
+ if (!dragOverPlugin) {
245
+ return false;
246
+ }
247
+ let maxW = gridProps.cols;
248
+ const { defaultLayout } = dragOverPlugin;
249
+ const maxH = Math.min(gridProps.maxRows || Infinity, defaultLayout.maxH || Infinity);
250
+ if (gridProps.compactType === common_1.COMPACT_TYPE_HORIZONTAL_NOWRAP) {
251
+ maxW = groupLayout.reduce((memo, item) => memo - item.w, gridProps.cols);
252
+ }
253
+ if (maxW === 0 ||
254
+ maxH === 0 ||
255
+ maxW < defaultLayout.minW ||
256
+ maxH < defaultLayout.minH) {
257
+ return false;
173
258
  }
174
- return false;
259
+ const { h = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.h) || common_1.DEFAULT_WIDGET_HEIGHT, w = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.w) || common_1.DEFAULT_WIDGET_WIDTH, } = dragProps.layout || {};
260
+ return {
261
+ h: maxH ? Math.min(h, maxH) : h,
262
+ w: maxW ? Math.min(w, maxW) : w,
263
+ };
175
264
  }, [resetTemporaryLayout, temporaryLayout, dragOverPlugin, dragProps]);
176
265
  const onDropProp = props.onDrop;
177
266
  const onDrop = react_1.default.useCallback((newLayout, item) => {
@@ -185,11 +274,11 @@ function useMemoStateContext(props) {
185
274
  onDropProp({
186
275
  newLayout: newLayout.reduce((memo, l) => {
187
276
  if (l.i !== item.i) {
188
- memo.push((0, pick_1.default)(l, ['i', 'h', 'w', 'x', 'y', 'parent']));
277
+ memo.push((0, pick_1.default)(l, ITEM_PROPS));
189
278
  }
190
279
  return memo;
191
280
  }, []),
192
- itemLayout: (0, pick_1.default)(item, ['i', 'h', 'w', 'x', 'y', 'parent']),
281
+ itemLayout: (0, pick_1.default)(item, ITEM_PROPS),
193
282
  commit: resetTemporaryLayout,
194
283
  dragProps,
195
284
  });
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const useDeepEqualMemo: <T extends unknown>(predicate: () => T, deps: React.DependencyList) => T;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useDeepEqualMemo = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importDefault(require("react"));
6
+ const isEqual_1 = tslib_1.__importDefault(require("lodash/isEqual"));
7
+ const useDeepEqualMemo = (predicate, deps) => {
8
+ const previousValueRef = react_1.default.useRef({});
9
+ return react_1.default.useMemo(() => {
10
+ const value = predicate();
11
+ if (!(0, isEqual_1.default)(previousValueRef.current, value)) {
12
+ previousValueRef.current = value;
13
+ }
14
+ return previousValueRef.current;
15
+ }, [previousValueRef, ...deps]);
16
+ };
17
+ exports.useDeepEqualMemo = useDeepEqualMemo;
@@ -19,7 +19,9 @@ export interface DashkitGroupRenderProps {
19
19
  items: ConfigItem[];
20
20
  layout: ConfigLayout[];
21
21
  }
22
- export type ReactGridLayoutProps = Omit<ReactGridLayout.ReactGridLayoutProps, 'children' | 'innerRef' | 'key' | 'layout' | 'isDraggable' | 'isResizable' | 'onDragStart' | 'onDragStop' | 'onResizeStart' | 'onResizeStop' | 'draggableHandle' | 'isDroppable' | 'onDropDragOver' | 'onDrop' | 'draggableCancel'>;
22
+ export type ReactGridLayoutProps = Omit<ReactGridLayout.ReactGridLayoutProps, 'children' | 'compactType' | 'innerRef' | 'key' | 'layout' | 'isDraggable' | 'isResizable' | 'onDragStart' | 'onDragStop' | 'onResizeStart' | 'onResizeStop' | 'draggableHandle' | 'isDroppable' | 'onDropDragOver' | 'onDrop' | 'draggableCancel'> & {
23
+ compactType?: ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap';
24
+ };
23
25
  export interface DashKitGroup {
24
26
  id?: string;
25
27
  render?: (id: string, children: React.ReactNode, props: DashkitGroupRenderProps) => React.ReactNode;
@@ -9,6 +9,10 @@ export default class GridLayout extends React.PureComponent<any, any, any> {
9
9
  componentDidMount(): void;
10
10
  componentDidUpdate(): void;
11
11
  componentWillUnmount(): void;
12
+ _memoCallbacksForGroups: {};
13
+ _memoGroupsProps: {};
14
+ _memoGroupsLayouts: {};
15
+ _memoForwardedPluginRef: any[];
12
16
  _timeout: any;
13
17
  _lastReloadAt: any;
14
18
  onVisibilityChange: () => void;
@@ -19,11 +23,22 @@ export default class GridLayout extends React.PureComponent<any, any, any> {
19
23
  }) => void;
20
24
  getItemsMeta: () => any;
21
25
  getActiveLayout(): any;
26
+ getMemoGroupLayout: (group: any, layout: any) => any;
27
+ getMemoGroupCallbacks: (group: any) => any;
28
+ getMemoGroupProps: (group: any, renderLayout: any, properties: any) => {
29
+ layout: any;
30
+ callbacks: any;
31
+ };
32
+ getLayoutAndPropsByGroup: (group: any) => {
33
+ properties: any;
34
+ layout: any;
35
+ };
36
+ getMemoForwardRefCallback: (refIndex: any) => any;
22
37
  mergeGroupsLayout(group: any, newLayout: any, temporaryItem: any): any;
23
38
  reloadItems(): void;
24
39
  _onStart: () => void;
25
40
  _onStop: (group: any, newLayout: any) => void;
26
- _onDropDragOver: (e: any) => any;
41
+ _onDropDragOver: (group: any, e: any) => any;
27
42
  _onDrop: (group: any, newLayout: any, item: any, e: any) => false | undefined;
28
43
  renderTemporaryPlaceholder(): JSX.Element | null;
29
44
  renderGroup(group: any, renderLayout: any, renderItems: any, offset: number | undefined, groupGridProperties: any): JSX.Element;
@@ -1,11 +1,15 @@
1
1
  import React from 'react';
2
- import { DEFAULT_GROUP, OVERLAY_CONTROLS_CLASS_NAME, TEMPORARY_ITEM_ID } from '../../constants';
2
+ import { COMPACT_TYPE_HORIZONTAL_NOWRAP, DEFAULT_GROUP, OVERLAY_CONTROLS_CLASS_NAME, TEMPORARY_ITEM_ID, } from '../../constants';
3
3
  import { DashKitContext } from '../../context/DashKitContext';
4
4
  import GridItem from '../GridItem/GridItem';
5
5
  import { Layout } from './ReactGridLayout';
6
6
  class GridLayout extends React.PureComponent {
7
7
  constructor(props, context) {
8
8
  super(props, context);
9
+ this._memoForwardedPluginRef = [];
10
+ this._memoGroupsProps = {};
11
+ this._memoGroupsLayouts = {};
12
+ this._memoCallbacksForGroups = {};
9
13
  this.onVisibilityChange = () => {
10
14
  this.setState({
11
15
  isPageHidden: document.hidden,
@@ -27,6 +31,65 @@ class GridLayout extends React.PureComponent {
27
31
  const { getItemsMeta } = this.context;
28
32
  return getItemsMeta(this.pluginsRefs);
29
33
  };
34
+ this.getMemoGroupLayout = (group, layout) => {
35
+ // fastest possible way to match json
36
+ const key = JSON.stringify(layout);
37
+ if (this._memoGroupsLayouts[group]) {
38
+ if (this._memoGroupsLayouts[group].key === key) {
39
+ return this._memoGroupsLayouts[group];
40
+ }
41
+ else {
42
+ this._memoGroupsLayouts[group].key = key;
43
+ this._memoGroupsLayouts[group].layout = layout;
44
+ }
45
+ }
46
+ else {
47
+ this._memoGroupsLayouts[group] = {
48
+ key,
49
+ layout,
50
+ };
51
+ }
52
+ return this._memoGroupsLayouts[group];
53
+ };
54
+ this.getMemoGroupCallbacks = (group) => {
55
+ if (!this._memoCallbacksForGroups[group]) {
56
+ const onStart = this._onStart;
57
+ const onStop = this._onStop.bind(this, group);
58
+ const onDrop = this._onDrop.bind(this, group);
59
+ const onDropDragOver = this._onDropDragOver.bind(this, group);
60
+ this._memoCallbacksForGroups[group] = {
61
+ onDragStart: onStart,
62
+ onResizeStart: onStart,
63
+ onDragStop: onStop,
64
+ onResizeStop: onStop,
65
+ onDrop: onDrop,
66
+ onDropDragOver,
67
+ };
68
+ }
69
+ return this._memoCallbacksForGroups[group];
70
+ };
71
+ this.getMemoGroupProps = (group, renderLayout, properties) => {
72
+ // Needed for _onDropDragOver
73
+ this._memoGroupsProps[group] = properties;
74
+ return {
75
+ layout: this.getMemoGroupLayout(group, renderLayout).layout,
76
+ callbacks: this.getMemoGroupCallbacks(group),
77
+ };
78
+ };
79
+ this.getLayoutAndPropsByGroup = (group) => {
80
+ return {
81
+ properties: this._memoGroupsProps[group],
82
+ layout: this._memoGroupsLayouts[group].layout,
83
+ };
84
+ };
85
+ this.getMemoForwardRefCallback = (refIndex) => {
86
+ if (!this._memoForwardedPluginRef[refIndex]) {
87
+ this._memoForwardedPluginRef[refIndex] = (pluginRef) => {
88
+ this.pluginsRefs[refIndex] = pluginRef;
89
+ };
90
+ }
91
+ return this._memoForwardedPluginRef[refIndex];
92
+ };
30
93
  this._onStart = () => {
31
94
  if (this.temporaryLayout)
32
95
  return;
@@ -43,12 +106,13 @@ class GridLayout extends React.PureComponent {
43
106
  }
44
107
  this.setState({ isDragging: false });
45
108
  };
46
- this._onDropDragOver = (e) => {
47
- const { editMode, dragOverPlugin, onDropDragOver } = this.context;
109
+ this._onDropDragOver = (group, e) => {
110
+ const { editMode, dragOverPlugin } = this.context;
48
111
  if (!editMode || !dragOverPlugin) {
49
112
  return false;
50
113
  }
51
- return onDropDragOver(e);
114
+ const { properties, layout } = this.getLayoutAndPropsByGroup(group);
115
+ return this.context.onDropDragOver(e, properties, layout);
52
116
  };
53
117
  this._onDrop = (group, newLayout, item, e) => {
54
118
  if (!item) {
@@ -81,6 +145,10 @@ class GridLayout extends React.PureComponent {
81
145
  componentWillUnmount() {
82
146
  clearTimeout(this._timeout);
83
147
  document.removeEventListener('visibilitychange', this.onVisibilityChange);
148
+ this._memoCallbacksForGroups = {};
149
+ this._memoGroupsProps = {};
150
+ this._memoGroupsLayouts = {};
151
+ this._memoForwardedPluginRef = [];
84
152
  }
85
153
  getActiveLayout() {
86
154
  const { layout, temporaryLayout } = this.context;
@@ -133,19 +201,22 @@ class GridLayout extends React.PureComponent {
133
201
  const properties = groupGridProperties
134
202
  ? groupGridProperties(Object.assign({}, registerManager.gridLayout))
135
203
  : registerManager.gridLayout;
136
- return (React.createElement(Layout, Object.assign({}, properties, { layout: renderLayout, key: `group_${group}`, isDraggable: editMode, isResizable: editMode, onDragStart: this._onStart, onDragStop: (...args) => this._onStop(group, ...args), onResizeStart: this._onStart, onResizeStop: (...args) => this._onStop(group, ...args) }, (draggableHandleClassName
204
+ let { compactType } = properties;
205
+ if (compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
206
+ compactType = 'horizontal';
207
+ }
208
+ const { callbacks, layout } = this.getMemoGroupProps(group, renderLayout, properties);
209
+ return (React.createElement(Layout, Object.assign({}, properties, { compactType: compactType, layout: layout, key: `group_${group}`, isDraggable: editMode, isResizable: editMode, onDragStart: callbacks.onDragStart, onDragStop: callbacks.onDragStop, onResizeStart: callbacks.onResizeStart, onResizeStop: callbacks.onResizeStop }, (draggableHandleClassName
137
210
  ? { draggableHandle: `.${draggableHandleClassName}` }
138
211
  : null), (outerDnDEnable
139
212
  ? {
140
213
  isDroppable: true,
141
- onDropDragOver: this._onDropDragOver,
142
- onDrop: (...args) => this._onDrop(group, ...args),
214
+ onDropDragOver: callbacks.onDropDragOver,
215
+ onDrop: callbacks.onDrop,
143
216
  }
144
217
  : null), { draggableCancel: `.${OVERLAY_CONTROLS_CLASS_NAME}` }),
145
218
  renderItems.map((item, i) => {
146
- return (React.createElement(GridItem, { forwardedPluginRef: (pluginRef) => {
147
- this.pluginsRefs[offset + i] = pluginRef;
148
- }, key: item.id, id: item.id, item: item, layout: renderLayout, adjustWidgetLayout: this.adjustWidgetLayout, isDragging: this.state.isDragging, noOverlay: noOverlay, focusable: focusable, withCustomHandle: Boolean(draggableHandleClassName), overlayControls: this.props.overlayControls }));
219
+ return (React.createElement(GridItem, { forwardedPluginRef: this.getMemoForwardRefCallback(offset + i), 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 }));
149
220
  }),
150
221
  this.renderTemporaryPlaceholder()));
151
222
  }
@@ -2,6 +2,8 @@ declare const _default: {
2
2
  new (props: any): {
3
3
  shouldComponentUpdate(nextProps: any): boolean;
4
4
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
5
+ _currentRenderProps: {};
6
+ getRenderProps: () => {};
5
7
  render(): JSX.Element;
6
8
  context: unknown;
7
9
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -26,6 +28,8 @@ declare const _default: {
26
28
  new (props: any, context: any): {
27
29
  shouldComponentUpdate(nextProps: any): boolean;
28
30
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
31
+ _currentRenderProps: {};
32
+ getRenderProps: () => {};
29
33
  render(): JSX.Element;
30
34
  context: unknown;
31
35
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -11,3 +11,4 @@ export declare const MenuItems: {
11
11
  export declare const DEFAULT_WIDGET_HEIGHT = 3;
12
12
  export declare const DEFAULT_WIDGET_WIDTH = 3;
13
13
  export declare const DEFAULT_GROUP = "__default";
14
+ export declare const COMPACT_TYPE_HORIZONTAL_NOWRAP = "horizontal-nowrap";
@@ -11,3 +11,4 @@ export const MenuItems = {
11
11
  export const DEFAULT_WIDGET_HEIGHT = 3;
12
12
  export const DEFAULT_WIDGET_WIDTH = 3;
13
13
  export const DEFAULT_GROUP = '__default';
14
+ export const COMPACT_TYPE_HORIZONTAL_NOWRAP = 'horizontal-nowrap';
@@ -2,6 +2,8 @@ export function prepareItem(Component: any): {
2
2
  new (props: any): {
3
3
  shouldComponentUpdate(nextProps: any): boolean;
4
4
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
5
+ _currentRenderProps: {};
6
+ getRenderProps: () => {};
5
7
  render(): JSX.Element;
6
8
  context: unknown;
7
9
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -26,6 +28,8 @@ export function prepareItem(Component: any): {
26
28
  new (props: any, context: any): {
27
29
  shouldComponentUpdate(nextProps: any): boolean;
28
30
  _onStateAndParamsChange: (stateAndParams: any, options: any) => void;
31
+ _currentRenderProps: {};
32
+ getRenderProps: () => {};
29
33
  render(): JSX.Element;
30
34
  context: unknown;
31
35
  setState<K extends string | number | symbol>(state: any, callback?: (() => void) | undefined): void;
@@ -9,6 +9,35 @@ export function prepareItem(Component) {
9
9
  this._onStateAndParamsChange = (stateAndParams, options) => {
10
10
  this.context.onItemStateAndParamsChange(this.props.id, stateAndParams, options);
11
11
  };
12
+ this._currentRenderProps = {};
13
+ this.getRenderProps = () => {
14
+ const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder } = this.props;
15
+ const { itemsState, itemsParams, registerManager, settings, context, editMode } = this.context;
16
+ const { data, defaults, namespace } = item;
17
+ const rendererProps = {
18
+ data,
19
+ editMode,
20
+ params: itemsParams[id],
21
+ state: itemsState[id],
22
+ onStateAndParamsChange: this._onStateAndParamsChange,
23
+ width,
24
+ height,
25
+ id,
26
+ defaults,
27
+ namespace,
28
+ settings,
29
+ context,
30
+ layout,
31
+ gridLayout: registerManager.gridLayout,
32
+ adjustWidgetLayout,
33
+ isPlaceholder,
34
+ };
35
+ const changedProp = Object.entries(rendererProps).find(([key, value]) => this._currentRenderProps[key] !== value);
36
+ if (changedProp) {
37
+ this._currentRenderProps = rendererProps;
38
+ }
39
+ return this._currentRenderProps;
40
+ };
12
41
  }
13
42
  shouldComponentUpdate(nextProps) {
14
43
  const { width, height, transform } = this.props;
@@ -22,28 +51,10 @@ export function prepareItem(Component) {
22
51
  return true;
23
52
  }
24
53
  render() {
25
- const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder } = this.props;
26
- const { itemsState, itemsParams, registerManager, settings, context, editMode } = this.context;
27
- const { type, data, defaults, namespace } = item;
28
- const rendererProps = {
29
- data,
30
- editMode,
31
- params: itemsParams[id],
32
- state: itemsState[id],
33
- onStateAndParamsChange: this._onStateAndParamsChange,
34
- width,
35
- height,
36
- id,
37
- defaults,
38
- namespace,
39
- settings,
40
- context,
41
- layout,
42
- gridLayout: registerManager.gridLayout,
43
- adjustWidgetLayout,
44
- isPlaceholder,
45
- };
46
- return (React.createElement(Component, { forwardedPluginRef: this.props.forwardedPluginRef, rendererProps: rendererProps, registerManager: registerManager, type: type, isPlaceholder: isPlaceholder }));
54
+ const { item, isPlaceholder } = this.props;
55
+ const { registerManager } = this.context;
56
+ const { type } = item;
57
+ return (React.createElement(Component, { forwardedPluginRef: this.props.forwardedPluginRef, rendererProps: this.getRenderProps(), registerManager: registerManager, type: type, isPlaceholder: isPlaceholder }));
47
58
  }
48
59
  },
49
60
  _a.propTypes = {
@@ -1,10 +1,13 @@
1
+ import { __rest } from "tslib";
1
2
  import React from 'react';
2
3
  import isEqual from 'lodash/isEqual';
3
4
  import pick from 'lodash/pick';
4
- import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, TEMPORARY_ITEM_ID } from '../constants/common';
5
+ import { COMPACT_TYPE_HORIZONTAL_NOWRAP, DEFAULT_GROUP, DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, TEMPORARY_ITEM_ID, } from '../constants/common';
5
6
  import { DashKitContext, DashKitDnDContext } from '../context/DashKitContext';
7
+ import { useDeepEqualMemo } from '../hooks/useDeepEqualMemo';
6
8
  import { getItemsParams, getItemsState } from '../shared';
7
9
  import { UpdateManager } from '../utils';
10
+ const ITEM_PROPS = ['i', 'h', 'w', 'x', 'y', 'parent'];
8
11
  function useMemoStateContext(props) {
9
12
  // так как мы не хотим хранить параметры виджета с активированной автовысотой в сторе и на сервере, актуальный
10
13
  // (видимый юзером в конкретный момент времени) лэйаут (массив объектов с данными о ширине, высоте,
@@ -13,6 +16,7 @@ function useMemoStateContext(props) {
13
16
  // (высота) из конфига будет запоминаться в originalLayouts, новое значение высоты в adjustedLayouts
14
17
  const originalLayouts = React.useRef({});
15
18
  const adjustedLayouts = React.useRef({});
19
+ const nowrapAdjustedLayouts = React.useRef({});
16
20
  const [temporaryLayout, setTemporaryLayout] = React.useState(null);
17
21
  const resetTemporaryLayout = React.useCallback(() => setTemporaryLayout(null), [setTemporaryLayout]);
18
22
  const dndContext = React.useContext(DashKitDnDContext);
@@ -48,6 +52,11 @@ function useMemoStateContext(props) {
48
52
  }
49
53
  }, [props.config, onChange]);
50
54
  const onItemRemove = React.useCallback((id) => {
55
+ if (nowrapAdjustedLayouts.current[id]) {
56
+ delete nowrapAdjustedLayouts.current[id];
57
+ delete adjustedLayouts.current[id];
58
+ delete originalLayouts.current[id];
59
+ }
51
60
  if (id === TEMPORARY_ITEM_ID) {
52
61
  resetTemporaryLayout();
53
62
  }
@@ -102,7 +111,55 @@ function useMemoStateContext(props) {
102
111
  forceUpdateLayout((prev) => prev + 1);
103
112
  }
104
113
  }, []);
105
- const itemsParams = React.useMemo(() => getItemsParams({
114
+ React.useMemo(() => {
115
+ const groups = props.groups;
116
+ const layout = props.layout;
117
+ const defaultProps = props.registerManager._gridLayout || {};
118
+ const nowrapGroups = {};
119
+ let hasNowrapGroups = false;
120
+ if (defaultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
121
+ nowrapGroups[DEFAULT_GROUP] = {
122
+ items: [],
123
+ leftSpace: defaultProps.cols,
124
+ };
125
+ hasNowrapGroups = true;
126
+ }
127
+ if (groups) {
128
+ groups.forEach((group) => {
129
+ var _a;
130
+ const resultProps = ((_a = group.gridProperties) === null || _a === void 0 ? void 0 : _a.call(group, defaultProps)) || {};
131
+ if (resultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
132
+ nowrapGroups[group.id] = {
133
+ items: [],
134
+ leftSpace: resultProps.cols,
135
+ };
136
+ hasNowrapGroups = true;
137
+ }
138
+ });
139
+ }
140
+ if (hasNowrapGroups) {
141
+ layout.forEach((item) => {
142
+ const widgetId = item.i;
143
+ const parentId = item.parent || DEFAULT_GROUP;
144
+ if (nowrapGroups[parentId]) {
145
+ // Collecting nowrap elements
146
+ nowrapGroups[parentId].items.push(item);
147
+ nowrapGroups[parentId].leftSpace -= item.w;
148
+ }
149
+ else if (nowrapAdjustedLayouts.current[widgetId]) {
150
+ // If element is not in horizontal-nowrap cleaning up and reverting adjustLayout values
151
+ delete nowrapAdjustedLayouts.current[widgetId];
152
+ }
153
+ });
154
+ Object.entries(nowrapGroups).forEach(([, { items, leftSpace }]) => {
155
+ items.forEach((item) => {
156
+ // setting maxW with adjustLayout fields and saving previous
157
+ nowrapAdjustedLayouts.current[item.i] = item.w + leftSpace;
158
+ });
159
+ });
160
+ }
161
+ }, [props.registerManager, props.groups, props.layout]);
162
+ const itemsParams = useDeepEqualMemo(() => getItemsParams({
106
163
  defaultGlobalParams: props.defaultGlobalParams,
107
164
  globalParams: props.globalParams,
108
165
  config: props.config,
@@ -115,7 +172,7 @@ function useMemoStateContext(props) {
115
172
  props.itemsStateAndParams,
116
173
  props.registerManager,
117
174
  ]);
118
- const itemsState = React.useMemo(() => getItemsState({
175
+ const itemsState = useDeepEqualMemo(() => getItemsState({
119
176
  config: props.config,
120
177
  itemsStateAndParams: props.itemsStateAndParams,
121
178
  }), [props.config, props.itemsStateAndParams]);
@@ -130,12 +187,31 @@ function useMemoStateContext(props) {
130
187
  .filter(Boolean);
131
188
  }, []);
132
189
  const resultLayout = React.useMemo(() => {
190
+ const adjusted = adjustedLayouts.current;
191
+ const original = originalLayouts.current;
192
+ const nowrapAdjust = nowrapAdjustedLayouts.current;
133
193
  return props.layout.map((item) => {
134
- if (item.i in adjustedLayouts.current) {
135
- return Object.assign(Object.assign({}, adjustedLayouts.current[item.i]), { w: item.w, x: item.x, y: item.y });
194
+ const widgetId = item.i;
195
+ if (adjusted[widgetId] || nowrapAdjust[widgetId]) {
196
+ original[widgetId] = item;
197
+ // eslint-disable-next-line no-unused-vars
198
+ const _a = adjusted[widgetId] || item, { parent } = _a, adjustedItem = __rest(_a, ["parent"]);
199
+ adjustedItem.w = item.w;
200
+ adjustedItem.x = item.x;
201
+ adjustedItem.y = item.y;
202
+ if (item.parent) {
203
+ adjustedItem.parent = item.parent;
204
+ }
205
+ if (nowrapAdjust[widgetId]) {
206
+ adjustedItem.maxW = nowrapAdjust[widgetId];
207
+ }
208
+ return adjustedItem;
136
209
  }
137
210
  else {
138
- return Object.assign({}, item);
211
+ if (original[widgetId]) {
212
+ delete original[widgetId];
213
+ }
214
+ return item;
139
215
  }
140
216
  });
141
217
  }, [props.layout, layoutUpdateCounter]);
@@ -157,17 +233,31 @@ function useMemoStateContext(props) {
157
233
  return null;
158
234
  }
159
235
  }, [dragProps, props.registerManager]);
160
- const onDropDragOver = React.useCallback(() => {
236
+ const onDropDragOver = React.useCallback((_e, gridProps, groupLayout) => {
161
237
  if (temporaryLayout) {
162
238
  resetTemporaryLayout();
163
239
  return false;
164
240
  }
165
- if (dragOverPlugin) {
166
- const { defaultLayout } = dragOverPlugin;
167
- const { h = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.h) || DEFAULT_WIDGET_HEIGHT, w = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.w) || DEFAULT_WIDGET_WIDTH, } = dragProps.layout || {};
168
- return { h, w };
241
+ if (!dragOverPlugin) {
242
+ return false;
243
+ }
244
+ let maxW = gridProps.cols;
245
+ const { defaultLayout } = dragOverPlugin;
246
+ const maxH = Math.min(gridProps.maxRows || Infinity, defaultLayout.maxH || Infinity);
247
+ if (gridProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
248
+ maxW = groupLayout.reduce((memo, item) => memo - item.w, gridProps.cols);
249
+ }
250
+ if (maxW === 0 ||
251
+ maxH === 0 ||
252
+ maxW < defaultLayout.minW ||
253
+ maxH < defaultLayout.minH) {
254
+ return false;
169
255
  }
170
- return false;
256
+ const { h = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.h) || DEFAULT_WIDGET_HEIGHT, w = (defaultLayout === null || defaultLayout === void 0 ? void 0 : defaultLayout.w) || DEFAULT_WIDGET_WIDTH, } = dragProps.layout || {};
257
+ return {
258
+ h: maxH ? Math.min(h, maxH) : h,
259
+ w: maxW ? Math.min(w, maxW) : w,
260
+ };
171
261
  }, [resetTemporaryLayout, temporaryLayout, dragOverPlugin, dragProps]);
172
262
  const onDropProp = props.onDrop;
173
263
  const onDrop = React.useCallback((newLayout, item) => {
@@ -181,11 +271,11 @@ function useMemoStateContext(props) {
181
271
  onDropProp({
182
272
  newLayout: newLayout.reduce((memo, l) => {
183
273
  if (l.i !== item.i) {
184
- memo.push(pick(l, ['i', 'h', 'w', 'x', 'y', 'parent']));
274
+ memo.push(pick(l, ITEM_PROPS));
185
275
  }
186
276
  return memo;
187
277
  }, []),
188
- itemLayout: pick(item, ['i', 'h', 'w', 'x', 'y', 'parent']),
278
+ itemLayout: pick(item, ITEM_PROPS),
189
279
  commit: resetTemporaryLayout,
190
280
  dragProps,
191
281
  });
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const useDeepEqualMemo: <T extends unknown>(predicate: () => T, deps: React.DependencyList) => T;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import isEqual from 'lodash/isEqual';
3
+ export const useDeepEqualMemo = (predicate, deps) => {
4
+ const previousValueRef = React.useRef({});
5
+ return React.useMemo(() => {
6
+ const value = predicate();
7
+ if (!isEqual(previousValueRef.current, value)) {
8
+ previousValueRef.current = value;
9
+ }
10
+ return previousValueRef.current;
11
+ }, [previousValueRef, ...deps]);
12
+ };
@@ -19,7 +19,9 @@ export interface DashkitGroupRenderProps {
19
19
  items: ConfigItem[];
20
20
  layout: ConfigLayout[];
21
21
  }
22
- export type ReactGridLayoutProps = Omit<ReactGridLayout.ReactGridLayoutProps, 'children' | 'innerRef' | 'key' | 'layout' | 'isDraggable' | 'isResizable' | 'onDragStart' | 'onDragStop' | 'onResizeStart' | 'onResizeStop' | 'draggableHandle' | 'isDroppable' | 'onDropDragOver' | 'onDrop' | 'draggableCancel'>;
22
+ export type ReactGridLayoutProps = Omit<ReactGridLayout.ReactGridLayoutProps, 'children' | 'compactType' | 'innerRef' | 'key' | 'layout' | 'isDraggable' | 'isResizable' | 'onDragStart' | 'onDragStop' | 'onResizeStart' | 'onResizeStop' | 'draggableHandle' | 'isDroppable' | 'onDropDragOver' | 'onDrop' | 'draggableCancel'> & {
23
+ compactType?: ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap';
24
+ };
23
25
  export interface DashKitGroup {
24
26
  id?: string;
25
27
  render?: (id: string, children: React.ReactNode, props: DashkitGroupRenderProps) => React.ReactNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dashkit",
3
- "version": "8.5.0",
3
+ "version": "8.6.0",
4
4
  "description": "Library for rendering dashboard grid layout",
5
5
  "license": "MIT",
6
6
  "repository": {