@vuu-ui/grid-layout 4.0.0-alpha.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 (142) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +141 -0
  3. package/package.json +40 -0
  4. package/src/GridCommand.js +395 -0
  5. package/src/GridComponentSettings.js +163 -0
  6. package/src/GridController.js +359 -0
  7. package/src/GridDragCoordinator.js +293 -0
  8. package/src/GridGeometry.js +1298 -0
  9. package/src/GridLayout.css.js +119 -0
  10. package/src/GridLayout.js +120 -0
  11. package/src/GridLayoutContext.js +58 -0
  12. package/src/GridLayoutDocument.js +307 -0
  13. package/src/GridLayoutDocumentIds.js +110 -0
  14. package/src/GridLayoutItem.css.js +46 -0
  15. package/src/GridLayoutItem.js +147 -0
  16. package/src/GridLayoutLegacyCompatibility.js +66 -0
  17. package/src/GridLayoutModel.js +138 -0
  18. package/src/GridLayoutProvider.js +263 -0
  19. package/src/GridLayoutStackedItem.css.js +26 -0
  20. package/src/GridLayoutStackedtem.js +140 -0
  21. package/src/GridModel.js +1289 -0
  22. package/src/GridPlaceholder.css.js +9 -0
  23. package/src/GridPlaceholder.js +24 -0
  24. package/src/GridSnapshot.js +11 -0
  25. package/src/GridSplitter.css.js +62 -0
  26. package/src/GridSplitter.js +26 -0
  27. package/src/GridStack.js +277 -0
  28. package/src/Icon.css.js +8 -0
  29. package/src/Icon.js +26 -0
  30. package/src/IconButton.css.js +9 -0
  31. package/src/IconButton.js +28 -0
  32. package/src/TabDialog.css.js +10 -0
  33. package/src/TabDialog.js +77 -0
  34. package/src/TabMenu.css.js +8 -0
  35. package/src/TabMenu.js +78 -0
  36. package/src/componentToJson.js +41 -0
  37. package/src/drag-drop-next/DragContextNext.js +173 -0
  38. package/src/drag-drop-next/DragDropProviderNext.css.js +72 -0
  39. package/src/drag-drop-next/DragDropProviderNext.js +91 -0
  40. package/src/drag-drop-next/SpaceMan.js +256 -0
  41. package/src/drag-drop-next/TemplateDragSession.js +36 -0
  42. package/src/drag-drop-next/tabstrip-drag-drop.js +251 -0
  43. package/src/grid-dom-utils.js +57 -0
  44. package/src/grid-layout-utils.js +39 -0
  45. package/src/grid-snapshot-adapters.js +435 -0
  46. package/src/index.js +24 -0
  47. package/src/json-value.js +56 -0
  48. package/src/layoutFromJson.js +15 -0
  49. package/src/layoutToJson.js +5 -0
  50. package/src/propUtils.js +15 -0
  51. package/src/react-element-utils.js +15 -0
  52. package/src/typeOf.js +15 -0
  53. package/src/useAsDropTarget.js +229 -0
  54. package/src/useDraggable.js +46 -0
  55. package/src/useEditTabName.js +70 -0
  56. package/src/useGridChildProps.js +47 -0
  57. package/src/useGridControllerSnapshot.js +21 -0
  58. package/src/useGridLayout.js +727 -0
  59. package/src/useGridSplitterResizing.js +262 -0
  60. package/src/useNotDropTarget.js +2 -0
  61. package/types/AddTabDialog.d.ts +5 -0
  62. package/types/GridCommand.d.ts +146 -0
  63. package/types/GridComponentSettings.d.ts +71 -0
  64. package/types/GridController.d.ts +50 -0
  65. package/types/GridDragCoordinator.d.ts +80 -0
  66. package/types/GridGeometry.d.ts +331 -0
  67. package/types/GridLayout.d.ts +20 -0
  68. package/types/GridLayoutContext.d.ts +141 -0
  69. package/types/GridLayoutDocument.d.ts +61 -0
  70. package/types/GridLayoutDocumentIds.d.ts +15 -0
  71. package/types/GridLayoutItem.d.ts +28 -0
  72. package/types/GridLayoutLegacyCompatibility.d.ts +57 -0
  73. package/types/GridLayoutModel.d.ts +72 -0
  74. package/types/GridLayoutProvider.d.ts +59 -0
  75. package/types/GridLayoutStackedtem.d.ts +10 -0
  76. package/types/GridModel.d.ts +504 -0
  77. package/types/GridPlaceholder.d.ts +5 -0
  78. package/types/GridSnapshot.d.ts +49 -0
  79. package/types/GridSplitter.d.ts +10 -0
  80. package/types/GridStack.d.ts +160 -0
  81. package/types/Icon.d.ts +6 -0
  82. package/types/IconButton.d.ts +6 -0
  83. package/types/TabDialog.d.ts +9 -0
  84. package/types/TabMenu.d.ts +7 -0
  85. package/types/componentToJson.d.ts +38 -0
  86. package/types/drag-drop-next/DragContextNext.d.ts +92 -0
  87. package/types/drag-drop-next/DragDropProviderNext.d.ts +23 -0
  88. package/types/drag-drop-next/SpaceMan.d.ts +35 -0
  89. package/types/drag-drop-next/TemplateDragSession.d.ts +21 -0
  90. package/types/drag-drop-next/tabstrip-drag-drop.d.ts +3 -0
  91. package/types/grid-dom-utils.d.ts +17 -0
  92. package/types/grid-layout-utils.d.ts +18 -0
  93. package/types/grid-matrix.d.ts +5 -0
  94. package/types/grid-snapshot-adapters.d.ts +22 -0
  95. package/types/index.d.ts +24 -0
  96. package/types/json-value.d.ts +23 -0
  97. package/types/layoutFromJson.d.ts +4 -0
  98. package/types/layoutToJson.d.ts +3 -0
  99. package/types/propUtils.d.ts +5 -0
  100. package/types/react-element-utils.d.ts +3 -0
  101. package/types/src/AddTabDialog.d.ts +5 -0
  102. package/types/src/GridLayout.d.ts +14 -0
  103. package/types/src/GridLayoutContext.d.ts +88 -0
  104. package/types/src/GridLayoutItem.d.ts +28 -0
  105. package/types/src/GridLayoutModel.d.ts +59 -0
  106. package/types/src/GridLayoutProvider.d.ts +37 -0
  107. package/types/src/GridLayoutStackedtem.d.ts +10 -0
  108. package/types/src/GridModel.d.ts +350 -0
  109. package/types/src/GridPlaceholder.d.ts +5 -0
  110. package/types/src/GridSplitter.d.ts +9 -0
  111. package/types/src/Icon.d.ts +6 -0
  112. package/types/src/IconButton.d.ts +6 -0
  113. package/types/src/TabMenu.d.ts +6 -0
  114. package/types/src/componentToJson.d.ts +32 -0
  115. package/types/src/drag-drop-next/DragContextNext.d.ts +73 -0
  116. package/types/src/drag-drop-next/DragDropProviderNext.d.ts +20 -0
  117. package/types/src/drag-drop-next/SpaceMan.d.ts +35 -0
  118. package/types/src/drag-drop-next/tabstrip-drag-drop.d.ts +3 -0
  119. package/types/src/grid-dom-utils.d.ts +17 -0
  120. package/types/src/grid-layout-utils.d.ts +80 -0
  121. package/types/src/grid-matrix.d.ts +5 -0
  122. package/types/src/index.d.ts +10 -0
  123. package/types/src/layoutFromJson.d.ts +3 -0
  124. package/types/src/layoutToJson.d.ts +2 -0
  125. package/types/src/propUtils.d.ts +5 -0
  126. package/types/src/react-element-utils.d.ts +3 -0
  127. package/types/src/typeOf.d.ts +6 -0
  128. package/types/src/useAsDropTarget.d.ts +7 -0
  129. package/types/src/useDraggable.d.ts +23 -0
  130. package/types/src/useGridChildProps.d.ts +13 -0
  131. package/types/src/useGridLayout.d.ts +43 -0
  132. package/types/src/useGridSplitterResizing.d.ts +12 -0
  133. package/types/src/useNotDropTarget.d.ts +1 -0
  134. package/types/typeOf.d.ts +6 -0
  135. package/types/useAsDropTarget.d.ts +7 -0
  136. package/types/useDraggable.d.ts +24 -0
  137. package/types/useEditTabName.d.ts +11 -0
  138. package/types/useGridChildProps.d.ts +13 -0
  139. package/types/useGridControllerSnapshot.d.ts +7 -0
  140. package/types/useGridLayout.d.ts +53 -0
  141. package/types/useGridSplitterResizing.d.ts +14 -0
  142. package/types/useNotDropTarget.d.ts +1 -0
