@gravity-ui/dashkit 10.2.0 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +95 -19
  2. package/build/cjs/components/DashKit/DashKit.d.ts +11 -1
  3. package/build/cjs/components/DashKit/DashKit.js +55 -7
  4. package/build/cjs/components/DashKit/DashKit.js.map +1 -1
  5. package/build/cjs/components/DashKit/__tests__/controlled-layout.test.d.ts +4 -0
  6. package/build/cjs/components/DashKit/__tests__/controlled-layout.test.js +196 -0
  7. package/build/cjs/components/DashKit/__tests__/controlled-layout.test.js.map +1 -0
  8. package/build/cjs/components/DashKit/__tests__/events.test.d.ts +1 -0
  9. package/build/cjs/components/DashKit/__tests__/events.test.js +227 -0
  10. package/build/cjs/components/DashKit/__tests__/events.test.js.map +1 -0
  11. package/build/cjs/components/DashKitView/DashKitView.d.ts +1 -1
  12. package/build/cjs/components/DashKitView/DashKitView.js +1 -3
  13. package/build/cjs/components/DashKitView/DashKitView.js.map +1 -1
  14. package/build/cjs/components/GridLayout/GroupLayout.js +2 -2
  15. package/build/cjs/components/GridLayout/GroupLayout.js.map +1 -1
  16. package/build/cjs/components/GridLayout/ReactGridLayout.d.ts +1 -0
  17. package/build/cjs/components/GridLayout/ReactGridLayout.js +37 -0
  18. package/build/cjs/components/GridLayout/ReactGridLayout.js.map +1 -1
  19. package/build/cjs/components/OverlayControls/OverlayControls.js +1 -1
  20. package/build/cjs/components/OverlayControls/OverlayControls.js.map +1 -1
  21. package/build/cjs/context/DashKitContext.d.ts +1 -0
  22. package/build/cjs/context/DashKitContext.js.map +1 -1
  23. package/build/cjs/context/DashkitOverlayControlsContext.d.ts +1 -1
  24. package/build/cjs/context/DashkitOverlayControlsContext.js +2 -2
  25. package/build/cjs/context/DashkitOverlayControlsContext.js.map +1 -1
  26. package/build/cjs/hocs/withContext.d.ts +2 -2
  27. package/build/cjs/hocs/withContext.js +61 -31
  28. package/build/cjs/hocs/withContext.js.map +1 -1
  29. package/build/cjs/package.json +1 -1
  30. package/build/cjs/plugins/Text/Text.js +1 -1
  31. package/build/cjs/plugins/Text/Text.js.map +1 -1
  32. package/build/cjs/shared/types/config.d.ts +2 -0
  33. package/build/cjs/shared/types/config.js +9 -0
  34. package/build/cjs/shared/types/config.js.map +1 -1
  35. package/build/cjs/typings/events.d.ts +34 -0
  36. package/build/cjs/typings/events.js +3 -0
  37. package/build/cjs/typings/events.js.map +1 -0
  38. package/build/cjs/typings/index.d.ts +1 -0
  39. package/build/cjs/typings/index.js +1 -0
  40. package/build/cjs/typings/index.js.map +1 -1
  41. package/build/cjs/utils/__tests__/get-layout-patches.test.d.ts +1 -0
  42. package/build/cjs/utils/__tests__/get-layout-patches.test.js +51 -0
  43. package/build/cjs/utils/__tests__/get-layout-patches.test.js.map +1 -0
  44. package/build/cjs/utils/enrichLayoutWithDefaults.d.ts +5 -0
  45. package/build/cjs/utils/enrichLayoutWithDefaults.js +29 -0
  46. package/build/cjs/utils/enrichLayoutWithDefaults.js.map +1 -0
  47. package/build/cjs/utils/events.d.ts +10 -0
  48. package/build/cjs/utils/events.js +34 -0
  49. package/build/cjs/utils/events.js.map +1 -0
  50. package/build/cjs/utils/get-layout-patches.d.ts +3 -0
  51. package/build/cjs/utils/get-layout-patches.js +23 -0
  52. package/build/cjs/utils/get-layout-patches.js.map +1 -0
  53. package/build/cjs/utils/index.d.ts +3 -0
  54. package/build/cjs/utils/index.js +3 -0
  55. package/build/cjs/utils/index.js.map +1 -1
  56. package/build/cjs/utils/update-manager.js +2 -2
  57. package/build/cjs/utils/update-manager.js.map +1 -1
  58. package/build/esm/components/DashKit/DashKit.d.ts +11 -1
  59. package/build/esm/components/DashKit/DashKit.js +54 -6
  60. package/build/esm/components/DashKit/DashKit.js.map +1 -1
  61. package/build/esm/components/DashKit/__tests__/controlled-layout.test.d.ts +4 -0
  62. package/build/esm/components/DashKit/__tests__/controlled-layout.test.js +193 -0
  63. package/build/esm/components/DashKit/__tests__/controlled-layout.test.js.map +1 -0
  64. package/build/esm/components/DashKit/__tests__/events.test.d.ts +1 -0
  65. package/build/esm/components/DashKit/__tests__/events.test.js +225 -0
  66. package/build/esm/components/DashKit/__tests__/events.test.js.map +1 -0
  67. package/build/esm/components/DashKitView/DashKitView.d.ts +1 -1
  68. package/build/esm/components/DashKitView/DashKitView.js +1 -3
  69. package/build/esm/components/DashKitView/DashKitView.js.map +1 -1
  70. package/build/esm/components/GridLayout/GroupLayout.js +2 -2
  71. package/build/esm/components/GridLayout/GroupLayout.js.map +1 -1
  72. package/build/esm/components/GridLayout/ReactGridLayout.d.ts +1 -0
  73. package/build/esm/components/GridLayout/ReactGridLayout.js +37 -0
  74. package/build/esm/components/GridLayout/ReactGridLayout.js.map +1 -1
  75. package/build/esm/components/OverlayControls/OverlayControls.js +2 -2
  76. package/build/esm/components/OverlayControls/OverlayControls.js.map +1 -1
  77. package/build/esm/context/DashKitContext.d.ts +1 -0
  78. package/build/esm/context/DashKitContext.js.map +1 -1
  79. package/build/esm/context/DashkitOverlayControlsContext.d.ts +1 -1
  80. package/build/esm/context/DashkitOverlayControlsContext.js +1 -1
  81. package/build/esm/context/DashkitOverlayControlsContext.js.map +1 -1
  82. package/build/esm/hocs/withContext.d.ts +2 -2
  83. package/build/esm/hocs/withContext.js +64 -34
  84. package/build/esm/hocs/withContext.js.map +1 -1
  85. package/build/esm/package.json +1 -1
  86. package/build/esm/plugins/Text/Text.js +1 -1
  87. package/build/esm/plugins/Text/Text.js.map +1 -1
  88. package/build/esm/shared/types/config.d.ts +2 -0
  89. package/build/esm/shared/types/config.js +8 -1
  90. package/build/esm/shared/types/config.js.map +1 -1
  91. package/build/esm/typings/events.d.ts +34 -0
  92. package/build/esm/typings/events.js +2 -0
  93. package/build/esm/typings/events.js.map +1 -0
  94. package/build/esm/typings/index.d.ts +1 -0
  95. package/build/esm/typings/index.js +1 -0
  96. package/build/esm/typings/index.js.map +1 -1
  97. package/build/esm/utils/__tests__/get-layout-patches.test.d.ts +1 -0
  98. package/build/esm/utils/__tests__/get-layout-patches.test.js +49 -0
  99. package/build/esm/utils/__tests__/get-layout-patches.test.js.map +1 -0
  100. package/build/esm/utils/enrichLayoutWithDefaults.d.ts +5 -0
  101. package/build/esm/utils/enrichLayoutWithDefaults.js +25 -0
  102. package/build/esm/utils/enrichLayoutWithDefaults.js.map +1 -0
  103. package/build/esm/utils/events.d.ts +10 -0
  104. package/build/esm/utils/events.js +30 -0
  105. package/build/esm/utils/events.js.map +1 -0
  106. package/build/esm/utils/get-layout-patches.d.ts +3 -0
  107. package/build/esm/utils/get-layout-patches.js +19 -0
  108. package/build/esm/utils/get-layout-patches.js.map +1 -0
  109. package/build/esm/utils/index.d.ts +3 -0
  110. package/build/esm/utils/index.js +3 -0
  111. package/build/esm/utils/index.js.map +1 -1
  112. package/build/esm/utils/update-manager.js +3 -3
  113. package/build/esm/utils/update-manager.js.map +1 -1
  114. package/package.json +4 -1
  115. package/build/cjs/hooks/useCalcLayout.d.ts +0 -3
  116. package/build/cjs/hooks/useCalcLayout.js +0 -40
  117. package/build/cjs/hooks/useCalcLayout.js.map +0 -1
  118. package/build/esm/hooks/useCalcLayout.d.ts +0 -3
  119. package/build/esm/hooks/useCalcLayout.js +0 -35
  120. package/build/esm/hooks/useCalcLayout.js.map +0 -1
