@gravity-ui/dashkit 8.5.0-beta.3 → 8.5.0-beta.5

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.
@@ -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, gridProperties: any, layout: 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, gridProperties, layout) => {
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, gridProperties, layout);
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;
@@ -140,19 +208,18 @@ class GridLayout extends react_1.default.PureComponent {
140
208
  if (compactType === constants_1.COMPACT_TYPE_HORIZONTAL_NOWRAP) {
141
209
  compactType = 'horizontal';
142
210
  }
143
- return (react_1.default.createElement(ReactGridLayout_1.Layout, Object.assign({}, properties, { compactType: compactType, 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
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
144
213
  ? { draggableHandle: `.${draggableHandleClassName}` }
145
214
  : null), (outerDnDEnable
146
215
  ? {
147
216
  isDroppable: true,
148
- onDropDragOver: (e) => this._onDropDragOver(e, properties, renderLayout),
149
- onDrop: (...args) => this._onDrop(group, ...args),
217
+ onDropDragOver: callbacks.onDropDragOver,
218
+ onDrop: callbacks.onDrop,
150
219
  }
151
220
  : null), { draggableCancel: `.${constants_1.OVERLAY_CONTROLS_CLASS_NAME}` }),
152
221
  renderItems.map((item, i) => {
153
- return (react_1.default.createElement(GridItem_1.default, { forwardedPluginRef: (pluginRef) => {
154
- this.pluginsRefs[offset + i] = pluginRef;
155
- }, 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 }));
156
223
  }),
157
224
  this.renderTemporaryPlaceholder()));
158
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;
@@ -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,6 +7,7 @@ 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");
12
13
  const ITEM_PROPS = ['i', 'h', 'w', 'x', 'y', 'parent'];
@@ -113,7 +114,9 @@ function useMemoStateContext(props) {
113
114
  forceUpdateLayout((prev) => prev + 1);
114
115
  }
115
116
  }, []);
