@gravity-ui/dashkit 8.9.1 → 8.10.1

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 (34) hide show
  1. package/README.md +4 -0
  2. package/build/cjs/components/DashKit/DashKit.d.ts +5 -0
  3. package/build/cjs/components/GridItem/GridItem.js +30 -2
  4. package/build/cjs/components/GridLayout/GridLayout.js +2 -2
  5. package/build/cjs/components/Item/Item.d.ts +2 -0
  6. package/build/cjs/components/Item/Item.js +13 -2
  7. package/build/cjs/hocs/prepareItem.d.ts +2 -0
  8. package/build/cjs/hocs/prepareItem.js +6 -3
  9. package/build/cjs/hocs/withContext.js +4 -0
  10. package/build/cjs/hooks/useCalcLayout.d.ts +2 -2
  11. package/build/cjs/plugins/Text/Text.d.ts +1 -0
  12. package/build/cjs/plugins/Text/Text.js +2 -0
  13. package/build/cjs/typings/plugin.d.ts +1 -0
  14. package/build/cjs/utils/__tests__/update-manager.test.js +98 -0
  15. package/build/cjs/utils/register-manager.d.ts +2 -2
  16. package/build/cjs/utils/update-manager.d.ts +2 -2
  17. package/build/cjs/utils/update-manager.js +4 -3
  18. package/build/esm/components/DashKit/DashKit.d.ts +5 -0
  19. package/build/esm/components/GridItem/GridItem.js +30 -2
  20. package/build/esm/components/GridLayout/GridLayout.js +2 -2
  21. package/build/esm/components/Item/Item.d.ts +2 -0
  22. package/build/esm/components/Item/Item.js +13 -2
  23. package/build/esm/hocs/prepareItem.d.ts +2 -0
  24. package/build/esm/hocs/prepareItem.js +6 -3
  25. package/build/esm/hocs/withContext.js +4 -0
  26. package/build/esm/hooks/useCalcLayout.d.ts +2 -2
  27. package/build/esm/plugins/Text/Text.d.ts +1 -0
  28. package/build/esm/plugins/Text/Text.js +2 -0
  29. package/build/esm/typings/plugin.d.ts +1 -0
  30. package/build/esm/utils/__tests__/update-manager.test.js +98 -0
  31. package/build/esm/utils/register-manager.d.ts +2 -2
  32. package/build/esm/utils/update-manager.d.ts +2 -2
  33. package/build/esm/utils/update-manager.js +4 -3
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -30,6 +30,8 @@ interface DashKitProps {
30
30
  onItemEdit: ({id}: {id: string}) => void;
31
31
  onChange: (data: {config: Config; itemsStateAndParams: ItemsStateAndParams}) => void;
32
32
  onDrop: (dropProps: ItemDropProps) => void;
33
+ onItemMountChange: (item: ConfigItem, state: {isAsync: boolead; isMounted: boolean}) => void;
34
+ onItemRender: (item: ConfigItem) => void;
33
35
  defaultGlobalParams: GlobalParams;
34
36
  globalParams: GlobalParams;
35
37
  itemsStateAndParams: ItemsStateAndParams;
@@ -48,6 +50,8 @@ interface DashKitProps {
48
50
  - **onItemEdit**: Called when you click to edit a widget.
49
51
  - **onChange**: Called when config or [itemsStateAndParams](#temsStateAndParams) are changed.
50
52
  - **onDrop**: Called when item dropped from ActionPanel using (#DashKitDnDWrapper)
53
+ - **onItemMountChange**: Called when item mount state changed
54
+ - **onItemRender**: Called when item render complete
51
55
  - **defaultGlobalParams**, **globalParams**: [Parameters](#Params) that affect all widgets. In DataLens, `defaultGlobalParams` are global parameters set in the dashboard settings. `globalParams` are globals parameters that can be set in the url.
52
56
  - **itemsStateAndParams**: [itemsStateAndParams](#temsStateAndParams).
53
57
  - **settings**: DashKit settings.
@@ -18,6 +18,11 @@ interface DashKitDefaultProps {
18
18
  groups?: DashKitGroup[];
19
19
  }) => void;
20
20
  onDrop: (dropProps: ItemDropProps) => void;
21
+ onItemMountChange?: (item: ConfigItem, state: {
22
+ isAsync: boolean;
23
+ isMounted: boolean;
24
+ }) => void;
25
+ onItemRender?: (item: ConfigItem) => void;
21
26
  defaultGlobalParams: GlobalParams;
22
27
  globalParams: GlobalParams;
23
28
  itemsStateAndParams: ItemsStateAndParams;
@@ -38,6 +38,7 @@ const windowFocusObserver = new WindowFocusObserver();
38
38
  class GridItem extends react_1.default.PureComponent {
39
39
  constructor() {
40
40
  super(...arguments);
41
+ this._isAsyncItem = false;
41
42
  this.state = {
42
43
  isFocused: false,
43
44
  };
@@ -56,6 +57,33 @@ class GridItem extends react_1.default.PureComponent {
56
57
  this.controller = null;
57
58
  });
58
59
  };
60
+ this.onMountChange = (isMounted) => {
61
+ var _a, _b, _c, _d, _e, _f;
62
+ if (isMounted) {
63
+ this._inited = true;
64
+ (_b = (_a = this.props).onItemMountChange) === null || _b === void 0 ? void 0 : _b.call(_a, this.props.item, {
65
+ isAsync: this._isAsyncItem,
66
+ isMounted: isMounted,
67
+ });
68
+ if (!this._isAsyncItem) {
69
+ (_d = (_c = this.props).onItemRender) === null || _d === void 0 ? void 0 : _d.call(_c, this.props.item);
70
+ }
71
+ }
72
+ else {
73
+ (_f = (_e = this.props).onItemMountChange) === null || _f === void 0 ? void 0 : _f.call(_e, this.props.item, {
74
+ isAsync: this._isAsyncItem,
75
+ isMounted: isMounted,
76
+ });
77
+ }
78
+ };
79
+ this.onBeforeLoad = () => {
80
+ this._isAsyncItem = true;
81
+ return this.onLoad;
82
+ };
83
+ this.onLoad = () => {
84
+ var _a, _b;
85
+ (_b = (_a = this.props).onItemRender) === null || _b === void 0 ? void 0 : _b.call(_a, this.props.item);
86
+ };
59
87
  }
60
88
  renderOverlay() {
61
89
  const { isPlaceholder } = this.props;
@@ -71,7 +99,7 @@ class GridItem extends react_1.default.PureComponent {
71
99
  render() {
72
100
  // из-за бага, что Grid Items unmounts при изменении static, isDraggable, isResaizable
73
101
  // https://github.com/STRML/react-grid-layout/issues/721
74
- const { style, onMouseDown, onMouseUp, onTouchEnd, onTouchStart, children, className, isDragging, noOverlay, focusable, withCustomHandle, isPlaceholder, } = this.props;
102
+ const { style, onMouseDown, onMouseUp, onTouchEnd, onTouchStart, children, className, isDragging, noOverlay, focusable, withCustomHandle, isPlaceholder = false, } = this.props;
75
103
  const { editMode } = this.context;
76
104
  const width = Number.parseInt(style.width, 10);
77
105
  const height = Number.parseInt(style.height, 10);
@@ -96,7 +124,7 @@ class GridItem extends react_1.default.PureComponent {
96
124
  }
97
125
  : {})),
98
126
  react_1.default.createElement("div", { className: b('item', { editMode: editMode && !_editActive && !noOverlay }) },
99
- react_1.default.createElement(Item_1.default, { id: this.props.id, item: this.props.item, shouldItemUpdate: !isDragging, width: width, height: height, transform: transform, isPlaceholder: isPlaceholder, adjustWidgetLayout: this.props.adjustWidgetLayout, layout: this.props.layout, forwardedPluginRef: this.props.forwardedPluginRef })),
127
+ react_1.default.createElement(Item_1.default, { id: this.props.id, item: this.props.item, shouldItemUpdate: !isDragging, width: width, height: height, transform: transform, isPlaceholder: isPlaceholder, adjustWidgetLayout: this.props.adjustWidgetLayout, layout: this.props.layout, forwardedPluginRef: this.props.forwardedPluginRef, onMountChange: this.onMountChange, onBeforeLoad: this.onBeforeLoad })),
100
128
  !noOverlay && this.renderOverlay(),
101
129
  preparedChildren));
102
130
  }
@@ -287,7 +287,7 @@ class GridLayout extends react_1.default.PureComponent {
287
287
  return (react_1.default.createElement(GridItem_1.default, { key: id, id: id, item: { id, type, data: {} }, layout: temporaryLayout.data, adjustWidgetLayout: this.adjustWidgetLayout, isDragging: this.state.isDragging, isPlaceholder: true, noOverlay: noOverlay, withCustomHandle: Boolean(draggableHandleClassName) }));
288
288
  }
289
289
  renderGroup(group, renderLayout, renderItems, offset = 0, groupGridProperties) {
290
- const { registerManager, editMode, noOverlay, focusable, draggableHandleClassName, outerDnDEnable, } = this.context;
290
+ const { registerManager, editMode, noOverlay, focusable, draggableHandleClassName, outerDnDEnable, onItemMountChange, onItemRender, } = this.context;
291
291
  const { currentDraggingElement, draggedOverGroup } = this.state;
292
292
  const properties = groupGridProperties
293
293
  ? groupGridProperties(Object.assign({}, registerManager.gridLayout))
@@ -310,7 +310,7 @@ class GridLayout extends react_1.default.PureComponent {
310
310
  }
311
311
  : null), { draggableCancel: `.${constants_1.OVERLAY_CONTROLS_CLASS_NAME}` }),