@@ -0,0 +1,193 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ jest.mock('@gravity-ui/icons', () => ({
5
+ Ellipsis: 'ellipsis',
6
+ Gear: 'gear',
7
+ Xmark: 'xmark',
8
+ }));
9
+ jest.mock('@gravity-ui/uikit', () => {
10
+ const ReactMock = require('react');
11
+ return {
12
+ Button: ({ children }) => ReactMock.createElement('button', null, children),
13
+ DropdownMenu: () => null,
14
+ Icon: () => null,
15
+ };
16
+ });
17
+ jest.mock('../../GridLayout/GridLayout', () => {
18
+ const ReactMock = require('react');
19
+ return ReactMock.forwardRef(() => null);
20
+ });
21
+ import React from 'react';
22
+ import { act, render } from '@testing-library/react';
23
+ import { DashKitContext } from "../../../context/index.js";
24
+ import { withContext } from "../../../hocs/withContext.js";
25
+ import { RegisterManager } from "../../../utils/index.js";
26
+ import { DashKit, _emitSymbol } from "../DashKit.js";
27
+ const PLUGIN_TYPE = 'controlled-layout-test';
28
+ const createConfig = (x) => ({
29
+ salt: 'test',
30
+ counter: 1,
31
+ items: [{ id: 'item1', type: PLUGIN_TYPE, namespace: 'default', data: {} }],
32
+ layout: [{ i: 'item1', x, y: 0, w: 2, h: 2 }],
33
+ aliases: {},
34
+ connections: [],
35
+ });
36
+ const createProps = ({ config, emitDashKitEvent, onChange = jest.fn(), }) => {
37
+ const registerManager = new RegisterManager();
38
+ registerManager.registerPlugin({
39
+ type: PLUGIN_TYPE,
40
+ defaultLayout: { x: 0, y: 0, w: 2, h: 2 },
41
+ renderer: () => null,
42
+ });
43
+ return {
44
+ config,
45
+ editMode: true,
46
+ layout: config.layout,
47
+ registerManager,
48
+ forwardedMetaRef: React.createRef(),
49
+ emitDashKitEvent,
50
+ itemsStateAndParams: {},
51
+ defaultGlobalParams: {},
52
+ globalParams: {},
53
+ context: {},
54
+ settings: {
55
+ autoupdateInterval: 0,
56
+ silentLoading: false,
57
+ },
58
+ noOverlay: false,
59
+ focusable: false,
60
+ onItemEdit: jest.fn(),
61
+ onChange,
62
+ onDrop: jest.fn(),
63
+ };
64
+ };
65
+ let captured = {};
66
+ const ContextCapture = () => {
67
+ captured = React.useContext(DashKitContext);
68
+ return null;
69
+ };
70
+ const TestComponent = withContext(ContextCapture);
71
+ describe('DashKit controlled layout strategy', () => {
72
+ beforeEach(() => {
73
+ captured = {};
74
+ });
75
+ it('Case 1: keeps internal layout after change event without config update, then applies external config update', () => {
76
+ const initialConfig = createConfig(0);
77
+ const nextInternalLayout = [{ i: 'item1', x: 1, y: 0, w: 2, h: 2 }];
78
+ const externalConfig = createConfig(3);
79
+ const dashkit = new DashKit(DashKit.defaultProps);
80
+ const onChange = jest.fn();
81
+ const onEventChange = jest.fn();
82
+ dashkit.on('change', onEventChange);
83
+ const { rerender } = render(React.createElement(TestComponent, Object.assign({}, createProps({
84
+ config: initialConfig,
85
+ emitDashKitEvent: dashkit[_emitSymbol],
86
+ onChange,
87
+ }))));
88
+ // Layout is now enriched with plugin defaults (minW, minH, etc)
89
+ expect(captured.layout).toMatchObject(initialConfig.layout);
90
+ expect(captured.layout[0]).toHaveProperty('minW');
91
+ expect(captured.layout[0]).toHaveProperty('minH');
92
+ act(() => {
93
+ captured.layoutChange(nextInternalLayout);
94
+ });
95
+ // Verify that change event was fired with correct layout
96
+ expect(onEventChange).toHaveBeenCalledTimes(1);
97
+ expect(onEventChange.mock.calls[0][0].layout).toEqual(nextInternalLayout);
98
+ expect(onChange).toHaveBeenCalledTimes(1);
99
+ // Now apply external config update
100
+ act(() => {
101
+ rerender(React.createElement(TestComponent, Object.assign({}, createProps({
102
+ config: externalConfig,
103
+ emitDashKitEvent: dashkit[_emitSymbol],
104
+ onChange,
105
+ }))));
106
+ });
107
+ // External props should win and layout should be updated
108
+ expect(captured.layout).toMatchObject(externalConfig.layout);
109
+ });
110
+ it('Case 2: uses on(change) without updating props - internal state persists', () => {
111
+ const initialConfig = createConfig(0);
112
+ const nextInternalLayout = [{ i: 'item1', x: 5, y: 0, w: 2, h: 2 }];
113
+ const dashkit = new DashKit(DashKit.defaultProps);
114
+ const onChange = jest.fn();
115
+ const onEventChange = jest.fn();
116
+ const capturedLayouts = [];
117
+ // Listen to change event but DON'T update props
118
+ dashkit.on('change', (event) => {
119
+ capturedLayouts.push(event.layout);
120
+ onEventChange(event);
121
+ });
122
+ const { rerender } = render(React.createElement(TestComponent, Object.assign({}, createProps({
123
+ config: initialConfig,
124
+ emitDashKitEvent: dashkit[_emitSymbol],
125
+ onChange,
126
+ }))));
127
+ // Initial layout should be enriched
128
+ expect(captured.layout).toMatchObject(initialConfig.layout);
129
+ // Trigger layout change
130
+ act(() => {
131
+ captured.layoutChange(nextInternalLayout);
132
+ });
133
+ // Event should fire with new layout
134
+ expect(onEventChange).toHaveBeenCalledTimes(1);
135
+ expect(capturedLayouts[0]).toEqual(nextInternalLayout);
136
+ // onChange callback fires (consumer COULD update props here, but doesn't in this case)
137
+ expect(onChange).toHaveBeenCalledTimes(1);
138
+ // Props remain unchanged - simulating consumer NOT updating props
139
+ act(() => {
140
+ rerender(React.createElement(TestComponent, Object.assign({}, createProps({
141
+ config: initialConfig,
142
+ emitDashKitEvent: dashkit[_emitSymbol],
143
+ onChange,
144
+ }))));
145
+ });
146
+ // Layout should still reflect the change (internal state wins)
147
+ // Note: we verify the event fired correctly
148
+ expect(onEventChange).toHaveBeenCalledTimes(1);
149
+ expect(capturedLayouts[0]).toMatchObject(nextInternalLayout);
150
+ });
151
+ it('Case 3: alternates between props.layout updates and on(change) events', () => {
152
+ const config1 = createConfig(0);
153
+ const config2 = createConfig(2);
154
+ const internalChange = [{ i: 'item1', x: 5, y: 0, w: 2, h: 2 }];
155
+ const config3 = createConfig(8);
156
+ const dashkit = new DashKit(DashKit.defaultProps);
157
+ const onChange = jest.fn();
158
+ const onEventChange = jest.fn();
159
+ dashkit.on('change', onEventChange);
160
+ const { rerender } = render(React.createElement(TestComponent, Object.assign({}, createProps({
161
+ config: config1,
162
+ emitDashKitEvent: dashkit[_emitSymbol],
163
+ onChange,
164
+ }))));
165
+ // Start with config1
166
+ expect(captured.layout).toMatchObject(config1.layout);
167
+ // Update via props (external update)
168
+ act(() => {
169
+ rerender(React.createElement(TestComponent, Object.assign({}, createProps({
170
+ config: config2,
171
+ emitDashKitEvent: dashkit[_emitSymbol],
172
+ onChange,
173
+ }))));
174
+ });
175
+ expect(captured.layout).toMatchObject(config2.layout);
176
+ // Update via internal change (user drag)
177
+ act(() => {
178
+ captured.layoutChange(internalChange);
179
+ });
180
+ expect(onEventChange).toHaveBeenCalledTimes(1);
181
+ expect(onEventChange.mock.calls[0][0].layout).toEqual(internalChange);
182
+ // Update via props again (external update wins)
183
+ act(() => {
184
+ rerender(React.createElement(TestComponent, Object.assign({}, createProps({
185
+ config: config3,
186
+ emitDashKitEvent: dashkit[_emitSymbol],
187
+ onChange,
188
+ }))));
189
+ });
190
+ expect(captured.layout).toMatchObject(config3.layout);
191
+ });
192
+ });
193
+ //# sourceMappingURL=controlled-layout.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controlled-layout.test.js","sourceRoot":"../../../../../src","sources":["components/DashKit/__tests__/controlled-layout.test.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * @jest-environment jsdom\n */\n\njest.mock('@gravity-ui/icons', () => ({\n Ellipsis: 'ellipsis',\n Gear: 'gear',\n Xmark: 'xmark',\n}));\n\njest.mock('@gravity-ui/uikit', () => {\n const ReactMock = require('react');\n\n return {\n Button: ({children}: {children?: unknown}) =>\n ReactMock.createElement('button', null, children),\n DropdownMenu: () => null,\n Icon: () => null,\n };\n});\n\njest.mock('../../GridLayout/GridLayout', () => {\n const ReactMock = require('react');\n\n return ReactMock.forwardRef(() => null);\n});\n\nimport React from 'react';\n\nimport {act, render} from '@testing-library/react';\n\nimport {DashKitContext, type DashKitCtxShape} from '../../../context';\nimport {type DashKitWithContextProps, withContext} from '../../../hocs/withContext';\nimport type {Config} from '../../../shared';\nimport {RegisterManager} from '../../../utils';\nimport {DashKit, _emitSymbol} from '../DashKit';\n\nconst PLUGIN_TYPE = 'controlled-layout-test';\n\nconst createConfig = (x: number): Config => ({\n salt: 'test',\n counter: 1,\n items: [{id: 'item1', type: PLUGIN_TYPE, namespace: 'default', data: {}}],\n layout: [{i: 'item1', x, y: 0, w: 2, h: 2}],\n aliases: {},\n connections: [],\n});\n\nconst createProps = ({\n config,\n emitDashKitEvent,\n onChange = jest.fn(),\n}: {\n config: Config;\n emitDashKitEvent: DashKitWithContextProps['emitDashKitEvent'];\n onChange?: jest.Mock;\n}) => {\n const registerManager = new RegisterManager();\n registerManager.registerPlugin({\n type: PLUGIN_TYPE,\n defaultLayout: {x: 0, y: 0, w: 2, h: 2},\n renderer: () => null,\n });\n\n return {\n config,\n editMode: true,\n layout: config.layout,\n registerManager,\n forwardedMetaRef: React.createRef(),\n emitDashKitEvent,\n itemsStateAndParams: {},\n defaultGlobalParams: {},\n globalParams: {},\n context: {},\n settings: {\n autoupdateInterval: 0,\n silentLoading: false,\n },\n noOverlay: false,\n focusable: false,\n onItemEdit: jest.fn(),\n onChange,\n onDrop: jest.fn(),\n };\n};\n\nlet captured: DashKitCtxShape = {} as DashKitCtxShape;\n\nconst ContextCapture = () => {\n captured = React.useContext(DashKitContext);\n return null;\n};\n\nconst TestComponent = withContext(ContextCapture);\n\ndescribe('DashKit controlled layout strategy', () => {\n beforeEach(() => {\n captured = {} as DashKitCtxShape;\n });\n\n it('Case 1: keeps internal layout after change event without config update, then applies external config update', () => {\n const initialConfig = createConfig(0);\n const nextInternalLayout: Config['layout'] = [{i: 'item1', x: 1, y: 0, w: 2, h: 2}];\n const externalConfig = createConfig(3);\n const dashkit = new DashKit(\n DashKit.defaultProps as unknown as ConstructorParameters<typeof DashKit>[0],\n );\n const onChange = jest.fn();\n const onEventChange = jest.fn();\n\n dashkit.on('change', onEventChange);\n\n const {rerender} = render(\n <TestComponent\n {...createProps({\n config: initialConfig,\n emitDashKitEvent: dashkit[_emitSymbol],\n onChange,\n })}\n />,\n );\n\n // Layout is now enriched with plugin defaults (minW, minH, etc)\n expect(captured.layout).toMatchObject(initialConfig.layout);\n expect(captured.layout[0]).toHaveProperty('minW');\n expect(captured.layout[0]).toHaveProperty('minH');\n\n act(() => {\n captured.layoutChange(nextInternalLayout);\n });\n\n // Verify that change event was fired with correct layout\n expect(onEventChange).toHaveBeenCalledTimes(1);\n expect(onEventChange.mock.calls[0][0].layout).toEqual(nextInternalLayout);\n expect(onChange).toHaveBeenCalledTimes(1);\n\n // Now apply external config update\n act(() => {\n rerender(\n <TestComponent\n {...createProps({\n config: externalConfig,\n emitDashKitEvent: dashkit[_emitSymbol],\n onChange,\n })}\n />,\n );\n });\n\n // External props should win and layout should be updated\n expect(captured.layout).toMatchObject(externalConfig.layout);\n });\n\n it('Case 2: uses on(change) without updating props - internal state persists', () => {\n const initialConfig = createConfig(0);\n const nextInternalLayout: Config['layout'] = [{i: 'item1', x: 5, y: 0, w: 2, h: 2}];\n const dashkit = new DashKit(\n DashKit.defaultProps as unknown as ConstructorParameters<typeof DashKit>[0],\n );\n const onChange = jest.fn();\n const onEventChange = jest.fn();\n const capturedLayouts: Config['layout'][] = [];\n\n // Listen to change event but DON'T update props\n dashkit.on('change', (event) => {\n capturedLayouts.push(event.layout);\n onEventChange(event);\n });\n\n const {rerender} = render(\n <TestComponent\n {...createProps({\n config: initialConfig,\n emitDashKitEvent: dashkit[_emitSymbol],\n onChange,\n })}\n />,\n );\n\n // Initial layout should be enriched\n expect(captured.layout).toMatchObject(initialConfig.layout);\n\n // Trigger layout change\n act(() => {\n captured.layoutChange(nextInternalLayout);\n });\n\n // Event should fire with new layout\n expect(onEventChange).toHaveBeenCalledTimes(1);\n expect(capturedLayouts[0]).toEqual(nextInternalLayout);\n\n // onChange callback fires (consumer COULD update props here, but doesn't in this case)\n expect(onChange).toHaveBeenCalledTimes(1);\n\n // Props remain unchanged - simulating consumer NOT updating props\n act(() => {\n rerender(\n <TestComponent\n {...createProps({\n config: initialConfig,\n emitDashKitEvent: dashkit[_emitSymbol],\n onChange,\n })}\n />,\n );\n });\n\n // Layout should still reflect the change (internal state wins)\n // Note: we verify the event fired correctly\n expect(onEventChange).toHaveBeenCalledTimes(1);\n expect(capturedLayouts[0]).toMatchObject(nextInternalLayout);\n });\n\n it('Case 3: alternates between props.layout updates and on(change) events', () => {\n const config1 = createConfig(0);\n const config2 = createConfig(2);\n const internalChange: Config['layout'] = [{i: 'item1', x: 5, y: 0, w: 2, h: 2}];\n const config3 = createConfig(8);\n\n const dashkit = new DashKit(\n DashKit.defaultProps as unknown as ConstructorParameters<typeof DashKit>[0],\n );\n const onChange = jest.fn();\n const onEventChange = jest.fn();\n\n dashkit.on('change', onEventChange);\n\n const {rerender} = render(\n <TestComponent\n {...createProps({\n config: config1,\n emitDashKitEvent: dashkit[_emitSymbol],\n onChange,\n })}\n />,\n );\n\n // Start with config1\n expect(captured.layout).toMatchObject(config1.layout);\n\n // Update via props (external update)\n act(() => {\n rerender(\n <TestComponent\n {...createProps({\n config: config2,\n emitDashKitEvent: dashkit[_emitSymbol],\n onChange,\n })}\n />,\n );\n });\n expect(captured.layout).toMatchObject(config2.layout);\n\n // Update via internal change (user drag)\n act(() => {\n captured.layoutChange(internalChange);\n });\n expect(onEventChange).toHaveBeenCalledTimes(1);\n expect(onEventChange.mock.calls[0][0].layout).toEqual(internalChange);\n\n // Update via props again (external update wins)\n act(() => {\n rerender(\n <TestComponent\n {...createProps({\n config: config3,\n emitDashKitEvent: dashkit[_emitSymbol],\n onChange,\n })}\n />,\n );\n });\n expect(captured.layout).toMatchObject(config3.layout);\n });\n});\n"]}
@@ -0,0 +1,225 @@
1
+ jest.mock('@gravity-ui/icons', () => ({
2
+ Ellipsis: 'ellipsis',
3
+ Gear: 'gear',
4
+ Xmark: 'xmark',
5
+ }));
6
+ jest.mock('@gravity-ui/uikit', () => {
7
+ const ReactMock = require('react');
8
+ return {
9
+ Button: ({ children }) => ReactMock.createElement('button', null, children),
10
+ DropdownMenu: () => null,
11
+ Icon: () => null,
12
+ };
13
+ });
14
+ jest.mock('../../GridLayout/GridLayout', () => {
15
+ const ReactMock = require('react');
16
+ return ReactMock.forwardRef(() => null);
17
+ });
18
+ import { emitDashKitChangeEvent } from "../../../utils/index.js";
19
+ import { DashKit, _emitSymbol } from "../index.js";
20
+ const TEST_CONFIG = {
21
+ salt: 'test',
22
+ counter: 1,
23
+ items: [{ id: 'item1', type: 'test-event', namespace: 'default', data: {} }],
24
+ layout: [{ i: 'item1', x: 0, y: 0, w: 2, h: 2 }],
25
+ aliases: {},
26
+ connections: [],
27
+ };
28
+ const NEXT_LAYOUT = [{ i: 'item1', x: 1, y: 0, w: 2, h: 2 }];
29
+ const NEXT_CONFIG = Object.assign(Object.assign({}, TEST_CONFIG), { layout: NEXT_LAYOUT });
30
+ describe('DashKit change event — onChange gate', () => {
31
+ it('no handler: onChange fires with full config when layout changes', () => {
32
+ const onChange = jest.fn();
33
+ const emitDashKitEvent = jest.fn();
34
+ emitDashKitChangeEvent({
35
+ config: TEST_CONFIG,
36
+ newConfig: NEXT_CONFIG,
37
+ onChange,
38
+ emitDashKitEvent,
39
+ });
40
+ expect(emitDashKitEvent).toHaveBeenCalledTimes(1);
41
+ expect(onChange).toHaveBeenCalledTimes(1);
42
+ expect(onChange.mock.calls[0][0].config.layout).toEqual(NEXT_LAYOUT);
43
+ });
44
+ it('handler without preventDefault: onChange fires and handler receives correct event', () => {
45
+ const instance = new DashKit(DashKit.defaultProps);
46
+ const onChange = jest.fn();
47
+ const handler = jest.fn();
48
+ instance.on('change', handler);
49
+ emitDashKitChangeEvent({
50
+ config: TEST_CONFIG,
51
+ newConfig: NEXT_CONFIG,
52
+ onChange,
53
+ emitDashKitEvent: instance[_emitSymbol],
54
+ });
55
+ expect(handler).toHaveBeenCalledTimes(1);
56
+ expect(onChange).toHaveBeenCalledTimes(1);
57
+ const event = handler.mock.calls[0][0];
58
+ expect(event.layout).toEqual(NEXT_LAYOUT);
59
+ expect(event.previousLayout).toEqual(TEST_CONFIG.layout);
60
+ expect(event.defaultPrevented).toBe(false);
61
+ });
62
+ it('handler calls preventDefault: onChange does NOT fire; event carries correct patches', () => {
63
+ const instance = new DashKit(DashKit.defaultProps);
64
+ const onChange = jest.fn();
65
+ let capturedEvent;
66
+ instance.on('change', (e) => {
67
+ capturedEvent = e;
68
+ e.preventDefault();
69
+ });
70
+ emitDashKitChangeEvent({
71
+ config: TEST_CONFIG,
72
+ newConfig: NEXT_CONFIG,
73
+ onChange,
74
+ emitDashKitEvent: instance[_emitSymbol],
75
+ });
76
+ expect(onChange).not.toHaveBeenCalled();
77
+ expect(capturedEvent).toBeDefined();
78
+ if (capturedEvent) {
79
+ expect(capturedEvent.patches).toEqual([{ i: 'item1', x: 1, y: 0, w: 2, h: 2 }]);
80
+ expect(capturedEvent.layout).toEqual(NEXT_LAYOUT);
81
+ expect(capturedEvent.previousLayout).toEqual(TEST_CONFIG.layout);
82
+ expect(capturedEvent.defaultPrevented).toBe(true);
83
+ }
84
+ });
85
+ it('same layout: event and onChange do not fire', () => {
86
+ const onChange = jest.fn();
87
+ const emitDashKitEvent = jest.fn();
88
+ emitDashKitChangeEvent({
89
+ config: TEST_CONFIG,
90
+ newConfig: TEST_CONFIG,
91
+ onChange,
92
+ emitDashKitEvent,
93
+ });
94
+ expect(emitDashKitEvent).not.toHaveBeenCalled();
95
+ expect(onChange).not.toHaveBeenCalled();
96
+ });
97
+ });
98
+ describe('DashKit event emitter', () => {
99
+ it('unsubscribe stops handler from being called', () => {
100
+ const instance = new DashKit(DashKit.defaultProps);
101
+ const handler = jest.fn();
102
+ const unsubscribe = instance.on('change', handler);
103
+ unsubscribe();
104
+ const event = {
105
+ patches: [],
106
+ layout: [],
107
+ previousLayout: [],
108
+ preventDefault: jest.fn(),
109
+ defaultPrevented: false,
110
+ };
111
+ instance[_emitSymbol]('change', event);
112
+ expect(handler).not.toHaveBeenCalled();
113
+ });
114
+ it('handler error does not stop other handlers; onChange still fires', () => {
115
+ const instance = new DashKit(DashKit.defaultProps);
116
+ const onChange = jest.fn();
117
+ const normalHandler = jest.fn();
118
+ instance.on('change', () => {
119
+ throw new Error('boom');
120
+ });
121
+ instance.on('change', normalHandler);
122
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
123
+ emitDashKitChangeEvent({
124
+ config: TEST_CONFIG,
125
+ newConfig: NEXT_CONFIG,
126
+ onChange,
127
+ emitDashKitEvent: instance[_emitSymbol],
128
+ });
129
+ consoleErrorSpy.mockRestore();
130
+ expect(normalHandler).toHaveBeenCalledTimes(1);
131
+ expect(onChange).toHaveBeenCalledTimes(1);
132
+ });
133
+ });
134
+ describe('DashKit change event — internal baseline (preventDefault flow)', () => {
135
+ it('preventDefault flow: sequential changes produce incremental patches when baseline is updated', () => {
136
+ const instance = new DashKit(DashKit.defaultProps);
137
+ const onChange = jest.fn();
138
+ const patches = [];
139
+ instance.on('change', (e) => {
140
+ patches.push([...e.patches]);
141
+ e.preventDefault();
142
+ });
143
+ const config1 = Object.assign(Object.assign({}, TEST_CONFIG), { items: [
144
+ { id: 'item1', type: 'test-event', namespace: 'default', data: {} },
145
+ { id: 'item2', type: 'test-event', namespace: 'default', data: {} },
146
+ ], layout: [
147
+ { i: 'item1', x: 0, y: 0, w: 2, h: 2 },
148
+ { i: 'item2', x: 2, y: 0, w: 2, h: 2 },
149
+ ] });
150
+ const config2 = Object.assign(Object.assign({}, config1), { layout: [
151
+ { i: 'item1', x: 1, y: 0, w: 2, h: 2 }, // item1 moved to x=1
152
+ { i: 'item2', x: 2, y: 0, w: 2, h: 2 }, // item2 unchanged
153
+ ] });
154
+ const config3 = Object.assign(Object.assign({}, config1), { layout: [
155
+ { i: 'item1', x: 1, y: 0, w: 2, h: 2 }, // item1 unchanged
156
+ { i: 'item2', x: 3, y: 0, w: 2, h: 2 }, // item2 moved to x=3
157
+ ] });
158
+ // First change: item1 moved from x=0 to x=1
159
+ emitDashKitChangeEvent({
160
+ config: config1,
161
+ newConfig: config2,
162
+ onChange,
163
+ emitDashKitEvent: instance[_emitSymbol],
164
+ });
165
+ // Second change: In withContext, the internal baseline is updated automatically
166
+ // after each change. Here we simulate this behavior by passing config2 as baseline.
167
+ emitDashKitChangeEvent({
168
+ config: config2, // internal baseline updated to config2.layout
169
+ newConfig: config3,
170
+ onChange,
171
+ emitDashKitEvent: instance[_emitSymbol],
172
+ });
173
+ expect(onChange).not.toHaveBeenCalled(); // preventDefault works
174
+ expect(patches).toHaveLength(2);
175
+ // First patch: only item1
176
+ expect(patches[0]).toEqual([{ i: 'item1', x: 1, y: 0, w: 2, h: 2 }]);
177
+ // Second patch: only item2 (NO accumulation!)
178
+ // This works because baseline was updated from config1.layout to config2.layout
179
+ expect(patches[1]).toEqual([{ i: 'item2', x: 3, y: 0, w: 2, h: 2 }]);
180
+ });
181
+ it('legacy flow: onChange updates props and patches work correctly', () => {
182
+ const instance = new DashKit(DashKit.defaultProps);
183
+ let currentConfig = Object.assign(Object.assign({}, TEST_CONFIG), { items: [
184
+ { id: 'item1', type: 'test-event', namespace: 'default', data: {} },
185
+ { id: 'item2', type: 'test-event', namespace: 'default', data: {} },
186
+ ], layout: [
187
+ { i: 'item1', x: 0, y: 0, w: 2, h: 2 },
188
+ { i: 'item2', x: 2, y: 0, w: 2, h: 2 },
189
+ ] });
190
+ const patches = [];
191
+ instance.on('change', (e) => {
192
+ patches.push([...e.patches]);
193
+ // NO preventDefault → onChange will fire
194
+ });
195
+ const onChange = jest.fn(({ config }) => {
196
+ currentConfig = config; // consumer updates state
197
+ });
198
+ const config2 = Object.assign(Object.assign({}, currentConfig), { layout: [
199
+ { i: 'item1', x: 1, y: 0, w: 2, h: 2 }, // item1 moved to x=1
200
+ { i: 'item2', x: 2, y: 0, w: 2, h: 2 }, // item2 unchanged
201
+ ] });
202
+ emitDashKitChangeEvent({
203
+ config: currentConfig,
204
+ newConfig: config2,
205
+ onChange,
206
+ emitDashKitEvent: instance[_emitSymbol],
207
+ });
208
+ expect(onChange).toHaveBeenCalledTimes(1);
209
+ expect(patches[0]).toEqual([{ i: 'item1', x: 1, y: 0, w: 2, h: 2 }]);
210
+ // Second change with updated config
211
+ const config3 = Object.assign(Object.assign({}, currentConfig), { layout: [
212
+ { i: 'item1', x: 1, y: 0, w: 2, h: 2 }, // item1 unchanged
213
+ { i: 'item2', x: 3, y: 0, w: 2, h: 2 }, // item2 moved to x=3
214
+ ] });
215
+ emitDashKitChangeEvent({
216
+ config: currentConfig, // ← updated after first onChange
217
+ newConfig: config3,
218
+ onChange,
219
+ emitDashKitEvent: instance[_emitSymbol],
220
+ });
221
+ expect(onChange).toHaveBeenCalledTimes(2);
222
+ expect(patches[1]).toEqual([{ i: 'item2', x: 3, y: 0, w: 2, h: 2 }]); // only new changes
223
+ });
224
+ });
225
+ //# sourceMappingURL=events.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.test.js","sourceRoot":"../../../../../src","sources":["components/DashKit/__tests__/events.test.tsx"],"names":[],"mappings":"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","sourcesContent":["jest.mock('@gravity-ui/icons', () => ({\n Ellipsis: 'ellipsis',\n Gear: 'gear',\n Xmark: 'xmark',\n}));\n\njest.mock('@gravity-ui/uikit', () => {\n const ReactMock = require('react');\n\n return {\n Button: ({children}: {children?: unknown}) =>\n ReactMock.createElement('button', null, children),\n DropdownMenu: () => null,\n Icon: () => null,\n };\n});\n\njest.mock('../../GridLayout/GridLayout', () => {\n const ReactMock = require('react');\n\n return ReactMock.forwardRef(() => null);\n});\n\nimport type {Config} from '../../../shared';\nimport type {DashKitEventMap, DashKitLayoutPatch} from '../../../typings';\nimport {emitDashKitChangeEvent} from '../../../utils';\nimport {DashKit, _emitSymbol} from '../../DashKit';\n\nconst TEST_CONFIG: Config = {\n salt: 'test',\n counter: 1,\n items: [{id: 'item1', type: 'test-event', namespace: 'default', data: {}}],\n layout: [{i: 'item1', x: 0, y: 0, w: 2, h: 2}],\n aliases: {},\n connections: [],\n};\n\nconst NEXT_LAYOUT: Config['layout'] = [{i: 'item1', x: 1, y: 0, w: 2, h: 2}];\nconst NEXT_CONFIG: Config = {...TEST_CONFIG, layout: NEXT_LAYOUT};\n\ndescribe('DashKit change event — onChange gate', () => {\n it('no handler: onChange fires with full config when layout changes', () => {\n const onChange = jest.fn();\n const emitDashKitEvent = jest.fn();\n\n emitDashKitChangeEvent({\n config: TEST_CONFIG,\n newConfig: NEXT_CONFIG,\n onChange,\n emitDashKitEvent,\n });\n\n expect(emitDashKitEvent).toHaveBeenCalledTimes(1);\n expect(onChange).toHaveBeenCalledTimes(1);\n expect(onChange.mock.calls[0][0].config.layout).toEqual(NEXT_LAYOUT);\n });\n\n it('handler without preventDefault: onChange fires and handler receives correct event', () => {\n const instance = new DashKit(DashKit.defaultProps as never);\n const onChange = jest.fn();\n const handler = jest.fn();\n\n instance.on('change', handler);\n\n emitDashKitChangeEvent({\n config: TEST_CONFIG,\n newConfig: NEXT_CONFIG,\n onChange,\n emitDashKitEvent: instance[_emitSymbol],\n });\n\n expect(handler).toHaveBeenCalledTimes(1);\n expect(onChange).toHaveBeenCalledTimes(1);\n const event = handler.mock.calls[0][0] as DashKitEventMap['change'];\n expect(event.layout).toEqual(NEXT_LAYOUT);\n expect(event.previousLayout).toEqual(TEST_CONFIG.layout);\n expect(event.defaultPrevented).toBe(false);\n });\n\n it('handler calls preventDefault: onChange does NOT fire; event carries correct patches', () => {\n const instance = new DashKit(DashKit.defaultProps as never);\n const onChange = jest.fn();\n let capturedEvent: DashKitEventMap['change'] | undefined;\n\n instance.on('change', (e) => {\n capturedEvent = e;\n e.preventDefault();\n });\n\n emitDashKitChangeEvent({\n config: TEST_CONFIG,\n newConfig: NEXT_CONFIG,\n onChange,\n emitDashKitEvent: instance[_emitSymbol],\n });\n\n expect(onChange).not.toHaveBeenCalled();\n expect(capturedEvent).toBeDefined();\n if (capturedEvent) {\n expect(capturedEvent.patches).toEqual([{i: 'item1', x: 1, y: 0, w: 2, h: 2}]);\n expect(capturedEvent.layout).toEqual(NEXT_LAYOUT);\n expect(capturedEvent.previousLayout).toEqual(TEST_CONFIG.layout);\n expect(capturedEvent.defaultPrevented).toBe(true);\n }\n });\n\n it('same layout: event and onChange do not fire', () => {\n const onChange = jest.fn();\n const emitDashKitEvent = jest.fn();\n\n emitDashKitChangeEvent({\n config: TEST_CONFIG,\n newConfig: TEST_CONFIG,\n onChange,\n emitDashKitEvent,\n });\n\n expect(emitDashKitEvent).not.toHaveBeenCalled();\n expect(onChange).not.toHaveBeenCalled();\n });\n});\n\ndescribe('DashKit event emitter', () => {\n it('unsubscribe stops handler from being called', () => {\n const instance = new DashKit(DashKit.defaultProps as never);\n const handler = jest.fn();\n\n const unsubscribe = instance.on('change', handler);\n unsubscribe();\n\n const event = {\n patches: [],\n layout: [],\n previousLayout: [],\n preventDefault: jest.fn(),\n defaultPrevented: false,\n } as unknown as DashKitEventMap['change'];\n\n instance[_emitSymbol]('change', event);\n\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('handler error does not stop other handlers; onChange still fires', () => {\n const instance = new DashKit(DashKit.defaultProps as never);\n const onChange = jest.fn();\n const normalHandler = jest.fn();\n\n instance.on('change', () => {\n throw new Error('boom');\n });\n instance.on('change', normalHandler);\n\n const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});\n\n emitDashKitChangeEvent({\n config: TEST_CONFIG,\n newConfig: NEXT_CONFIG,\n onChange,\n emitDashKitEvent: instance[_emitSymbol],\n });\n\n consoleErrorSpy.mockRestore();\n\n expect(normalHandler).toHaveBeenCalledTimes(1);\n expect(onChange).toHaveBeenCalledTimes(1);\n });\n});\n\ndescribe('DashKit change event — internal baseline (preventDefault flow)', () => {\n it('preventDefault flow: sequential changes produce incremental patches when baseline is updated', () => {\n const instance = new DashKit(DashKit.defaultProps as never);\n const onChange = jest.fn();\n const patches: Array<DashKitLayoutPatch[]> = [];\n\n instance.on('change', (e) => {\n patches.push([...e.patches]);\n e.preventDefault();\n });\n\n const config1: Config = {\n ...TEST_CONFIG,\n items: [\n {id: 'item1', type: 'test-event', namespace: 'default', data: {}},\n {id: 'item2', type: 'test-event', namespace: 'default', data: {}},\n ],\n layout: [\n {i: 'item1', x: 0, y: 0, w: 2, h: 2},\n {i: 'item2', x: 2, y: 0, w: 2, h: 2},\n ],\n };\n\n const config2: Config = {\n ...config1,\n layout: [\n {i: 'item1', x: 1, y: 0, w: 2, h: 2}, // item1 moved to x=1\n {i: 'item2', x: 2, y: 0, w: 2, h: 2}, // item2 unchanged\n ],\n };\n\n const config3: Config = {\n ...config1,\n layout: [\n {i: 'item1', x: 1, y: 0, w: 2, h: 2}, // item1 unchanged\n {i: 'item2', x: 3, y: 0, w: 2, h: 2}, // item2 moved to x=3\n ],\n };\n\n // First change: item1 moved from x=0 to x=1\n emitDashKitChangeEvent({\n config: config1,\n newConfig: config2,\n onChange,\n emitDashKitEvent: instance[_emitSymbol],\n });\n\n // Second change: In withContext, the internal baseline is updated automatically\n // after each change. Here we simulate this behavior by passing config2 as baseline.\n emitDashKitChangeEvent({\n config: config2, // internal baseline updated to config2.layout\n newConfig: config3,\n onChange,\n emitDashKitEvent: instance[_emitSymbol],\n });\n\n expect(onChange).not.toHaveBeenCalled(); // preventDefault works\n expect(patches).toHaveLength(2);\n\n // First patch: only item1\n expect(patches[0]).toEqual([{i: 'item1', x: 1, y: 0, w: 2, h: 2}]);\n\n // Second patch: only item2 (NO accumulation!)\n // This works because baseline was updated from config1.layout to config2.layout\n expect(patches[1]).toEqual([{i: 'item2', x: 3, y: 0, w: 2, h: 2}]);\n });\n\n it('legacy flow: onChange updates props and patches work correctly', () => {\n const instance = new DashKit(DashKit.defaultProps as never);\n let currentConfig: Config = {\n ...TEST_CONFIG,\n items: [\n {id: 'item1', type: 'test-event', namespace: 'default', data: {}},\n {id: 'item2', type: 'test-event', namespace: 'default', data: {}},\n ],\n layout: [\n {i: 'item1', x: 0, y: 0, w: 2, h: 2},\n {i: 'item2', x: 2, y: 0, w: 2, h: 2},\n ],\n };\n const patches: Array<Array<{i: string; x?: number; y?: number; w?: number; h?: number}>> =\n [];\n\n instance.on('change', (e) => {\n patches.push([...e.patches]);\n // NO preventDefault → onChange will fire\n });\n\n const onChange = jest.fn(({config}) => {\n currentConfig = config; // consumer updates state\n });\n\n const config2 = {\n ...currentConfig,\n layout: [\n {i: 'item1', x: 1, y: 0, w: 2, h: 2}, // item1 moved to x=1\n {i: 'item2', x: 2, y: 0, w: 2, h: 2}, // item2 unchanged\n ],\n };\n\n emitDashKitChangeEvent({\n config: currentConfig,\n newConfig: config2,\n onChange,\n emitDashKitEvent: instance[_emitSymbol],\n });\n\n expect(onChange).toHaveBeenCalledTimes(1);\n expect(patches[0]).toEqual([{i: 'item1', x: 1, y: 0, w: 2, h: 2}]);\n\n // Second change with updated config\n const config3 = {\n ...currentConfig,\n layout: [\n {i: 'item1', x: 1, y: 0, w: 2, h: 2}, // item1 unchanged\n {i: 'item2', x: 3, y: 0, w: 2, h: 2}, // item2 moved to x=3\n ],\n };\n\n emitDashKitChangeEvent({\n config: currentConfig, // ← updated after first onChange\n newConfig: config3,\n onChange,\n emitDashKitEvent: instance[_emitSymbol],\n });\n\n expect(onChange).toHaveBeenCalledTimes(2);\n expect(patches[1]).toEqual([{i: 'item2', x: 3, y: 0, w: 2, h: 2}]); // only new changes\n });\n});\n"]}
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { DashKitWithContextProps } from "../../hocs/withContext.js";
3
3
  import "./DashKitView.css";