116
- const horizontalNowrapLayoutAdjust = react_1.default.useCallback((groups, layout) => {
117
+ react_1.default.useMemo(() => {
118
+ const groups = props.groups;
119
+ const layout = props.layout;
117
120
  const defaultProps = props.registerManager._gridLayout || {};
118
121
  const nowrapGroups = {};
119
122
  let hasNowrapGroups = false;
@@ -142,39 +145,24 @@ function useMemoStateContext(props) {
142
145
  const widgetId = item.i;
143
146
  const parentId = item.parent || common_1.DEFAULT_GROUP;
144
147
  if (nowrapGroups[parentId]) {
148
+ // Collecting nowrap elements
145
149
  nowrapGroups[parentId].items.push(item);
146
150
  nowrapGroups[parentId].leftSpace -= item.w;
147
151
  }
148
- else if (nowrapAdjustedLayouts.current[item.i]) {
149
- const { originalMaxW } = nowrapAdjustedLayouts.current[widgetId];
150
- const adjustedItem = adjustedLayouts.current[widgetId];
151
- if (originalMaxW) {
152
- adjustedLayouts.current[widgetId] = Object.assign(Object.assign({}, adjustedItem), { maxW: originalMaxW });
153
- originalLayouts.current[widgetId] = item;
154
- }
155
- else {
156
- adjustedLayouts.current[widgetId] = Object.assign({}, adjustedItem);
157
- originalLayouts.current[widgetId] = item;
158
- }
152
+ else if (nowrapAdjustedLayouts.current[widgetId]) {
153
+ // If element is not in horizontal-nowrap cleaning up and reverting adjustLayout values
159
154
  delete nowrapAdjustedLayouts.current[widgetId];
160
155
  }
161
156
  });
162
157
  Object.entries(nowrapGroups).forEach(([, { items, leftSpace }]) => {
163
158
  items.forEach((item) => {
164
- const maxW = item.w + leftSpace;
165
- if (!adjustedLayouts[item.i] || adjustedLayouts[item.i].maxW > maxW) {
166
- originalLayouts.current[item.i] = item;
167
- adjustedLayouts.current[item.i] = Object.assign(Object.assign({}, item), { maxW });
168
- nowrapAdjustedLayouts.current[item.i] = item.maxW
169
- ? { maxW, originalMaxW: item.maxW }
170
- : { maxW };
171
- }
159
+ // setting maxW with adjustLayout fields and saving previous
160
+ nowrapAdjustedLayouts.current[item.i] = item.w + leftSpace;
172
161
  });
173
162
  });
174
163
  }
175
- return adjustedLayouts.current;
176
- }, [props.registerManager]);
177
- const itemsParams = react_1.default.useMemo(() => (0, shared_1.getItemsParams)({
164
+ }, [props.registerManager, props.groups, props.layout]);
165
+ const itemsParams = (0, useDeepEqualMemo_1.useDeepEqualMemo)(() => (0, shared_1.getItemsParams)({
178
166
  defaultGlobalParams: props.defaultGlobalParams,
179
167
  globalParams: props.globalParams,
180
168
  config: props.config,
@@ -187,7 +175,7 @@ function useMemoStateContext(props) {
187
175
  props.itemsStateAndParams,
188
176
  props.registerManager,
189
177
  ]);
190
- const itemsState = react_1.default.useMemo(() => (0, shared_1.getItemsState)({
178
+ const itemsState = (0, useDeepEqualMemo_1.useDeepEqualMemo)(() => (0, shared_1.getItemsState)({
191
179
  config: props.config,
192
180
  itemsStateAndParams: props.itemsStateAndParams,
193
181
  }), [props.config, props.itemsStateAndParams]);
@@ -202,24 +190,34 @@ function useMemoStateContext(props) {
202
190
  .filter(Boolean);
203
191
  }, []);
204
192
  const resultLayout = react_1.default.useMemo(() => {
205
- const adjusted = horizontalNowrapLayoutAdjust(props.groups, props.layout);
193
+ const adjusted = adjustedLayouts.current;
194
+ const original = originalLayouts.current;
195
+ const nowrapAdjust = nowrapAdjustedLayouts.current;
206
196
  return props.layout.map((item) => {
207
- if (item.i in adjusted) {
197
+ const widgetId = item.i;
198
+ if (adjusted[widgetId] || nowrapAdjust[widgetId]) {
199
+ original[widgetId] = item;
208
200
  // eslint-disable-next-line no-unused-vars
209
- const _a = adjusted[item.i] || {}, { parent } = _a, adjustedItem = tslib_1.__rest(_a, ["parent"]);
201
+ const _a = adjusted[widgetId] || item, { parent } = _a, adjustedItem = tslib_1.__rest(_a, ["parent"]);
210
202
  adjustedItem.w = item.w;
211
203
  adjustedItem.x = item.x;
212
204
  adjustedItem.y = item.y;
213
205
  if (item.parent) {
214
206
  adjustedItem.parent = item.parent;
215
207
  }
208
+ if (nowrapAdjust[widgetId]) {
209
+ adjustedItem.maxW = nowrapAdjust[widgetId];
210
+ }
216
211
  return adjustedItem;
217
212
  }
218
213
  else {
214
+ if (original[widgetId]) {
215
+ delete original[widgetId];
216
+ }
219
217
  return item;
220
218
  }
221
219
  });
222
- }, [props.layout, props.groups, horizontalNowrapLayoutAdjust, layoutUpdateCounter]);
220
+ }, [props.layout, layoutUpdateCounter]);
223
221
  const reloadItems = react_1.default.useCallback((pluginsRefs, data) => {
224
222
  pluginsRefs.forEach((ref) => ref && ref.reload && ref.reload(data));
225
223
  }, []);
@@ -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,19 @@
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
+ const callbackRef = react_1.default.useRef(predicate);
10
+ callbackRef.current = predicate;
11
+ return react_1.default.useMemo(() => {
12
+ const value = predicate();
13
+ if (!(0, isEqual_1.default)(previousValueRef.current, value)) {
14
+ previousValueRef.current = value;
15
+ }
16
+ return previousValueRef.current;
17
+ }, [callbackRef, previousValueRef, ...deps]);
18
+ };
19
+ exports.useDeepEqualMemo = useDeepEqualMemo;
@@ -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, gridProperties: any, layout: 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;
@@ -6,6 +6,10 @@ 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, gridProperties, layout) => {
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, gridProperties, layout);
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;
@@ -137,19 +205,18 @@ class GridLayout extends React.PureComponent {
137
205
  if (compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) {
138
206
  compactType = 'horizontal';
139
207
  }
140
- return (React.createElement(Layout, Object.assign({}, properties, { compactType: compactType, 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
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
141
210
  ? { draggableHandle: `.${draggableHandleClassName}` }
142
211
  : null), (outerDnDEnable
143
212
  ? {
144
213
  isDroppable: true,
145
- onDropDragOver: (e) => this._onDropDragOver(e, properties, renderLayout),
146
- onDrop: (...args) => this._onDrop(group, ...args),
214
+ onDropDragOver: callbacks.onDropDragOver,
215
+ onDrop: callbacks.onDrop,
147
216
  }
148
217
  : null), { draggableCancel: `.${OVERLAY_CONTROLS_CLASS_NAME}` }),
149
218
  renderItems.map((item, i) => {
150
- return (React.createElement(GridItem, { forwardedPluginRef: (pluginRef) => {
151
- this.pluginsRefs[offset + i] = pluginRef;
152
- }, 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 }));
153
220
  }),
154
221
  this.renderTemporaryPlaceholder()));
155
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;
@@ -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 = {
@@ -4,6 +4,7 @@ import isEqual from 'lodash/isEqual';
4
4
  import pick from 'lodash/pick';
5
5
  import { COMPACT_TYPE_HORIZONTAL_NOWRAP, DEFAULT_GROUP, DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, TEMPORARY_ITEM_ID, } from '../constants/common';
6
6
  import { DashKitContext, DashKitDnDContext } from '../context/DashKitContext';
7
+ import { useDeepEqualMemo } from '../hooks/useDeepEqualMemo';
7
8
  import { getItemsParams, getItemsState } from '../shared';
8
9
  import { UpdateManager } from '../utils';
9
10
  const ITEM_PROPS = ['i', 'h', 'w', 'x', 'y', 'parent'];
@@ -110,7 +111,9 @@ function useMemoStateContext(props) {
110
111
  forceUpdateLayout((prev) => prev + 1);
111
112
  }
112
113
  }, []);
113
- const horizontalNowrapLayoutAdjust = React.useCallback((groups, layout) => {
114
+ React.useMemo(() => {
115
+ const groups = props.groups;
116
+ const layout = props.layout;
114
117
  const defaultProps = props.registerManager._gridLayout || {};
115
118
  const nowrapGroups = {};
116
119
  let hasNowrapGroups = false;
@@ -139,39 +142,24 @@ function useMemoStateContext(props) {
139
142
  const widgetId = item.i;
140
143
  const parentId = item.parent || DEFAULT_GROUP;
141
144
  if (nowrapGroups[parentId]) {
145
+ // Collecting nowrap elements
142
146
  nowrapGroups[parentId].items.push(item);
143
147
  nowrapGroups[parentId].leftSpace -= item.w;
144
148
  }
145
- else if (nowrapAdjustedLayouts.current[item.i]) {
146
- const { originalMaxW } = nowrapAdjustedLayouts.current[widgetId];
147
- const adjustedItem = adjustedLayouts.current[widgetId];
148
- if (originalMaxW) {
149
- adjustedLayouts.current[widgetId] = Object.assign(Object.assign({}, adjustedItem), { maxW: originalMaxW });
150
- originalLayouts.current[widgetId] = item;
151
- }
152
- else {
153
- adjustedLayouts.current[widgetId] = Object.assign({}, adjustedItem);
154
- originalLayouts.current[widgetId] = item;
155
- }
149
+ else if (nowrapAdjustedLayouts.current[widgetId]) {
150
+ // If element is not in horizontal-nowrap cleaning up and reverting adjustLayout values
156
151
  delete nowrapAdjustedLayouts.current[widgetId];
157
152
  }
158
153
  });
159
154
  Object.entries(nowrapGroups).forEach(([, { items, leftSpace }]) => {
160
155
  items.forEach((item) => {
161
- const maxW = item.w + leftSpace;
162
- if (!adjustedLayouts[item.i] || adjustedLayouts[item.i].maxW > maxW) {
163
- originalLayouts.current[item.i] = item;
164
- adjustedLayouts.current[item.i] = Object.assign(Object.assign({}, item), { maxW });
165
- nowrapAdjustedLayouts.current[item.i] = item.maxW
166
- ? { maxW, originalMaxW: item.maxW }
167
- : { maxW };
168
- }
156
+ // setting maxW with adjustLayout fields and saving previous
157
+ nowrapAdjustedLayouts.current[item.i] = item.w + leftSpace;
169
158
  });
170
159
  });
171
160
  }
172
- return adjustedLayouts.current;
173
- }, [props.registerManager]);
174
- const itemsParams = React.useMemo(() => getItemsParams({
161
+ }, [props.registerManager, props.groups, props.layout]);
162
+ const itemsParams = useDeepEqualMemo(() => getItemsParams({
175
163
  defaultGlobalParams: props.defaultGlobalParams,
176
164
  globalParams: props.globalParams,
177
165
  config: props.config,
@@ -184,7 +172,7 @@ function useMemoStateContext(props) {
184
172
  props.itemsStateAndParams,
185
173
  props.registerManager,
186
174
  ]);
187
- const itemsState = React.useMemo(() => getItemsState({
175
+ const itemsState = useDeepEqualMemo(() => getItemsState({
188
176
  config: props.config,
189
177
  itemsStateAndParams: props.itemsStateAndParams,
190
178
  }), [props.config, props.itemsStateAndParams]);
@@ -199,24 +187,34 @@ function useMemoStateContext(props) {
199
187
  .filter(Boolean);
200
188
  }, []);
201
189
  const resultLayout = React.useMemo(() => {
202
- const adjusted = horizontalNowrapLayoutAdjust(props.groups, props.layout);
190
+ const adjusted = adjustedLayouts.current;
191
+ const original = originalLayouts.current;
192
+ const nowrapAdjust = nowrapAdjustedLayouts.current;
203
193
  return props.layout.map((item) => {
204
- if (item.i in adjusted) {
194
+ const widgetId = item.i;
195
+ if (adjusted[widgetId] || nowrapAdjust[widgetId]) {
196
+ original[widgetId] = item;
205
197
  // eslint-disable-next-line no-unused-vars
206
- const _a = adjusted[item.i] || {}, { parent } = _a, adjustedItem = __rest(_a, ["parent"]);
198
+ const _a = adjusted[widgetId] || item, { parent } = _a, adjustedItem = __rest(_a, ["parent"]);
207
199
  adjustedItem.w = item.w;
208
200
  adjustedItem.x = item.x;
209
201
  adjustedItem.y = item.y;
210
202
  if (item.parent) {
211
203
  adjustedItem.parent = item.parent;
212
204
  }
205
+ if (nowrapAdjust[widgetId]) {
206
+ adjustedItem.maxW = nowrapAdjust[widgetId];
207
+ }
213
208
  return adjustedItem;
214
209
  }
215
210
  else {
211
+ if (original[widgetId]) {
212
+ delete original[widgetId];
213
+ }
216
214
  return item;
217
215
  }
218
216
  });
219
- }, [props.layout, props.groups, horizontalNowrapLayoutAdjust, layoutUpdateCounter]);
217
+ }, [props.layout, layoutUpdateCounter]);
220
218
  const reloadItems = React.useCallback((pluginsRefs, data) => {
221
219
  pluginsRefs.forEach((ref) => ref && ref.reload && ref.reload(data));
222
220
  }, []);
@@ -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,14 @@
1
+ import React from 'react';
2
+ import isEqual from 'lodash/isEqual';
3
+ export const useDeepEqualMemo = (predicate, deps) => {
4
+ const previousValueRef = React.useRef({});
5
+ const callbackRef = React.useRef(predicate);
6
+ callbackRef.current = predicate;
7
+ return React.useMemo(() => {
8
+ const value = predicate();
9
+ if (!isEqual(previousValueRef.current, value)) {
10
+ previousValueRef.current = value;
11
+ }
12
+ return previousValueRef.current;
13
+ }, [callbackRef, previousValueRef, ...deps]);
14
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dashkit",
3
- "version": "8.5.0-beta.3",
3
+ "version": "8.5.0-beta.5",
4
4
  "description": "Library for rendering dashboard grid layout",
5
5
  "license": "MIT",
6
6
  "repository": {