312
312
  renderItems.map((item, i) => {
313
- 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) }));
313
+ 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), onItemMountChange: onItemMountChange, onItemRender: onItemRender }));
314
314
  }),
315
315
  this.renderTemporaryPlaceholder()));
316
316
  }
@@ -61,7 +61,9 @@ declare const _default: {
61
61
  height: PropTypes.Requireable<number>;
62
62
  transform: PropTypes.Requireable<string>;
63
63
  isPlaceholder: PropTypes.Requireable<boolean>;
64
+ onBeforeLoad: PropTypes.Requireable<(...args: any[]) => any>;
64
65
  forwardedPluginRef: PropTypes.Requireable<any>;
66
+ onMountChange: PropTypes.Requireable<(...args: any[]) => any>;
65
67
  };
66
68
  contextType: React.Context<any>;
67
69
  };
@@ -7,9 +7,18 @@ const prepareItem_1 = require("../../hocs/prepareItem");
7
7
  const cn_1 = require("../../utils/cn");
8
8
  const b = (0, cn_1.cn)('dashkit-item');
9
9
  // TODO: getDerivedStateFromError и заглушка с ошибкой
10
- const Item = ({ registerManager, rendererProps, type, isPlaceholder, forwardedPluginRef }) => {
10
+ const Item = ({ registerManager, rendererProps, type, isPlaceholder, forwardedPluginRef, onMountChange, }) => {
11
11
  var _a, _b;
12
- if (!registerManager.check(type)) {
12
+ const isRegisteredType = registerManager.check(type);
13
+ react_1.default.useLayoutEffect(() => {
14
+ if (isRegisteredType && !isPlaceholder) {
15
+ onMountChange === null || onMountChange === void 0 ? void 0 : onMountChange(true);
16
+ return () => {
17
+ onMountChange === null || onMountChange === void 0 ? void 0 : onMountChange(false);
18
+ };
19
+ }
20
+ }, []);
21
+ if (!isRegisteredType) {
13
22
  console.warn(`type [${type}] не зарегистрирован`);
14
23
  return null;
15
24
  }
@@ -25,5 +34,7 @@ Item.propTypes = {
25
34
  registerManager: prop_types_1.default.object,
26
35
  type: prop_types_1.default.string,
27
36
  isPlaceholder: prop_types_1.default.bool,
37
+ onMountChange: prop_types_1.default.func,
38
+ onBeforeLoad: prop_types_1.default.func,
28
39
  };
29
40
  exports.default = (0, prepareItem_1.prepareItem)(Item);
@@ -61,7 +61,9 @@ export function prepareItem(Component: any): {
61
61
  height: PropTypes.Requireable<number>;
62
62
  transform: PropTypes.Requireable<string>;
63
63
  isPlaceholder: PropTypes.Requireable<boolean>;
64
+ onBeforeLoad: PropTypes.Requireable<(...args: any[]) => any>;
64
65
  forwardedPluginRef: PropTypes.Requireable<any>;
66
+ onMountChange: PropTypes.Requireable<(...args: any[]) => any>;
65
67
  };
66
68
  contextType: React.Context<any>;
67
69
  };
@@ -15,7 +15,7 @@ function prepareItem(Component) {
15
15
  };
16
16
  this._currentRenderProps = {};
17
17
  this.getRenderProps = () => {
18
- const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder } = this.props;
18
+ const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder, onBeforeLoad, } = this.props;
19
19
  const { itemsState, itemsParams, registerManager, settings, context, editMode } = this.context;
20
20
  const { data, defaults, namespace } = item;
21
21
  const rendererProps = {
@@ -24,6 +24,7 @@ function prepareItem(Component) {
24
24
  params: itemsParams[id],
25
25
  state: itemsState[id],
26
26
  onStateAndParamsChange: this._onStateAndParamsChange,
27
+ onBeforeLoad,
27
28
  width,
28
29
  height,
29
30
  id,
@@ -55,10 +56,10 @@ function prepareItem(Component) {
55
56
  return true;
56
57
  }
57
58
  render() {
58
- const { item, isPlaceholder } = this.props;
59
+ const { item, isPlaceholder, forwardedPluginRef, onMountChange } = this.props;
59
60
  const { registerManager } = this.context;
60
61
  const { type } = item;
61
- return (react_1.default.createElement(Component, { forwardedPluginRef: this.props.forwardedPluginRef, rendererProps: this.getRenderProps(), registerManager: registerManager, type: type, isPlaceholder: isPlaceholder }));
62
+ return (react_1.default.createElement(Component, { forwardedPluginRef: forwardedPluginRef, rendererProps: this.getRenderProps(), registerManager: registerManager, type: type, isPlaceholder: isPlaceholder, onMountChange: onMountChange }));
62
63
  }
63
64
  },
64
65
  _a.propTypes = {
@@ -71,7 +72,9 @@ function prepareItem(Component) {
71
72
  height: prop_types_1.default.number,
72
73
  transform: prop_types_1.default.string,
73
74
  isPlaceholder: prop_types_1.default.bool,
75
+ onBeforeLoad: prop_types_1.default.func,
74
76
  forwardedPluginRef: prop_types_1.default.any,
77
+ onMountChange: prop_types_1.default.func,
75
78
  },
76
79
  _a.contextType = DashKitContext_1.DashKitContext,
77
80
  _a;
@@ -320,6 +320,8 @@ function useMemoStateContext(props) {
320
320
  onItemStateAndParamsChange,
321
321
  onDrop,
322
322
  onDropDragOver,
323
+ onItemMountChange: props.onItemMountChange,
324
+ onItemRender: props.onItemRender,
323
325
  layoutChange: onLayoutChange,
324
326
  getItemsMeta,
325
327
  reloadItems,
@@ -347,6 +349,8 @@ function useMemoStateContext(props) {
347
349
  onDrop,
348
350
  onDropDragOver,
349
351
  onLayoutChange,
352
+ props.onItemMountChange,
353
+ props.onItemRender,
350
354
  getItemsMeta,
351
355
  reloadItems,
352
356
  memorizeOriginalLayout,
@@ -7,8 +7,8 @@ export declare const useCalcPropsLayout: (config: Config, registerManager: Regis
7
7
  x: number;
8
8
  y: number;
9
9
  parent?: string | undefined;
10
- minW: number;
11
- minH: number;
10
+ minW?: number | undefined;
11
+ minH?: number | undefined;
12
12
  maxW?: number | undefined;
13
13
  maxH?: number | undefined;
14
14
  }[];
@@ -15,6 +15,7 @@ export interface PluginTextProps extends PluginWidgetProps {
15
15
  data: {
16
16
  text: string;
17
17
  } & PluginWidgetProps['data'];
18
+ onBeforeRender: () => () => void;
18
19
  }
19
20
  interface PluginTextState {
20
21
  htmlText: string;
@@ -80,6 +80,7 @@ class PluginText extends react_1.default.PureComponent {
80
80
  return;
81
81
  }
82
82
  this.setState({ status: LoadStatus.Pending });
83
+ const onLoadComplete = this.props.onBeforeLoad();
83
84
  try {
84
85
  let htmlText = '';
85
86
  if (this.state.text && this.state.text.trim()) {
@@ -100,6 +101,7 @@ class PluginText extends react_1.default.PureComponent {
100
101
  }
101
102
  this.setState({ status: LoadStatus.Fail });
102
103
  }
104
+ onLoadComplete();
103
105
  }
104
106
  get withMarkdown() {
105
107
  return typeof this.props.apiHandler === 'function';
@@ -8,6 +8,7 @@ export interface PluginWidgetProps<T = StringParams> {
8
8
  params: T;
9
9
  state: ItemState;
10
10
  onStateAndParamsChange: (stateAndParams: ItemStateAndParams, options?: ItemStateAndParamsChangeOptions) => void;
11
+ onBeforeLoad: () => () => void;
11
12
  width: number;
12
13
  height: number;
13
14
  data: ConfigItem['data'];
@@ -188,6 +188,104 @@ beforeEach(() => {
188
188
  jest.spyOn(console, 'warn').mockImplementation(jest.fn());
189
189
  });
190
190
  describe('UpdateManager', () => {
191
+ describe('addItem', () => {
192
+ it('add new item', () => {
193
+ expect(update_manager_1.UpdateManager.addItem({
194
+ item: {
195
+ data: {},
196
+ type: 'widget',
197
+ },
198
+ namespace: 'default',
199
+ layout: { h: 4, w: 4, x: 0, y: 0 },
200
+ config: config,
201
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
202
+ ...config.items,
203
+ {
204
+ id: 'EZ',
205
+ data: {},
206
+ type: 'widget',
207
+ namespace: 'default',
208
+ },
209
+ ], layout: [...config.layout, { h: 4, w: 4, x: 0, y: 32, i: 'EZ' }] }));
210
+ });
211
+ it('add new item excludeIds', () => {
212
+ expect(update_manager_1.UpdateManager.addItem({
213
+ item: {
214
+ data: {},
215
+ type: 'widget',
216
+ },
217
+ namespace: 'default',
218
+ layout: { h: 4, w: 4, x: 0, y: 0 },
219
+ config: config,
220
+ options: {
221
+ excludeIds: ['EZ'],
222
+ },
223
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 16, items: [
224
+ ...config.items,
225
+ {
226
+ id: '1n',
227
+ data: {},
228
+ type: 'widget',
229
+ namespace: 'default',
230
+ },
231
+ ], layout: [...config.layout, { h: 4, w: 4, x: 0, y: 32, i: '1n' }] }));
232
+ });
233
+ it('add new item with layout update', () => {
234
+ const updateLayout = [
235
+ { h: 4, i: 'al', w: 4, x: 8, y: 4 },
236
+ { h: 16, i: 'Q8', w: 12, x: 12, y: 4 },
237
+ { h: 2, i: 'L5', w: 8, x: 0, y: 6 },
238
+ { h: 2, i: 'C8', w: 8, x: 10, y: 34 },
239
+ { h: 2, i: 'lko', w: 8, x: 0, y: 16 },
240
+ { h: 2, i: 'qY', w: 8, x: 0, y: 28 },
241
+ ];
242
+ expect(update_manager_1.UpdateManager.addItem({
243
+ item: {
244
+ data: {},
245
+ type: 'widget',
246
+ },
247
+ namespace: 'default',
248
+ layout: { h: 4, w: 12, x: 0, y: 0 },
249
+ config: config,
250
+ options: {
251
+ updateLayout,
252
+ },
253
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
254
+ ...config.items,
255
+ {
256
+ id: 'EZ',
257
+ data: {},
258
+ type: 'widget',
259
+ namespace: 'default',
260
+ },
261
+ ], layout: [...updateLayout, { h: 4, w: 12, x: 0, y: 0, i: 'EZ' }] }));
262
+ });
263
+ });
264
+ describe('removeItem', () => {
265
+ it('remove item by id', () => {
266
+ const deleteId = 'lko';
267
+ expect(update_manager_1.UpdateManager.removeItem({ id: deleteId, config, itemsStateAndParams: {} })).toEqual({
268
+ config: Object.assign(Object.assign({}, config), { items: config.items.filter(({ id }) => id !== deleteId), layout: config.layout.filter(({ i }) => i !== deleteId) }),
269
+ itemsStateAndParams: {
270
+ __meta__: { queue: [], version: 2 },
271
+ },
272
+ });
273
+ });
274
+ it('remove item when items and layout not sorted', () => {
275
+ const deleteId = 'lko';
276
+ const configCopy = Object.assign(Object.assign({}, config), { layout: config.layout.reverse() });
277
+ expect(update_manager_1.UpdateManager.removeItem({
278
+ id: deleteId,
279
+ config: configCopy,
280
+ itemsStateAndParams: {},
281
+ })).toEqual({
282
+ config: Object.assign(Object.assign({}, config), { items: config.items.filter(({ id }) => id !== deleteId), layout: config.layout.filter(({ i }) => i !== deleteId) }),
283
+ itemsStateAndParams: {
284
+ __meta__: { queue: [], version: 2 },
285
+ },
286
+ });
287
+ });
288
+ });
191
289
  describe('changeStateAndParams', () => {
192
290
  it('control adds params to empty itemsStateAndParams: common control', () => {
193
291
  expect(update_manager_1.UpdateManager.changeStateAndParams({
@@ -5,8 +5,8 @@ interface RegisterManagerDefaultLayout {
5
5
  y: number;
6
6
  w: number;
7
7
  h: number;
8
- minW: number;
9
- minH: number;
8
+ minW?: number;
9
+ minH?: number;
10
10
  }
11
11
  export type RegisterManagerPluginLayout = RegisterManagerDefaultLayout & PluginDefaultLayout;
12
12
  export interface RegisterManagerPlugin extends Omit<Plugin, 'defaultLayout'> {
@@ -26,13 +26,13 @@ export declare class UpdateManager {
26
26
  namespace: string;
27
27
  layout: RegisterManagerPluginLayout;
28
28
  config: Config;
29
- options: SetNewItemOptions;
29
+ options?: SetNewItemOptions;
30
30
  }): Config;
31
31
  static editItem({ item, namespace, config, options, }: {
32
32
  item: ConfigItem;
33
33
  namespace: string;
34
34
  config: Config;
35
- options: SetItemOptions;
35
+ options?: SetItemOptions;
36
36
  }): Config;
37
37
  static removeItem({ id, config, itemsStateAndParams }: RemoveItemArg): {
38
38
  config: Config;
@@ -227,7 +227,7 @@ function changeGroupParams({ groupItemIds, initiatorId, initiatorItem, itemsStat
227
227
  return (0, immutability_helper_1.default)(itemsStateAndParams, obj);
228
228
  }
229
229
  class UpdateManager {
230
- static addItem({ item, namespace = constants_1.DEFAULT_NAMESPACE, layout, config, options, }) {
230
+ static addItem({ item, namespace = constants_1.DEFAULT_NAMESPACE, layout, config, options = {}, }) {
231
231
  const salt = config.salt;
232
232
  const newItemData = getNewItemData({ item, config, salt, counter: config.counter, options });
233
233
  let counter = newItemData.counter;
@@ -260,7 +260,7 @@ class UpdateManager {
260
260
  });
261
261
  }
262
262
  }
263
- static editItem({ item, namespace = constants_1.DEFAULT_NAMESPACE, config, options, }) {
263
+ static editItem({ item, namespace = constants_1.DEFAULT_NAMESPACE, config, options = {}, }) {
264
264
  const itemIndex = config.items.findIndex(({ id }) => item.id === id);
265
265
  const { counter, data } = getNewItemData({
266
266
  item,
@@ -279,6 +279,7 @@ class UpdateManager {
279
279
  return removeItemVersion1({ id, config, itemsStateAndParams });
280
280
  }
281
281
  const itemIndex = config.items.findIndex((item) => item.id === id);
282
+ const layoutIndex = config.layout.findIndex((item) => item.i === id);
282
283
  const item = config.items[itemIndex];
283
284
  let itemIds = [id];
284
285
  if ((0, shared_1.isItemWithTabs)(item)) {
@@ -294,7 +295,7 @@ class UpdateManager {
294
295
  $splice: [[itemIndex, 1]],
295
296
  },
296
297
  layout: {
297
- $splice: [[itemIndex, 1]],
298
+ $splice: [[layoutIndex, 1]],
298
299
  },
299
300
  connections: {
300
301
  $set: connections,
@@ -18,6 +18,11 @@ interface DashKitDefaultProps {
18
18
  groups?: DashKitGroup[];
19
19
  }) => void;
20
20
  onDrop: (dropProps: ItemDropProps) => void;
21
+ onItemMountChange?: (item: ConfigItem, state: {
22
+ isAsync: boolean;
23
+ isMounted: boolean;
24
+ }) => void;
25
+ onItemRender?: (item: ConfigItem) => void;
21
26
  defaultGlobalParams: GlobalParams;
22
27
  globalParams: GlobalParams;
23
28
  itemsStateAndParams: ItemsStateAndParams;
@@ -36,6 +36,7 @@ const windowFocusObserver = new WindowFocusObserver();
36
36
  class GridItem extends React.PureComponent {
37
37
  constructor() {
38
38
  super(...arguments);
39
+ this._isAsyncItem = false;
39
40
  this.state = {
40
41
  isFocused: false,
41
42
  };
@@ -54,6 +55,33 @@ class GridItem extends React.PureComponent {
54
55
  this.controller = null;
55
56
  });
56
57
  };
58
+ this.onMountChange = (isMounted) => {
59
+ var _a, _b, _c, _d, _e, _f;
60
+ if (isMounted) {
61
+ this._inited = true;
62
+ (_b = (_a = this.props).onItemMountChange) === null || _b === void 0 ? void 0 : _b.call(_a, this.props.item, {
63
+ isAsync: this._isAsyncItem,
64
+ isMounted: isMounted,
65
+ });
66
+ if (!this._isAsyncItem) {
67
+ (_d = (_c = this.props).onItemRender) === null || _d === void 0 ? void 0 : _d.call(_c, this.props.item);
68
+ }
69
+ }
70
+ else {
71
+ (_f = (_e = this.props).onItemMountChange) === null || _f === void 0 ? void 0 : _f.call(_e, this.props.item, {
72
+ isAsync: this._isAsyncItem,
73
+ isMounted: isMounted,
74
+ });
75
+ }
76
+ };
77
+ this.onBeforeLoad = () => {
78
+ this._isAsyncItem = true;
79
+ return this.onLoad;
80
+ };
81
+ this.onLoad = () => {
82
+ var _a, _b;
83
+ (_b = (_a = this.props).onItemRender) === null || _b === void 0 ? void 0 : _b.call(_a, this.props.item);
84
+ };
57
85
  }
58
86
  renderOverlay() {
59
87
  const { isPlaceholder } = this.props;
@@ -69,7 +97,7 @@ class GridItem extends React.PureComponent {
69
97
  render() {
70
98
  // из-за бага, что Grid Items unmounts при изменении static, isDraggable, isResaizable
71
99
  // https://github.com/STRML/react-grid-layout/issues/721
72
- const { style, onMouseDown, onMouseUp, onTouchEnd, onTouchStart, children, className, isDragging, noOverlay, focusable, withCustomHandle, isPlaceholder, } = this.props;
100
+ const { style, onMouseDown, onMouseUp, onTouchEnd, onTouchStart, children, className, isDragging, noOverlay, focusable, withCustomHandle, isPlaceholder = false, } = this.props;
73
101
  const { editMode } = this.context;
74
102
  const width = Number.parseInt(style.width, 10);
75
103
  const height = Number.parseInt(style.height, 10);
@@ -94,7 +122,7 @@ class GridItem extends React.PureComponent {
94
122
  }
95
123
  : {})),
96
124
  React.createElement("div", { className: b('item', { editMode: editMode && !_editActive && !noOverlay }) },
97
- React.createElement(Item, { id: this.props.id, item: this.props.item, shouldItemUpdate: !isDragging, width: width, height: height, transform: transform, isPlaceholder: isPlaceholder, adjustWidgetLayout: this.props.adjustWidgetLayout, layout: this.props.layout, forwardedPluginRef: this.props.forwardedPluginRef })),
125
+ React.createElement(Item, { id: this.props.id, item: this.props.item, shouldItemUpdate: !isDragging, width: width, height: height, transform: transform, isPlaceholder: isPlaceholder, adjustWidgetLayout: this.props.adjustWidgetLayout, layout: this.props.layout, forwardedPluginRef: this.props.forwardedPluginRef, onMountChange: this.onMountChange, onBeforeLoad: this.onBeforeLoad })),
98
126
  !noOverlay && this.renderOverlay(),
99
127
  preparedChildren));
100
128
  }
@@ -284,7 +284,7 @@ class GridLayout extends React.PureComponent {
284
284
  return (React.createElement(GridItem, { key: id, id: id, item: { id, type, data: {} }, layout: temporaryLayout.data, adjustWidgetLayout: this.adjustWidgetLayout, isDragging: this.state.isDragging, isPlaceholder: true, noOverlay: noOverlay, withCustomHandle: Boolean(draggableHandleClassName) }));
285
285
  }
286
286
  renderGroup(group, renderLayout, renderItems, offset = 0, groupGridProperties) {
287
- const { registerManager, editMode, noOverlay, focusable, draggableHandleClassName, outerDnDEnable, } = this.context;
287
+ const { registerManager, editMode, noOverlay, focusable, draggableHandleClassName, outerDnDEnable, onItemMountChange, onItemRender, } = this.context;
288
288
  const { currentDraggingElement, draggedOverGroup } = this.state;
289
289
  const properties = groupGridProperties
290
290
  ? groupGridProperties(Object.assign({}, registerManager.gridLayout))
@@ -307,7 +307,7 @@ class GridLayout extends React.PureComponent {
307
307
  }
308
308
  : null), { draggableCancel: `.${OVERLAY_CONTROLS_CLASS_NAME}` }),
309
309
  renderItems.map((item, i) => {
310
- 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) }));
310
+ 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), onItemMountChange: onItemMountChange, onItemRender: onItemRender }));
311
311
  }),
312
312
  this.renderTemporaryPlaceholder()));
313
313
  }
@@ -61,7 +61,9 @@ declare const _default: {
61
61
  height: PropTypes.Requireable<number>;
62
62
  transform: PropTypes.Requireable<string>;
63
63
  isPlaceholder: PropTypes.Requireable<boolean>;
64
+ onBeforeLoad: PropTypes.Requireable<(...args: any[]) => any>;
64
65
  forwardedPluginRef: PropTypes.Requireable<any>;
66
+ onMountChange: PropTypes.Requireable<(...args: any[]) => any>;
65
67
  };
66
68
  contextType: React.Context<any>;
67
69
  };
@@ -5,9 +5,18 @@ import { cn } from '../../utils/cn';
5
5
  import './Item.css';
6
6
  const b = cn('dashkit-item');
7
7
  // TODO: getDerivedStateFromError и заглушка с ошибкой
8
- const Item = ({ registerManager, rendererProps, type, isPlaceholder, forwardedPluginRef }) => {
8
+ const Item = ({ registerManager, rendererProps, type, isPlaceholder, forwardedPluginRef, onMountChange, }) => {
9
9
  var _a, _b;
10
- if (!registerManager.check(type)) {
10
+ const isRegisteredType = registerManager.check(type);
11
+ React.useLayoutEffect(() => {
12
+ if (isRegisteredType && !isPlaceholder) {
13
+ onMountChange === null || onMountChange === void 0 ? void 0 : onMountChange(true);
14
+ return () => {
15
+ onMountChange === null || onMountChange === void 0 ? void 0 : onMountChange(false);
16
+ };
17
+ }
18
+ }, []);
19
+ if (!isRegisteredType) {
11
20
  console.warn(`type [${type}] не зарегистрирован`);
12
21
  return null;
13
22
  }
@@ -23,5 +32,7 @@ Item.propTypes = {
23
32
  registerManager: PropTypes.object,
24
33
  type: PropTypes.string,
25
34
  isPlaceholder: PropTypes.bool,
35
+ onMountChange: PropTypes.func,
36
+ onBeforeLoad: PropTypes.func,
26
37
  };
27
38
  export default prepareItem(Item);
@@ -61,7 +61,9 @@ export function prepareItem(Component: any): {
61
61
  height: PropTypes.Requireable<number>;
62
62
  transform: PropTypes.Requireable<string>;
63
63
  isPlaceholder: PropTypes.Requireable<boolean>;
64
+ onBeforeLoad: PropTypes.Requireable<(...args: any[]) => any>;
64
65
  forwardedPluginRef: PropTypes.Requireable<any>;
66
+ onMountChange: PropTypes.Requireable<(...args: any[]) => any>;
65
67
  };
66
68
  contextType: React.Context<any>;
67
69
  };
@@ -11,7 +11,7 @@ export function prepareItem(Component) {
11
11
  };
12
12
  this._currentRenderProps = {};
13
13
  this.getRenderProps = () => {
14
- const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder } = this.props;
14
+ const { id, width, height, item, adjustWidgetLayout, layout, isPlaceholder, onBeforeLoad, } = this.props;
15
15
  const { itemsState, itemsParams, registerManager, settings, context, editMode } = this.context;
16
16
  const { data, defaults, namespace } = item;
17
17
  const rendererProps = {
@@ -20,6 +20,7 @@ export function prepareItem(Component) {
20
20
  params: itemsParams[id],
21
21
  state: itemsState[id],
22
22
  onStateAndParamsChange: this._onStateAndParamsChange,
23
+ onBeforeLoad,
23
24
  width,
24
25
  height,
25
26
  id,
@@ -51,10 +52,10 @@ export function prepareItem(Component) {
51
52
  return true;
52
53
  }
53
54
  render() {
54
- const { item, isPlaceholder } = this.props;
55
+ const { item, isPlaceholder, forwardedPluginRef, onMountChange } = this.props;
55
56
  const { registerManager } = this.context;
56
57
  const { type } = item;
57
- return (React.createElement(Component, { forwardedPluginRef: this.props.forwardedPluginRef, rendererProps: this.getRenderProps(), registerManager: registerManager, type: type, isPlaceholder: isPlaceholder }));
58
+ return (React.createElement(Component, { forwardedPluginRef: forwardedPluginRef, rendererProps: this.getRenderProps(), registerManager: registerManager, type: type, isPlaceholder: isPlaceholder, onMountChange: onMountChange }));
58
59
  }
59
60
  },
60
61
  _a.propTypes = {
@@ -67,7 +68,9 @@ export function prepareItem(Component) {
67
68
  height: PropTypes.number,
68
69
  transform: PropTypes.string,
69
70
  isPlaceholder: PropTypes.bool,
71
+ onBeforeLoad: PropTypes.func,
70
72
  forwardedPluginRef: PropTypes.any,
73
+ onMountChange: PropTypes.func,
71
74
  },
72
75
  _a.contextType = DashKitContext,
73
76
  _a;
@@ -317,6 +317,8 @@ function useMemoStateContext(props) {
317
317
  onItemStateAndParamsChange,
318
318
  onDrop,
319
319
  onDropDragOver,
320
+ onItemMountChange: props.onItemMountChange,
321
+ onItemRender: props.onItemRender,
320
322
  layoutChange: onLayoutChange,
321
323
  getItemsMeta,
322
324
  reloadItems,
@@ -344,6 +346,8 @@ function useMemoStateContext(props) {
344
346
  onDrop,
345
347
  onDropDragOver,
346
348
  onLayoutChange,
349
+ props.onItemMountChange,
350
+ props.onItemRender,
347
351
  getItemsMeta,
348
352
  reloadItems,
349
353
  memorizeOriginalLayout,
@@ -7,8 +7,8 @@ export declare const useCalcPropsLayout: (config: Config, registerManager: Regis
7
7
  x: number;
8
8
  y: number;
9
9
  parent?: string | undefined;
10
- minW: number;
11
- minH: number;
10
+ minW?: number | undefined;
11
+ minH?: number | undefined;
12
12
  maxW?: number | undefined;
13
13
  maxH?: number | undefined;
14
14
  }[];
@@ -16,6 +16,7 @@ export interface PluginTextProps extends PluginWidgetProps {
16
16
  data: {
17
17
  text: string;
18
18
  } & PluginWidgetProps['data'];
19
+ onBeforeRender: () => () => void;
19
20
  }
20
21
  interface PluginTextState {
21
22
  htmlText: string;
@@ -77,6 +77,7 @@ export class PluginText extends React.PureComponent {
77
77
  return;
78
78
  }
79
79
  this.setState({ status: LoadStatus.Pending });
80
+ const onLoadComplete = this.props.onBeforeLoad();
80
81
  try {
81
82
  let htmlText = '';
82
83
  if (this.state.text && this.state.text.trim()) {
@@ -97,6 +98,7 @@ export class PluginText extends React.PureComponent {
97
98
  }
98
99
  this.setState({ status: LoadStatus.Fail });
99
100
  }
101
+ onLoadComplete();
100
102
  }
101
103
  get withMarkdown() {
102
104
  return typeof this.props.apiHandler === 'function';
@@ -8,6 +8,7 @@ export interface PluginWidgetProps<T = StringParams> {
8
8
  params: T;
9
9
  state: ItemState;
10
10
  onStateAndParamsChange: (stateAndParams: ItemStateAndParams, options?: ItemStateAndParamsChangeOptions) => void;
11
+ onBeforeLoad: () => () => void;
11
12
  width: number;
12
13
  height: number;
13
14
  data: ConfigItem['data'];
@@ -186,6 +186,104 @@ beforeEach(() => {
186
186
  jest.spyOn(console, 'warn').mockImplementation(jest.fn());
187
187
  });
188
188
  describe('UpdateManager', () => {
189
+ describe('addItem', () => {
190
+ it('add new item', () => {
191
+ expect(UpdateManager.addItem({
192
+ item: {
193
+ data: {},
194
+ type: 'widget',
195
+ },
196
+ namespace: 'default',
197
+ layout: { h: 4, w: 4, x: 0, y: 0 },
198
+ config: config,
199
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
200
+ ...config.items,
201
+ {
202
+ id: 'EZ',
203
+ data: {},
204
+ type: 'widget',
205
+ namespace: 'default',
206
+ },
207
+ ], layout: [...config.layout, { h: 4, w: 4, x: 0, y: 32, i: 'EZ' }] }));
208
+ });
209
+ it('add new item excludeIds', () => {
210
+ expect(UpdateManager.addItem({
211
+ item: {
212
+ data: {},
213
+ type: 'widget',
214
+ },
215
+ namespace: 'default',
216
+ layout: { h: 4, w: 4, x: 0, y: 0 },
217
+ config: config,
218
+ options: {
219
+ excludeIds: ['EZ'],
220
+ },
221
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 16, items: [
222
+ ...config.items,
223
+ {
224
+ id: '1n',
225
+ data: {},
226
+ type: 'widget',
227
+ namespace: 'default',
228
+ },
229
+ ], layout: [...config.layout, { h: 4, w: 4, x: 0, y: 32, i: '1n' }] }));
230
+ });
231
+ it('add new item with layout update', () => {
232
+ const updateLayout = [
233
+ { h: 4, i: 'al', w: 4, x: 8, y: 4 },
234
+ { h: 16, i: 'Q8', w: 12, x: 12, y: 4 },
235
+ { h: 2, i: 'L5', w: 8, x: 0, y: 6 },
236
+ { h: 2, i: 'C8', w: 8, x: 10, y: 34 },
237
+ { h: 2, i: 'lko', w: 8, x: 0, y: 16 },
238
+ { h: 2, i: 'qY', w: 8, x: 0, y: 28 },
239
+ ];
240
+ expect(UpdateManager.addItem({
241
+ item: {
242
+ data: {},
243
+ type: 'widget',
244
+ },
245
+ namespace: 'default',
246
+ layout: { h: 4, w: 12, x: 0, y: 0 },
247
+ config: config,
248
+ options: {
249
+ updateLayout,
250
+ },
251
+ })).toEqual(Object.assign(Object.assign({}, config), { salt: '0.09852189776033704', counter: 15, items: [
252
+ ...config.items,
253
+ {
254
+ id: 'EZ',
255
+ data: {},
256
+ type: 'widget',
257
+ namespace: 'default',
258
+ },
259
+ ], layout: [...updateLayout, { h: 4, w: 12, x: 0, y: 0, i: 'EZ' }] }));
260
+ });
261
+ });
262
+ describe('removeItem', () => {
263
+ it('remove item by id', () => {
264
+ const deleteId = 'lko';
265
+ expect(UpdateManager.removeItem({ id: deleteId, config, itemsStateAndParams: {} })).toEqual({
266
+ config: Object.assign(Object.assign({}, config), { items: config.items.filter(({ id }) => id !== deleteId), layout: config.layout.filter(({ i }) => i !== deleteId) }),
267
+ itemsStateAndParams: {
268
+ __meta__: { queue: [], version: 2 },
269
+ },
270
+ });
271
+ });
272
+ it('remove item when items and layout not sorted', () => {
273
+ const deleteId = 'lko';
274
+ const configCopy = Object.assign(Object.assign({}, config), { layout: config.layout.reverse() });
275
+ expect(UpdateManager.removeItem({
276
+ id: deleteId,
277
+ config: configCopy,
278
+ itemsStateAndParams: {},
279
+ })).toEqual({
280
+ config: Object.assign(Object.assign({}, config), { items: config.items.filter(({ id }) => id !== deleteId), layout: config.layout.filter(({ i }) => i !== deleteId) }),
281
+ itemsStateAndParams: {
282
+ __meta__: { queue: [], version: 2 },
283
+ },
284
+ });
285
+ });
286
+ });
189
287
  describe('changeStateAndParams', () => {
190
288
  it('control adds params to empty itemsStateAndParams: common control', () => {
191
289
  expect(UpdateManager.changeStateAndParams({
@@ -5,8 +5,8 @@ interface RegisterManagerDefaultLayout {
5
5
  y: number;
6
6
  w: number;
7
7
  h: number;
8
- minW: number;
9
- minH: number;
8
+ minW?: number;
9
+ minH?: number;
10
10
  }
11
11
  export type RegisterManagerPluginLayout = RegisterManagerDefaultLayout & PluginDefaultLayout;
12
12
  export interface RegisterManagerPlugin extends Omit<Plugin, 'defaultLayout'> {
@@ -26,13 +26,13 @@ export declare class UpdateManager {
26
26
  namespace: string;
27
27
  layout: RegisterManagerPluginLayout;
28
28
  config: Config;
29
- options: SetNewItemOptions;
29
+ options?: SetNewItemOptions;
30
30
  }): Config;
31
31
  static editItem({ item, namespace, config, options, }: {
32
32
  item: ConfigItem;
33
33
  namespace: string;
34
34
  config: Config;
35
- options: SetItemOptions;
35
+ options?: SetItemOptions;
36
36
  }): Config;
37
37
  static removeItem({ id, config, itemsStateAndParams }: RemoveItemArg): {
38
38
  config: Config;
@@ -222,7 +222,7 @@ function changeGroupParams({ groupItemIds, initiatorId, initiatorItem, itemsStat
222
222
  return update(itemsStateAndParams, obj);
223
223
  }
224
224
  export class UpdateManager {
225
- static addItem({ item, namespace = DEFAULT_NAMESPACE, layout, config, options, }) {
225
+ static addItem({ item, namespace = DEFAULT_NAMESPACE, layout, config, options = {}, }) {
226
226
  const salt = config.salt;
227
227
  const newItemData = getNewItemData({ item, config, salt, counter: config.counter, options });
228
228
  let counter = newItemData.counter;
@@ -255,7 +255,7 @@ export class UpdateManager {
255
255
  });
256
256
  }
257
257
  }
258
- static editItem({ item, namespace = DEFAULT_NAMESPACE, config, options, }) {
258
+ static editItem({ item, namespace = DEFAULT_NAMESPACE, config, options = {}, }) {
259
259
  const itemIndex = config.items.findIndex(({ id }) => item.id === id);
260
260
  const { counter, data } = getNewItemData({
261
261
  item,
@@ -274,6 +274,7 @@ export class UpdateManager {
274
274
  return removeItemVersion1({ id, config, itemsStateAndParams });
275
275
  }
276
276
  const itemIndex = config.items.findIndex((item) => item.id === id);
277
+ const layoutIndex = config.layout.findIndex((item) => item.i === id);
277
278
  const item = config.items[itemIndex];
278
279
  let itemIds = [id];
279
280
  if (isItemWithTabs(item)) {
@@ -289,7 +290,7 @@ export class UpdateManager {
289
290
  $splice: [[itemIndex, 1]],
290
291
  },
291
292
  layout: {
292
- $splice: [[itemIndex, 1]],
293
+ $splice: [[layoutIndex, 1]],
293
294
  },
294
295
  connections: {
295
296
  $set: connections,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dashkit",
3
- "version": "8.9.1",
3
+ "version": "8.10.1",
4
4
  "description": "Library for rendering dashboard grid layout",
5
5
  "license": "MIT",
6
6
  "repository": {