4
- type DashKitViewProps = Omit<DashKitWithContextProps, 'layout' | 'forwardedMetaRef'>;
4
+ type DashKitViewProps = Omit<DashKitWithContextProps, 'forwardedMetaRef'>;
5
5
  declare const DashKitViewForwardedMeta: React.ForwardRefExoticComponent<DashKitViewProps & React.RefAttributes<unknown>>;
6
6
  export default DashKitViewForwardedMeta;
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
2
  import { DashKitContext } from "../../context/index.js";
3
3
  import { withContext } from "../../hocs/withContext.js";
4
- import { useCalcPropsLayout } from "../../hooks/useCalcLayout.js";
5
4
  import { cn } from "../../utils/cn.js";
6
5
  import GridLayout from "../GridLayout/GridLayout.js";
7
6
  import MobileLayout from "../MobileLayout/MobileLayout.js";
@@ -14,8 +13,7 @@ function DashKitView() {
14
13
  }
15
14
  const DashKitViewWithContext = withContext(DashKitView);
16
15
  const DashKitViewForwardedMeta = React.forwardRef((props, ref) => {
17
- const layout = useCalcPropsLayout(props.config, props.registerManager);
18
- return React.createElement(DashKitViewWithContext, Object.assign({}, props, { layout: layout, forwardedMetaRef: ref }));
16
+ return React.createElement(DashKitViewWithContext, Object.assign({}, props, { forwardedMetaRef: ref }));
19
17
  });