@@ -0,0 +1,727 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { asReactElements, isGridLayoutSplitDirection, isSimpleStateValue, uuid } from "@vuu-ui/vuu-utils";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { layoutFromJson } from "./layoutFromJson.js";
5
+ import { getActiveIndex, getGridArea, getSharedGridPosition } from "./grid-layout-utils.js";
6
+ import { isTypedComponentTemplate, sourceIsComponent, sourceIsTabbedComponent, sourceIsTemplate } from "./GridLayoutContext.js";
7
+ import { GridLayoutItem, isGridLayoutItem } from "./GridLayoutItem.js";
8
+ import { GridLayoutModel } from "./GridLayoutModel.js";
9
+ import { useGridChangeHandler, useGridLayoutOptions, useSavedGrid } from "./GridLayoutProvider.js";
10
+ import { GridModel, GridModelChildItem, isStackedItem } from "./GridModel.js";
11
+ import { addChildToStackedGridItem, getGridItemChild } from "./react-element-utils.js";
12
+ import { GridCommandExecutionError, LegacyGridCommandExecutor, throwForGridCommandFailure } from "./GridCommand.js";
13
+ import { GridController } from "./GridController.js";
14
+ import { GridDragCoordinator } from "./GridDragCoordinator.js";
15
+ import { gridSnapshotToGridLayoutDescriptor } from "./grid-snapshot-adapters.js";
16
+ import { isGridLayoutStackedItem } from "./GridLayoutStackedtem.js";
17
+ import { useGridControllerSnapshot } from "./useGridControllerSnapshot.js";
18
+ const layoutDescriptorFromChildren = (colsAndRows, children)=>{
19
+ const stackTemplates = new Map(children.filter(isGridLayoutStackedItem).map((element)=>[
20
+ element.props.id,
21
+ element.props
22
+ ]));
23
+ return {
24
+ ...colsAndRows,
25
+ gridLayoutItems: Object.fromEntries(children.filter(isGridLayoutItem).map(({ props })=>{
26
+ const { componentId, contentVisible, "data-drop-target": dropTarget, header, id, minHeight, minWidth, resizeable, stackId, style, title } = props;
27
+ const stackTemplate = stackId ? stackTemplates.get(stackId) : void 0;
28
+ const gridArea = style?.gridArea ?? stackTemplate?.style?.gridArea;
29
+ if (!gridArea) throw Error(`[useGridLayout] GridLayoutItem #${id} has no gridArea`);
30
+ return [
31
+ id,
32
+ {
33
+ componentId,
34
+ contentVisible,
35
+ dropTarget,
36
+ gridArea,
37
+ header,
38
+ minHeight: minHeight ?? stackTemplate?.minHeight,
39
+ minWidth: minWidth ?? stackTemplate?.minWidth,
40
+ resizeable: resizeable ?? stackTemplate?.resizeable,
41
+ stackId,
42
+ title
43
+ }
44
+ ];
45
+ }))
46
+ };
47
+ };
48
+ const assertNeverGridLayoutAction = (action)=>{
49
+ throw new GridCommandExecutionError({
50
+ code: "UNSUPPORTED_ACTION",
51
+ message: `Unsupported GridLayoutAction: ${JSON.stringify(action)}`
52
+ });
53
+ };
54
+ const useGridLayout = ({ children: childrenProp, id, colsAndRows, onChange })=>{
55
+ const containerRef = useRef(null);
56
+ const { onCommittedSnapshot, releaseTemplateComponent, renderTemplateComponent } = useGridChangeHandler();
57
+ const layoutOptions = useGridLayoutOptions();
58
+ const getSavedGrid = useSavedGrid();
59
+ const [[children, layout, persistedPlaceholderIds], setInitialGrid] = useState(()=>{
60
+ const savedGrid = getSavedGrid?.(id);
61
+ if (savedGrid) {
62
+ const { components: savedChildren, layout: savedLayout } = savedGrid;
63
+ return [
64
+ Object.values(savedChildren),
65
+ savedLayout,
66
+ savedGrid.placeholderIds
67
+ ];
68
+ }
69
+ if (colsAndRows) {
70
+ const reactElements = asReactElements(childrenProp);
71
+ const layoutDescriptor = layoutDescriptorFromChildren(colsAndRows, reactElements);
72
+ return [
73
+ reactElements,
74
+ layoutDescriptor,
75
+ void 0
76
+ ];
77
+ }
78
+ throw Error("[useGridLayout] no saved grid details available and no layout provided. Either pass layout props or provide a layout using GridLayoutProvider");
79
+ });
80
+ const [childElements, setChildElements] = useState(children);
81
+ const childrenRef = useRef(childElements);
82
+ const contentRegistryRef = useRef(new Map(children.map((element)=>[
83
+ element.props.id,
84
+ element
85
+ ])));
86
+ const savedGridReaderRef = useRef(getSavedGrid);
87
+ useEffect(()=>{
88
+ if (savedGridReaderRef.current === getSavedGrid) return;
89
+ savedGridReaderRef.current = getSavedGrid;
90
+ const savedGrid = getSavedGrid?.(id);
91
+ if (savedGrid) {
92
+ const nextChildren = Object.values(savedGrid.components);
93
+ childrenRef.current = nextChildren;
94
+ contentRegistryRef.current = new Map(nextChildren.map((element)=>[
95
+ element.props.id,
96
+ element
97
+ ]));
98
+ setChildElements(nextChildren);
99
+ setInitialGrid([
100
+ nextChildren,
101
+ savedGrid.layout,
102
+ savedGrid.placeholderIds
103
+ ]);
104
+ }
105
+ }, [
106
+ getSavedGrid,
107
+ id
108
+ ]);
109
+ const setChildren = useCallback((newChildren)=>{
110
+ if (isSimpleStateValue(newChildren)) childrenRef.current = newChildren;
111
+ else {
112
+ const { current: prev } = childrenRef;
113
+ childrenRef.current = newChildren(prev);
114
+ }
115
+ contentRegistryRef.current = new Map(childrenRef.current.map((element)=>[
116
+ element.props.id,
117
+ element
118
+ ]));
119
+ setChildElements(childrenRef.current);
120
+ }, []);
121
+ const [gridModel, gridLayoutModel, containerCallback] = useMemo(()=>{
122
+ const gridModel = new GridModel(id, layout);
123
+ for (const placeholderId of persistedPlaceholderIds ?? [])gridModel.getChildItem(placeholderId, true).type = "placeholder";
124
+ for (const [stackId, items] of gridModel.getStackedChildItems())if (void 0 === gridModel.getChildItem(stackId)) {
125
+ const { column, row } = getSharedGridPosition(items);
126
+ const stackTemplate = children.find((element)=>isGridLayoutStackedItem(element) && element.props.id === stackId);
127
+ gridModel.setTabState(stackId, items, getActiveIndex(items));
128
+ gridModel.addChildItem(new GridModelChildItem({
129
+ column,
130
+ id: stackId,
131
+ minHeight: stackTemplate?.props.minHeight,
132
+ minWidth: stackTemplate?.props.minWidth,
133
+ resizeable: stackTemplate?.props.resizeable,
134
+ row,
135
+ type: "stacked-content"
136
+ }));
137
+ }
138
+ if (void 0 === persistedPlaceholderIds) gridModel.createPlaceholders();
139
+ const gridLayoutModel = new GridLayoutModel(gridModel);
140
+ const callbackRef = (el)=>{
141
+ if (el) containerRef.current = el;
142
+ };
143
+ return [
144
+ gridModel,
145
+ gridLayoutModel,
146
+ callbackRef
147
+ ];
148
+ }, [
149
+ children,
150
+ id,
151
+ layout,
152
+ persistedPlaceholderIds
153
+ ]);
154
+ const gridController = useMemo(()=>new GridController(gridModel, 0, new LegacyGridCommandExecutor(gridModel, gridLayoutModel)), [
155
+ gridLayoutModel,
156
+ gridModel
157
+ ]);
158
+ const snapshot = useGridControllerSnapshot(gridController);
159
+ const dragCoordinator = useMemo(()=>new GridDragCoordinator(id, gridController), [
160
+ gridController,
161
+ id
162
+ ]);
163
+ const provisionalTemplateRef = useRef(void 0);
164
+ const pendingExistingDragRef = useRef(void 0);
165
+ const clearPendingExistingDrag = useCallback(()=>{
166
+ if (pendingExistingDragRef.current) {
167
+ const { id, ownerWindow } = pendingExistingDragRef.current;
168
+ ownerWindow.clearTimeout(id);
169
+ pendingExistingDragRef.current = void 0;
170
+ }
171
+ }, []);
172
+ const clearDragAppearance = useCallback(()=>{
173
+ containerRef.current?.classList.remove("vuuDragging");
174
+ }, []);
175
+ useEffect(()=>()=>{
176
+ clearPendingExistingDrag();
177
+ clearDragAppearance();
178
+ dragCoordinator.dispose();
179
+ }, [
180
+ clearDragAppearance,
181
+ clearPendingExistingDrag,
182
+ dragCoordinator
183
+ ]);
184
+ const contentIds = useMemo(()=>new Set(childElements.filter(isGridLayoutItem).map(({ props: { id } })=>id)), [
185
+ childElements
186
+ ]);
187
+ const stackTemplates = useMemo(()=>new Map(childElements.filter(isGridLayoutStackedItem).map((element)=>[
188
+ element.props.id,
189
+ element
190
+ ])), [
191
+ childElements
192
+ ]);
193
+ const renderedChildren = snapshot.items.flatMap(({ id: itemId })=>{
194
+ const element = contentRegistryRef.current.get(itemId);
195
+ return element ? [
196
+ element
197
+ ] : [];
198
+ });
199
+ const nonContentGridItems = useMemo(()=>{
200
+ const placeholderPreview = "previewing" === dragCoordinator.state.phase && "palette-template" === dragCoordinator.state.source.kind && "replace" === dragCoordinator.state.target.intent.kind ? {
201
+ id: dragCoordinator.state.target.targetId,
202
+ snapshotId: dragCoordinator.state.source.item.id
203
+ } : void 0;
204
+ return {
205
+ placeholders: snapshot.items.filter(({ id: itemId })=>!contentIds.has(itemId)).map(({ id: snapshotId })=>placeholderPreview?.snapshotId === snapshotId ? placeholderPreview : {
206
+ id: snapshotId,
207
+ snapshotId
208
+ }),
209
+ splitters: gridLayoutModel.createSplitters(),
210
+ stackIds: snapshot.stacks.map(({ id: stackId })=>stackId)
211
+ };
212
+ }, [
213
+ contentIds,
214
+ dragCoordinator,
215
+ gridLayoutModel,
216
+ snapshot
217
+ ]);
218
+ const saveGridLayout = useCallback((id, gridLayout)=>{
219
+ onChange?.(id, gridLayout);
220
+ }, [
221
+ onChange
222
+ ]);
223
+ const handleDragStart = useCallback((evt, options)=>{
224
+ if ("text/plain" === options.type) {
225
+ clearPendingExistingDrag();
226
+ const ownerWindow = evt.currentTarget.ownerDocument.defaultView;
227
+ if (ownerWindow) {
228
+ const timeoutId = ownerWindow.setTimeout(()=>{
229
+ pendingExistingDragRef.current = void 0;
230
+ const result = dragCoordinator.begin({
231
+ itemId: options.id,
232
+ kind: "existing-item",
233
+ sourceGridId: id
234
+ });
235
+ if (!result.ok) throw Error(result.error.message);
236
+ });
237
+ pendingExistingDragRef.current = {
238
+ id: timeoutId,
239
+ ownerWindow
240
+ };
241
+ }
242
+ }
243
+ }, [
244
+ clearPendingExistingDrag,
245
+ dragCoordinator,
246
+ id
247
+ ]);
248
+ const handleCancelDrag = useCallback(()=>{
249
+ clearPendingExistingDrag();
250
+ clearDragAppearance();
251
+ if ("dragging" === dragCoordinator.state.phase || "previewing" === dragCoordinator.state.phase) {
252
+ const result = dragCoordinator.cancel();
253
+ if (!result.ok) throw Error(result.error.message);
254
+ }
255
+ if (provisionalTemplateRef.current) {
256
+ releaseTemplateComponent?.(provisionalTemplateRef.current.itemId);
257
+ provisionalTemplateRef.current = void 0;
258
+ }
259
+ }, [
260
+ clearDragAppearance,
261
+ clearPendingExistingDrag,
262
+ dragCoordinator,
263
+ releaseTemplateComponent
264
+ ]);
265
+ const releaseProvisionalTemplate = useCallback(()=>{
266
+ const provisional = provisionalTemplateRef.current;
267
+ if (provisional) {
268
+ releaseTemplateComponent?.(provisional.itemId);
269
+ provisionalTemplateRef.current = void 0;
270
+ }
271
+ }, [
272
+ releaseTemplateComponent
273
+ ]);
274
+ const handleDragEnd = useCallback((_evt, dropped)=>{
275
+ clearDragAppearance();
276
+ if (!dropped) handleCancelDrag();
277
+ provisionalTemplateRef.current = void 0;
278
+ }, [
279
+ clearDragAppearance,
280
+ handleCancelDrag
281
+ ]);
282
+ const addChildComponent = useCallback((component, { column, header, id, row, title, type })=>{
283
+ if ("stacked-content" === type) {
284
+ const stackedGridItem = getGridItemChild(childrenRef.current, id);
285
+ const newChild = addChildToStackedGridItem(stackedGridItem, component);
286
+ setChildren((c)=>c.map((child)=>child.props.id === id ? newChild : child));
287
+ } else {
288
+ const newChild = /*#__PURE__*/ jsx(GridLayoutItem, {
289
+ "data-drop-target": true,
290
+ header: header,
291
+ id: id,
292
+ resizeable: "hv",
293
+ style: {
294
+ gridArea: getGridArea({
295
+ column,
296
+ row
297
+ })
298
+ },
299
+ title: title,
300
+ children: component
301
+ }, id);
302
+ setChildren((c)=>c.concat(newChild));
303
+ }
304
+ }, [
305
+ setChildren
306
+ ]);
307
+ const replaceChildComponent = useCallback((targetItemId, component, { column, header, id, row, title })=>{
308
+ const newChild = /*#__PURE__*/ jsx(GridLayoutItem, {
309
+ "data-drop-target": true,
310
+ header: header,
311
+ id: id,
312
+ resizeable: "hv",
313
+ style: {
314
+ gridArea: getGridArea({
315
+ column,
316
+ row
317
+ })
318
+ },
319
+ title: title,
320
+ children: component
321
+ }, id);
322
+ setChildren((c)=>c.map((child)=>child.props.id === targetItemId ? newChild : child));
323
+ }, [
324
+ setChildren
325
+ ]);
326
+ const prepareDragSource = useCallback((dragSource)=>{
327
+ if (sourceIsTemplate(dragSource)) {
328
+ let provisional = provisionalTemplateRef.current;
329
+ if (!provisional || provisional.template !== dragSource) {
330
+ if (provisional) releaseTemplateComponent?.(provisional.itemId);
331
+ const itemId = uuid();
332
+ const component = isTypedComponentTemplate(dragSource) ? renderTemplateComponent?.(dragSource, itemId) : (()=>{
333
+ const { label: _label, ...layout } = JSON.parse(dragSource.componentJson);
334
+ return layoutFromJson(layout);
335
+ })();
336
+ if (!component) throw Error(`[useGridLayout#${id}] component template could not be rendered`);
337
+ provisional = {
338
+ component,
339
+ itemId,
340
+ template: dragSource
341
+ };
342
+ provisionalTemplateRef.current = provisional;
343
+ }
344
+ if ("dragging" !== dragCoordinator.state.phase && "previewing" !== dragCoordinator.state.phase) {
345
+ const begun = dragCoordinator.begin({
346
+ item: {
347
+ column: {
348
+ span: 1,
349
+ start: 1
350
+ },
351
+ dropTarget: true,
352
+ header: layoutOptions?.newChildItem.header,
353
+ id: provisional.itemId,
354
+ resizeable: "hv",
355
+ row: {
356
+ span: 1,
357
+ start: 1
358
+ },
359
+ title: dragSource.label ?? "New Item"
360
+ },
361
+ kind: "palette-template",
362
+ templateId: isTypedComponentTemplate(dragSource) ? `${dragSource.component.type}@${dragSource.component.version}` : dragSource.componentJson
363
+ });
364
+ if (!begun.ok) {
365
+ releaseProvisionalTemplate();
366
+ throw Error(begun.error.message);
367
+ }
368
+ }
369
+ } else if (sourceIsComponent(dragSource) && "dragging" !== dragCoordinator.state.phase && "previewing" !== dragCoordinator.state.phase) {
370
+ clearPendingExistingDrag();
371
+ const begun = dragCoordinator.begin({
372
+ itemId: dragSource.id,
373
+ kind: "existing-item",
374
+ sourceGridId: dragSource.layoutId
375
+ });
376
+ if (!begun.ok) throw Error(begun.error.message);
377
+ } else if (sourceIsTabbedComponent(dragSource) && "dragging" !== dragCoordinator.state.phase && "previewing" !== dragCoordinator.state.phase) {
378
+ const begun = dragCoordinator.begin({
379
+ itemId: dragSource.tab.id,
380
+ kind: "stack-member",
381
+ selected: dragSource.isSelectedTab,
382
+ sourceGridId: dragSource.layoutId,
383
+ stackId: dragSource.tabsId
384
+ });
385
+ if (!begun.ok) throw Error(begun.error.message);
386
+ }
387
+ }, [
388
+ clearPendingExistingDrag,
389
+ dragCoordinator,
390
+ id,
391
+ layoutOptions?.newChildItem.header,
392
+ releaseTemplateComponent,
393
+ releaseProvisionalTemplate,
394
+ renderTemplateComponent
395
+ ]);
396
+ const handleDragLeave = useCallback(()=>{
397
+ const templateDrag = "idle" !== dragCoordinator.state.phase && "palette-template" === dragCoordinator.state.source.kind;
398
+ if ("previewing" === dragCoordinator.state.phase) {
399
+ const result = dragCoordinator.clearPreview();
400
+ if (!result.ok) throw Error(result.error.message);
401
+ }
402
+ if (templateDrag && "dragging" === dragCoordinator.state.phase) {
403
+ const result = dragCoordinator.cancel();
404
+ if (!result.ok) throw Error(result.error.message);
405
+ }
406
+ releaseProvisionalTemplate();
407
+ }, [
408
+ dragCoordinator,
409
+ releaseProvisionalTemplate
410
+ ]);
411
+ const handleDragPreview = useCallback((targetItemId, dragSource, position)=>{
412
+ const targetGridItem = gridModel.getChildItem(targetItemId, true);
413
+ if ("centre" === position && isStackedItem(targetGridItem)) return false;
414
+ prepareDragSource(dragSource);
415
+ const targetId = isGridLayoutSplitDirection(position) && isStackedItem(targetGridItem) ? targetGridItem.stackId : targetItemId;
416
+ const intent = isGridLayoutSplitDirection(position) ? {
417
+ kind: "split",
418
+ position
419
+ } : "centre" === position ? {
420
+ kind: "replace"
421
+ } : "header" === position ? {
422
+ kind: "create-stack"
423
+ } : void 0;
424
+ if (!intent) return false;
425
+ const previewed = dragCoordinator.preview({
426
+ gridId: id,
427
+ intent,
428
+ targetId
429
+ });
430
+ if (!previewed.ok) {
431
+ handleCancelDrag();
432
+ return false;
433
+ }
434
+ const cleared = dragCoordinator.clearPreview();
435
+ if (!cleared.ok) throw Error(cleared.error.message);
436
+ return true;
437
+ }, [
438
+ dragCoordinator,
439
+ gridModel,
440
+ handleCancelDrag,
441
+ id,
442
+ prepareDragSource
443
+ ]);
444
+ const handleDrop = useCallback((targetItemId, dragSource, position)=>{
445
+ containerRef.current?.classList.remove("vuuDragging");
446
+ prepareDragSource(dragSource);
447
+ const existingPlaceholderPreview = "centre" === position && "previewing" === dragCoordinator.state.phase && "palette-template" === dragCoordinator.state.source.kind && "replace" === dragCoordinator.state.target.intent.kind && dragCoordinator.state.target.targetId === targetItemId;
448
+ const replacingPlaceholder = "centre" === position && !contentRegistryRef.current.has(targetItemId);
449
+ const targetGridItem = existingPlaceholderPreview ? void 0 : gridModel.getChildItem(targetItemId, true);
450
+ if (!isGridLayoutSplitDirection(position) && "centre" !== position && "header" !== position) {
451
+ handleCancelDrag();
452
+ return false;
453
+ }
454
+ if ("centre" === position && targetGridItem && isStackedItem(targetGridItem)) {
455
+ handleCancelDrag();
456
+ return false;
457
+ }
458
+ const targetId = isGridLayoutSplitDirection(position) && targetGridItem && isStackedItem(targetGridItem) ? targetGridItem.stackId : targetItemId;
459
+ const intent = isGridLayoutSplitDirection(position) ? {
460
+ kind: "split",
461
+ position
462
+ } : "centre" === position ? {
463
+ kind: "replace"
464
+ } : {
465
+ kind: "create-stack"
466
+ };
467
+ const preview = existingPlaceholderPreview || dragCoordinator.preview({
468
+ gridId: id,
469
+ intent,
470
+ targetId
471
+ }).ok;
472
+ if (!preview) {
473
+ handleCancelDrag();
474
+ return false;
475
+ }
476
+ const committed = dragCoordinator.commit();
477
+ if (!committed.ok) throw Error(committed.error.message);
478
+ clearDragAppearance();
479
+ if (sourceIsTemplate(dragSource) || "centre" !== position) {
480
+ if (sourceIsTemplate(dragSource)) {
481
+ const provisional = provisionalTemplateRef.current;
482
+ if (!provisional) throw Error(`[useGridLayout#${id}] template item id not allocated`);
483
+ const newItem = gridModel.getChildItem(provisional.itemId, true);
484
+ if ("centre" !== position || replacingPlaceholder) addChildComponent(provisional.component, newItem);
485
+ else replaceChildComponent(targetItemId, provisional.component, newItem);
486
+ provisionalTemplateRef.current = void 0;
487
+ }
488
+ } else setChildren((current)=>current.filter((child)=>child.props.id !== targetItemId));
489
+ return true;
490
+ }, [
491
+ addChildComponent,
492
+ clearDragAppearance,
493
+ gridModel,
494
+ handleCancelDrag,
495
+ id,
496
+ dragCoordinator,
497
+ prepareDragSource,
498
+ replaceChildComponent,
499
+ setChildren
500
+ ]);
501
+ const handleDetachTab = useCallback(({ gridId, itemId, tabsId })=>{
502
+ if (gridId === id) {
503
+ const stack = snapshot.stacks.find(({ id })=>id === tabsId);
504
+ if (!itemId || !stack?.itemIds.includes(itemId)) throw Error(`[useGridLayout#${id}] cannot identify dragged tab #${itemId}`);
505
+ const result = dragCoordinator.begin({
506
+ itemId,
507
+ kind: "stack-member",
508
+ selected: stack?.selectedItemId === itemId,
509
+ sourceGridId: id,
510
+ stackId: tabsId
511
+ });
512
+ if (!result.ok) throw Error(result.error.message);
513
+ }
514
+ }, [
515
+ dragCoordinator,
516
+ id,
517
+ snapshot
518
+ ]);
519
+ const handleCancelTabDrag = useCallback(({ gridId })=>{
520
+ if (gridId === id && ("dragging" === dragCoordinator.state.phase || "previewing" === dragCoordinator.state.phase)) {
521
+ const result = dragCoordinator.cancel();
522
+ if (!result.ok) throw Error(result.error.message);
523
+ }
524
+ }, [
525
+ dragCoordinator,
526
+ id
527
+ ]);
528
+ const handleDropStackedItem = useCallback(({ dragSource, tabsId: targetStackItemId, dropPosition })=>{
529
+ if (!targetStackItemId || !dropPosition) throw Error("[useGridLayout] handleDropStackedItem no details of the stacked drop target");
530
+ const targetStack = snapshot.stacks.find(({ id })=>id === targetStackItemId);
531
+ if (!targetStack) throw Error(`[useGridLayout#${id}] stack drop target #${targetStackItemId} not found`);
532
+ const targetItemId = targetStack.itemIds.find((itemId)=>itemId === dropPosition.target);
533
+ if (!targetItemId) throw Error(`[useGridLayout#${id}] stack item drop target #${dropPosition.target} not found`);
534
+ let component;
535
+ let newChildId;
536
+ if (sourceIsTemplate(dragSource)) {
537
+ newChildId = uuid();
538
+ component = isTypedComponentTemplate(dragSource) ? renderTemplateComponent?.(dragSource, newChildId) : (()=>{
539
+ const { label: _label, ...layout } = JSON.parse(dragSource.componentJson);
540
+ return layoutFromJson(layout);
541
+ })();
542
+ if (!component) throw Error(`[useGridLayout#${id}] component template could not be rendered`);
543
+ const begun = dragCoordinator.begin({
544
+ item: {
545
+ column: {
546
+ span: 1,
547
+ start: 1
548
+ },
549
+ dropTarget: true,
550
+ header: layoutOptions?.newChildItem.header,
551
+ id: newChildId,
552
+ resizeable: "hv",
553
+ row: {
554
+ span: 1,
555
+ start: 1
556
+ },
557
+ title: dragSource.label ?? "New Item"
558
+ },
559
+ kind: "palette-template",
560
+ templateId: isTypedComponentTemplate(dragSource) ? `${dragSource.component.type}@${dragSource.component.version}` : dragSource.componentJson
561
+ });
562
+ if (!begun.ok) throw Error(begun.error.message);
563
+ } else if (sourceIsComponent(dragSource) && "idle" === dragCoordinator.state.phase) {
564
+ const begun = dragCoordinator.begin({
565
+ itemId: dragSource.id,
566
+ kind: "existing-item",
567
+ sourceGridId: dragSource.layoutId
568
+ });
569
+ if (!begun.ok) throw Error(begun.error.message);
570
+ }
571
+ const preview = dragCoordinator.preview({
572
+ gridId: id,
573
+ intent: {
574
+ kind: "stack",
575
+ position: dropPosition.position,
576
+ targetItemId
577
+ },
578
+ targetId: targetStackItemId
579
+ });
580
+ if (!preview.ok) {
581
+ if ("dragging" === dragCoordinator.state.phase || "previewing" === dragCoordinator.state.phase) dragCoordinator.cancel();
582
+ return;
583
+ }
584
+ const committed = dragCoordinator.commit();
585
+ if (!committed.ok) throw Error(committed.error.message);
586
+ clearDragAppearance();
587
+ if (component && newChildId) addChildComponent(component, gridModel.getChildItem(newChildId, true));
588
+ }, [
589
+ addChildComponent,
590
+ clearDragAppearance,
591
+ dragCoordinator,
592
+ gridModel,
593
+ id,
594
+ layoutOptions?.newChildItem.header,
595
+ renderTemplateComponent,
596
+ snapshot
597
+ ]);
598
+ const dispatchGridLayoutAction = useCallback((action)=>{
599
+ switch(action.type){
600
+ case "close":
601
+ throwForGridCommandFailure(gridController.dispatch({
602
+ itemId: action.id,
603
+ reason: "close",
604
+ type: "remove-item"
605
+ }));
606
+ setChildren((children)=>children.filter((child)=>child.props.id !== action.id));
607
+ break;
608
+ case "rename-tab":
609
+ throwForGridCommandFailure(gridController.dispatch({
610
+ itemId: action.id,
611
+ title: action.title,
612
+ type: "rename-item"
613
+ }));
614
+ break;
615
+ case "add-tabbed-child":
616
+ {
617
+ const { componentTemplate, title, stackId } = action;
618
+ const { dropTarget = true } = componentTemplate;
619
+ const { column, row } = gridModel.getChildItem(stackId, true);
620
+ const newChildId = uuid();
621
+ const component = isTypedComponentTemplate(componentTemplate) ? renderTemplateComponent?.(componentTemplate, newChildId) : layoutFromJson({
622
+ ...JSON.parse(componentTemplate.componentJson),
623
+ title
624
+ });
625
+ if (!component) throw Error(`[useGridLayout#${id}] component template could not be rendered`);
626
+ throwForGridCommandFailure(gridController.dispatch({
627
+ item: {
628
+ id: newChildId,
629
+ column: {
630
+ span: column.end - column.start,
631
+ start: column.start
632
+ },
633
+ dropTarget: dropTarget || void 0,
634
+ header: layoutOptions?.newChildItem.header,
635
+ resizeable: "hv",
636
+ row: {
637
+ span: row.end - row.start,
638
+ start: row.start
639
+ },
640
+ title: title ?? componentTemplate.label ?? "New Item"
641
+ },
642
+ stackId,
643
+ type: "add-stack-item"
644
+ }));
645
+ const gridModelChildItem = gridModel.getChildItem(newChildId, true);
646
+ addChildComponent(component, gridModelChildItem);
647
+ throwForGridCommandFailure(gridController.dispatch({
648
+ itemId: newChildId,
649
+ stackId,
650
+ type: "select-stack-item"
651
+ }));
652
+ }
653
+ break;
654
+ case "resize-grid-column":
655
+ throwForGridCommandFailure(gridController.dispatch({
656
+ index: action.trackIndex,
657
+ size: action.value,
658
+ track: "column",
659
+ type: "resize-track"
660
+ }));
661
+ break;
662
+ case "resize-grid-row":
663
+ throwForGridCommandFailure(gridController.dispatch({
664
+ index: action.trackIndex,
665
+ size: action.value,
666
+ track: "row",
667
+ type: "resize-track"
668
+ }));
669
+ break;
670
+ case "select-tab":
671
+ throwForGridCommandFailure(gridController.dispatch({
672
+ itemId: action.itemId,
673
+ stackId: action.stackId,
674
+ type: "select-stack-item"
675
+ }));
676
+ break;
677
+ case "switch-tab":
678
+ throw new GridCommandExecutionError({
679
+ code: "UNSUPPORTED_ACTION",
680
+ message: "GridLayoutAction 'switch-tab' has no supported legacy semantics"
681
+ });
682
+ default:
683
+ return assertNeverGridLayoutAction(action);
684
+ }
685
+ }, [
686
+ addChildComponent,
687
+ gridController,
688
+ gridModel,
689
+ id,
690
+ layoutOptions?.newChildItem.header,
691
+ renderTemplateComponent,
692
+ setChildren
693
+ ]);
694
+ useEffect(()=>gridController.subscribeCommitted((transition)=>{
695
+ const { snapshot } = transition;
696
+ saveGridLayout(id, gridSnapshotToGridLayoutDescriptor(snapshot));
697
+ onCommittedSnapshot?.(transition, gridModel.getPlaceholders().map(({ id: placeholderId })=>placeholderId));
698
+ }), [
699
+ gridController,
700
+ gridModel,
701
+ id,
702
+ onCommittedSnapshot,
703
+ saveGridLayout
704
+ ]);
705
+ return {
706
+ children: renderedChildren,
707
+ containerCallback,
708
+ containerRef,
709
+ dispatchGridLayoutAction,
710
+ gridController,
711
+ gridLayoutModel,
712
+ gridModel,
713
+ gridSnapshot: snapshot,
714
+ nonContentGridItems,
715
+ onCancelDrag: handleCancelDrag,
716
+ onCancelTabDrag: handleCancelTabDrag,
717
+ onDetachTab: handleDetachTab,
718
+ onDragEnd: handleDragEnd,
719
+ onDragLeave: handleDragLeave,
720
+ onDragPreview: handleDragPreview,
721
+ onDragStart: handleDragStart,
722
+ onDrop: handleDrop,
723
+ onDropStackedItem: handleDropStackedItem,
724
+ stackTemplates
725
+ };
726
+ };
727
+ export { useGridLayout };