20
18
  DashKitViewForwardedMeta.displayName = 'DashKitViewForwardedMeta';
21
19
  export default DashKitViewForwardedMeta;
@@ -1 +1 @@
1
- {"version":3,"file":"DashKitView.js","sourceRoot":"../../../../src","sources":["components/DashKitView/DashKitView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAC,cAAc,EAAC,+BAAsB;AAC7C,OAAO,EAAC,WAAW,EAAC,kCAA+B;AAEnD,OAAO,EAAC,kBAAkB,EAAC,qCAAkC;AAC7D,OAAO,EAAC,EAAE,EAAC,0BAAuB;AAClC,OAAO,UAAU,oCAAiC;AAClD,OAAO,YAAY,wCAAqC;AAExD,2BAA4B;AAE5B,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAIxB,SAAS,WAAW;IAChB,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACjD,MAAM,EAAC,eAAe,EAAE,gBAAgB,EAAC,GAAG,OAAO,CAAC;IAEpD,OAAO,CACH,6BAAK,SAAS,EAAE,CAAC,CAAC,EAAC,KAAK,EAAE,eAAe,CAAC,QAAQ,CAAC,KAAK,EAAC,CAAC,IACrD,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CACjC,oBAAC,YAAY,OAAG,CACnB,CAAC,CAAC,CAAC,CACA,oBAAC,UAAU,IAAC,GAAG,EAAE,gBAAgB,GAAI,CACxC,CACC,CACT,CAAC;AACN,CAAC;AAED,MAAM,sBAAsB,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;AAExD,MAAM,wBAAwB,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,KAAuB,EAAE,GAAG,EAAE,EAAE;IAC/E,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAEvE,OAAO,oBAAC,sBAAsB,oBAAK,KAAK,IAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;AACxF,CAAC,CAAC,CAAC;AAEH,wBAAwB,CAAC,WAAW,GAAG,0BAA0B,CAAC;AAElE,eAAe,wBAAwB,CAAC","sourcesContent":["import React from 'react';\n\nimport {DashKitContext} from '../../context';\nimport {withContext} from '../../hocs/withContext';\nimport type {DashKitWithContextProps} from '../../hocs/withContext';\nimport {useCalcPropsLayout} from '../../hooks/useCalcLayout';\nimport {cn} from '../../utils/cn';\nimport GridLayout from '../GridLayout/GridLayout';\nimport MobileLayout from '../MobileLayout/MobileLayout';\n\nimport './DashKitView.scss';\n\nconst b = cn('dashkit');\n\ntype DashKitViewProps = Omit<DashKitWithContextProps, 'layout' | 'forwardedMetaRef'>;\n\nfunction DashKitView() {\n const context = React.useContext(DashKitContext);\n const {registerManager, forwardedMetaRef} = context;\n\n return (\n <div className={b({theme: registerManager.settings.theme})}>\n {registerManager.settings.isMobile ? (\n <MobileLayout />\n ) : (\n <GridLayout ref={forwardedMetaRef} />\n )}\n </div>\n );\n}\n\nconst DashKitViewWithContext = withContext(DashKitView);\n\nconst DashKitViewForwardedMeta = React.forwardRef((props: DashKitViewProps, ref) => {\n const layout = useCalcPropsLayout(props.config, props.registerManager);\n\n return <DashKitViewWithContext {...props} layout={layout} forwardedMetaRef={ref} />;\n});\n\nDashKitViewForwardedMeta.displayName = 'DashKitViewForwardedMeta';\n\nexport default DashKitViewForwardedMeta;\n"]}
1
+ {"version":3,"file":"DashKitView.js","sourceRoot":"../../../../src","sources":["components/DashKitView/DashKitView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAC,cAAc,EAAC,+BAAsB;AAC7C,OAAO,EAAC,WAAW,EAAC,kCAA+B;AAEnD,OAAO,EAAC,EAAE,EAAC,0BAAuB;AAClC,OAAO,UAAU,oCAAiC;AAClD,OAAO,YAAY,wCAAqC;AAExD,2BAA4B;AAE5B,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAIxB,SAAS,WAAW;IAChB,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACjD,MAAM,EAAC,eAAe,EAAE,gBAAgB,EAAC,GAAG,OAAO,CAAC;IAEpD,OAAO,CACH,6BAAK,SAAS,EAAE,CAAC,CAAC,EAAC,KAAK,EAAE,eAAe,CAAC,QAAQ,CAAC,KAAK,EAAC,CAAC,IACrD,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CACjC,oBAAC,YAAY,OAAG,CACnB,CAAC,CAAC,CAAC,CACA,oBAAC,UAAU,IAAC,GAAG,EAAE,gBAAgB,GAAI,CACxC,CACC,CACT,CAAC;AACN,CAAC;AAED,MAAM,sBAAsB,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;AAExD,MAAM,wBAAwB,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,KAAuB,EAAE,GAAG,EAAE,EAAE;IAC/E,OAAO,oBAAC,sBAAsB,oBAAK,KAAK,IAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;AACxE,CAAC,CAAC,CAAC;AAEH,wBAAwB,CAAC,WAAW,GAAG,0BAA0B,CAAC;AAElE,eAAe,wBAAwB,CAAC","sourcesContent":["import React from 'react';\n\nimport {DashKitContext} from '../../context';\nimport {withContext} from '../../hocs/withContext';\nimport type {DashKitWithContextProps} from '../../hocs/withContext';\nimport {cn} from '../../utils/cn';\nimport GridLayout from '../GridLayout/GridLayout';\nimport MobileLayout from '../MobileLayout/MobileLayout';\n\nimport './DashKitView.scss';\n\nconst b = cn('dashkit');\n\ntype DashKitViewProps = Omit<DashKitWithContextProps, 'forwardedMetaRef'>;\n\nfunction DashKitView() {\n const context = React.useContext(DashKitContext);\n const {registerManager, forwardedMetaRef} = context;\n\n return (\n <div className={b({theme: registerManager.settings.theme})}>\n {registerManager.settings.isMobile ? (\n <MobileLayout />\n ) : (\n <GridLayout ref={forwardedMetaRef} />\n )}\n </div>\n );\n}\n\nconst DashKitViewWithContext = withContext(DashKitView);\n\nconst DashKitViewForwardedMeta = React.forwardRef((props: DashKitViewProps, ref) => {\n return <DashKitViewWithContext {...props} forwardedMetaRef={ref} />;\n});\n\nDashKitViewForwardedMeta.displayName = 'DashKitViewForwardedMeta';\n\nexport default DashKitViewForwardedMeta;\n"]}
@@ -19,7 +19,7 @@ function groupLayoutPropsAreEqual(prev, next) {
19
19
  return !needRerenderIfChanged && !needRerenderGroupScoped && !hasTmpPlaceholderChanged;
20
20
  }
21
21
  export const GroupLayout = React.memo(function GroupLayout({ group, renderItems, offset, properties, compactType, callbacks, layout, temporaryPlaceholder, dragStateRef, sharedDragPositionRef, groupResetRegistryRef, isDragCaptured, isAnyDragging, currentDraggingItemId, isAnyDraggedOut, adjustWidgetLayout, getMemoForwardRefCallback, }) {
22
- const { editMode, noOverlay, focusable, draggableHandleClassName, outerDnDEnable, onItemMountChange, onItemRender, onItemFocus, onItemBlur, } = React.useContext(DashKitContext);
22
+ const { editMode, noOverlay, focusable, draggableHandleClassName, outerDnDEnable, onItemMountChange, onItemRender, onItemFocus, onItemBlur, externalLayoutRevision, } = React.useContext(DashKitContext);
23
23
  // Use group-specific noOverlay if it was explicitly set via groupGridProperties,
24
24
  // otherwise fallback to the dashboard-level noOverlay from context.
25
25
  const resolvedNoOverlay = 'noOverlay' in properties ? properties.noOverlay : noOverlay;
@@ -50,7 +50,7 @@ export const GroupLayout = React.memo(function GroupLayout({ group, renderItems,
50
50
  getMemoForwardRefCallback,
51
51
  editMode,
52
52
  ]);
53
- return (React.createElement(Layout, Object.assign({ key: `group_${group}`, isDraggable: editMode, isResizable: editMode }, properties, { compactType: compactType, layout: layout, draggableCancel: `.${DRAGGABLE_CANCEL_CLASS_NAME}`, draggableHandle: draggableHandleClassName ? `.${draggableHandleClassName}` : undefined, onDragStart: callbacks.onDragStart, onDrag: callbacks.onDrag, onDragStop: callbacks.onDragStop, onResizeStart: callbacks.onResizeStart, onResize: callbacks.onResize, onResizeStop: callbacks.onResizeStop, onDragTargetRestore: callbacks.onDragTargetRestore, onDropDragOver: callbacks.onDropDragOver, onDrop: callbacks.onDrop, dragStateRef: dragStateRef, sharedDragPositionRef: sharedDragPositionRef, groupResetRegistryRef: groupResetRegistryRef, group: group, isDragCaptured: isDragCaptured, isDroppable: Boolean(outerDnDEnable) && editMode }),
53
+ return (React.createElement(Layout, Object.assign({ key: `group_${group}`, isDraggable: editMode, externalLayoutRevision: externalLayoutRevision, isResizable: editMode }, properties, { compactType: compactType, layout: layout, draggableCancel: `.${DRAGGABLE_CANCEL_CLASS_NAME}`, draggableHandle: draggableHandleClassName ? `.${draggableHandleClassName}` : undefined, onDragStart: callbacks.onDragStart, onDrag: callbacks.onDrag, onDragStop: callbacks.onDragStop, onResizeStart: callbacks.onResizeStart, onResize: callbacks.onResize, onResizeStop: callbacks.onResizeStop, onDragTargetRestore: callbacks.onDragTargetRestore, onDropDragOver: callbacks.onDropDragOver, onDrop: callbacks.onDrop, dragStateRef: dragStateRef, sharedDragPositionRef: sharedDragPositionRef, groupResetRegistryRef: groupResetRegistryRef, group: group, isDragCaptured: isDragCaptured, isDroppable: Boolean(outerDnDEnable) && editMode }),
54
54
  itemElements,
55
55
  temporaryPlaceholder));
56
56
  }, groupLayoutPropsAreEqual);
@@ -1 +1 @@
1
- {"version":3,"file":"GroupLayout.js","sourceRoot":"../../../../src","sources":["components/GridLayout/GroupLayout.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\n\nimport {DRAGGABLE_CANCEL_CLASS_NAME} from '../../constants';\nimport {DashKitContext} from '../../context';\nimport type {ConfigItem, ConfigLayout} from '../../shared';\nimport type {PluginRef, PluginWidgetProps, ReactGridLayoutProps} from '../../typings';\nimport GridItem from '../GridItem/GridItem';\n\nimport {Layout} from './ReactGridLayout';\nimport type {GroupCallbacks} from './types';\nimport {shallowArrayEqual, shallowObjectEqual} from './utils';\n\nexport interface GroupLayoutProps {\n group: string;\n renderItems: ConfigItem[];\n offset: number;\n\n // Pre-computed by GridLayout.renderGroup (stable refs via getMemoGroupProps)\n properties: Partial<ReactGridLayoutProps>;\n compactType: 'vertical' | 'horizontal' | null | undefined;\n callbacks: GroupCallbacks;\n layout: ConfigLayout[];\n temporaryPlaceholder: React.ReactNode;\n\n // Refs read imperatively by DragOverLayout — no re-render on drag start.\n isDragCaptured: boolean;\n dragStateRef: React.MutableRefObject<{isDragging: boolean; sourceGroup: string | null}>;\n sharedDragPositionRef: React.MutableRefObject<{offsetX: number; offsetY: number} | null>;\n groupResetRegistryRef: React.MutableRefObject<Map<string, () => void>>;\n\n // Drag state scoped to this group only (always false/null for non-source groups)\n isAnyDragging: boolean;\n currentDraggingItemId: string | null;\n isAnyDraggedOut: boolean;\n\n // Stable instance method refs from GridLayout (arrow function class properties)\n adjustWidgetLayout: PluginWidgetProps['adjustWidgetLayout'];\n getMemoForwardRefCallback: (index: number) => (ref: PluginRef) => void;\n}\n\nfunction groupLayoutPropsAreEqual(prev: GroupLayoutProps, next: GroupLayoutProps): boolean {\n const needRerenderIfChanged =\n prev.group !== next.group ||\n prev.layout !== next.layout ||\n prev.offset !== next.offset ||\n !shallowArrayEqual(prev.renderItems, next.renderItems) ||\n !shallowObjectEqual(prev.properties, next.properties);\n\n // Drag refs are stable and never trigger re-renders; only group-scoped state matters.\n const needRerenderGroupScoped =\n prev.isDragCaptured !== next.isDragCaptured ||\n prev.isAnyDragging !== next.isAnyDragging ||\n prev.currentDraggingItemId !== next.currentDraggingItemId ||\n prev.isAnyDraggedOut !== next.isAnyDraggedOut;\n\n const hasTmpPlaceholderChanged = prev.temporaryPlaceholder !== next.temporaryPlaceholder;\n\n return !needRerenderIfChanged && !needRerenderGroupScoped && !hasTmpPlaceholderChanged;\n}\n\nexport const GroupLayout = React.memo(function GroupLayout({\n group,\n renderItems,\n offset,\n properties,\n compactType,\n callbacks,\n layout,\n temporaryPlaceholder,\n dragStateRef,\n sharedDragPositionRef,\n groupResetRegistryRef,\n isDragCaptured,\n isAnyDragging,\n currentDraggingItemId,\n isAnyDraggedOut,\n adjustWidgetLayout,\n getMemoForwardRefCallback,\n}: GroupLayoutProps) {\n const {\n editMode,\n noOverlay,\n focusable,\n draggableHandleClassName,\n outerDnDEnable,\n onItemMountChange,\n onItemRender,\n onItemFocus,\n onItemBlur,\n } = React.useContext(DashKitContext);\n\n // Use group-specific noOverlay if it was explicitly set via groupGridProperties,\n // otherwise fallback to the dashboard-level noOverlay from context.\n const resolvedNoOverlay = 'noOverlay' in properties ? properties.noOverlay : noOverlay;\n\n // Memoize items array; non-source groups have stable false/null drag props so React skips their subtrees.\n const itemElements = React.useMemo(() => {\n return renderItems.map((item, i) => {\n const keyId = item.id;\n\n const isDragging = editMode && isAnyDragging && keyId === currentDraggingItemId;\n const isDraggedOut = isDragging && isAnyDraggedOut;\n\n return (\n <GridItem\n key={keyId}\n forwardedPluginRef={getMemoForwardRefCallback(offset + i)}\n id={keyId}\n item={item}\n layout={layout}\n adjustWidgetLayout={adjustWidgetLayout}\n isDragging={isDragging}\n isDraggedOut={isDraggedOut || undefined}\n noOverlay={resolvedNoOverlay}\n focusable={focusable}\n withCustomHandle={Boolean(draggableHandleClassName)}\n onItemMountChange={onItemMountChange}\n onItemRender={onItemRender}\n gridLayout={properties}\n onItemFocus={onItemFocus}\n onItemBlur={onItemBlur}\n />\n );\n });\n }, [\n renderItems,\n isAnyDragging,\n currentDraggingItemId,\n isAnyDraggedOut,\n offset,\n layout,\n adjustWidgetLayout,\n resolvedNoOverlay,\n focusable,\n draggableHandleClassName,\n onItemMountChange,\n onItemRender,\n properties,\n onItemFocus,\n onItemBlur,\n getMemoForwardRefCallback,\n editMode,\n ]);\n\n return (\n <Layout\n key={`group_${group}`}\n isDraggable={editMode}\n isResizable={editMode}\n {...properties}\n compactType={compactType}\n layout={layout}\n draggableCancel={`.${DRAGGABLE_CANCEL_CLASS_NAME}`}\n draggableHandle={draggableHandleClassName ? `.${draggableHandleClassName}` : undefined}\n onDragStart={callbacks.onDragStart}\n onDrag={callbacks.onDrag}\n onDragStop={callbacks.onDragStop}\n onResizeStart={callbacks.onResizeStart}\n onResize={callbacks.onResize}\n onResizeStop={callbacks.onResizeStop}\n onDragTargetRestore={callbacks.onDragTargetRestore}\n onDropDragOver={callbacks.onDropDragOver}\n onDrop={callbacks.onDrop}\n dragStateRef={dragStateRef}\n sharedDragPositionRef={sharedDragPositionRef}\n groupResetRegistryRef={groupResetRegistryRef}\n group={group}\n isDragCaptured={isDragCaptured}\n isDroppable={Boolean(outerDnDEnable) && editMode}\n >\n {itemElements}\n {temporaryPlaceholder}\n </Layout>\n );\n}, groupLayoutPropsAreEqual);\n"]}
1
+ {"version":3,"file":"GroupLayout.js","sourceRoot":"../../../../src","sources":["components/GridLayout/GroupLayout.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\n\nimport {DRAGGABLE_CANCEL_CLASS_NAME} from '../../constants';\nimport {DashKitContext} from '../../context';\nimport type {ConfigItem, ConfigLayout} from '../../shared';\nimport type {PluginRef, PluginWidgetProps, ReactGridLayoutProps} from '../../typings';\nimport GridItem from '../GridItem/GridItem';\n\nimport {Layout} from './ReactGridLayout';\nimport type {GroupCallbacks} from './types';\nimport {shallowArrayEqual, shallowObjectEqual} from './utils';\n\nexport interface GroupLayoutProps {\n group: string;\n renderItems: ConfigItem[];\n offset: number;\n\n // Pre-computed by GridLayout.renderGroup (stable refs via getMemoGroupProps)\n properties: Partial<ReactGridLayoutProps>;\n compactType: 'vertical' | 'horizontal' | null | undefined;\n callbacks: GroupCallbacks;\n layout: ConfigLayout[];\n temporaryPlaceholder: React.ReactNode;\n\n // Refs read imperatively by DragOverLayout — no re-render on drag start.\n isDragCaptured: boolean;\n dragStateRef: React.MutableRefObject<{isDragging: boolean; sourceGroup: string | null}>;\n sharedDragPositionRef: React.MutableRefObject<{offsetX: number; offsetY: number} | null>;\n groupResetRegistryRef: React.MutableRefObject<Map<string, () => void>>;\n\n // Drag state scoped to this group only (always false/null for non-source groups)\n isAnyDragging: boolean;\n currentDraggingItemId: string | null;\n isAnyDraggedOut: boolean;\n\n // Stable instance method refs from GridLayout (arrow function class properties)\n adjustWidgetLayout: PluginWidgetProps['adjustWidgetLayout'];\n getMemoForwardRefCallback: (index: number) => (ref: PluginRef) => void;\n}\n\nfunction groupLayoutPropsAreEqual(prev: GroupLayoutProps, next: GroupLayoutProps): boolean {\n const needRerenderIfChanged =\n prev.group !== next.group ||\n prev.layout !== next.layout ||\n prev.offset !== next.offset ||\n !shallowArrayEqual(prev.renderItems, next.renderItems) ||\n !shallowObjectEqual(prev.properties, next.properties);\n\n // Drag refs are stable and never trigger re-renders; only group-scoped state matters.\n const needRerenderGroupScoped =\n prev.isDragCaptured !== next.isDragCaptured ||\n prev.isAnyDragging !== next.isAnyDragging ||\n prev.currentDraggingItemId !== next.currentDraggingItemId ||\n prev.isAnyDraggedOut !== next.isAnyDraggedOut;\n\n const hasTmpPlaceholderChanged = prev.temporaryPlaceholder !== next.temporaryPlaceholder;\n\n return !needRerenderIfChanged && !needRerenderGroupScoped && !hasTmpPlaceholderChanged;\n}\n\nexport const GroupLayout = React.memo(function GroupLayout({\n group,\n renderItems,\n offset,\n properties,\n compactType,\n callbacks,\n layout,\n temporaryPlaceholder,\n dragStateRef,\n sharedDragPositionRef,\n groupResetRegistryRef,\n isDragCaptured,\n isAnyDragging,\n currentDraggingItemId,\n isAnyDraggedOut,\n adjustWidgetLayout,\n getMemoForwardRefCallback,\n}: GroupLayoutProps) {\n const {\n editMode,\n noOverlay,\n focusable,\n draggableHandleClassName,\n outerDnDEnable,\n onItemMountChange,\n onItemRender,\n onItemFocus,\n onItemBlur,\n externalLayoutRevision,\n } = React.useContext(DashKitContext);\n\n // Use group-specific noOverlay if it was explicitly set via groupGridProperties,\n // otherwise fallback to the dashboard-level noOverlay from context.\n const resolvedNoOverlay = 'noOverlay' in properties ? properties.noOverlay : noOverlay;\n\n // Memoize items array; non-source groups have stable false/null drag props so React skips their subtrees.\n const itemElements = React.useMemo(() => {\n return renderItems.map((item, i) => {\n const keyId = item.id;\n\n const isDragging = editMode && isAnyDragging && keyId === currentDraggingItemId;\n const isDraggedOut = isDragging && isAnyDraggedOut;\n\n return (\n <GridItem\n key={keyId}\n forwardedPluginRef={getMemoForwardRefCallback(offset + i)}\n id={keyId}\n item={item}\n layout={layout}\n adjustWidgetLayout={adjustWidgetLayout}\n isDragging={isDragging}\n isDraggedOut={isDraggedOut || undefined}\n noOverlay={resolvedNoOverlay}\n focusable={focusable}\n withCustomHandle={Boolean(draggableHandleClassName)}\n onItemMountChange={onItemMountChange}\n onItemRender={onItemRender}\n gridLayout={properties}\n onItemFocus={onItemFocus}\n onItemBlur={onItemBlur}\n />\n );\n });\n }, [\n renderItems,\n isAnyDragging,\n currentDraggingItemId,\n isAnyDraggedOut,\n offset,\n layout,\n adjustWidgetLayout,\n resolvedNoOverlay,\n focusable,\n draggableHandleClassName,\n onItemMountChange,\n onItemRender,\n properties,\n onItemFocus,\n onItemBlur,\n getMemoForwardRefCallback,\n editMode,\n ]);\n\n return (\n <Layout\n key={`group_${group}`}\n isDraggable={editMode}\n externalLayoutRevision={externalLayoutRevision}\n isResizable={editMode}\n {...properties}\n compactType={compactType}\n layout={layout}\n draggableCancel={`.${DRAGGABLE_CANCEL_CLASS_NAME}`}\n draggableHandle={draggableHandleClassName ? `.${draggableHandleClassName}` : undefined}\n onDragStart={callbacks.onDragStart}\n onDrag={callbacks.onDrag}\n onDragStop={callbacks.onDragStop}\n onResizeStart={callbacks.onResizeStart}\n onResize={callbacks.onResize}\n onResizeStop={callbacks.onResizeStop}\n onDragTargetRestore={callbacks.onDragTargetRestore}\n onDropDragOver={callbacks.onDropDragOver}\n onDrop={callbacks.onDrop}\n dragStateRef={dragStateRef}\n sharedDragPositionRef={sharedDragPositionRef}\n groupResetRegistryRef={groupResetRegistryRef}\n group={group}\n isDragCaptured={isDragCaptured}\n isDroppable={Boolean(outerDnDEnable) && editMode}\n >\n {itemElements}\n {temporaryPlaceholder}\n </Layout>\n );\n}, groupLayoutPropsAreEqual);\n"]}
@@ -16,5 +16,6 @@ export declare const Layout: React.ComponentClass<ReactGridLayout.ReactGridLayou
16
16
  onDragTargetRestore?: () => void;
17
17
  transformScaleRef?: React.MutableRefObject<number>;
18
18
  groupResetRegistryRef?: React.MutableRefObject<Map<string, () => void>>;
19
+ externalLayoutRevision?: number;
19
20
  } & ReactGridLayout.WidthProviderProps, any>;
20
21
  export {};