@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,119 @@
1
+ const css = `
2
+ .vuuGridLayout {
3
+ --vuu-layout-tabs-height: 32px;
4
+ --vuu-grid-gap: 6px;
5
+ --vuu-grid-header-height: 25px;
6
+ padding: var(--vuu-grid-gap);
7
+ display: grid;
8
+
9
+ &.vuuResizing {
10
+ transition: all .3s;
11
+ }
12
+
13
+ &.vuuFullPage {
14
+ width: 100vw;
15
+ height: 100vh;
16
+ }
17
+ }
18
+
19
+ .vuu-detached {
20
+ visibility: hidden;
21
+ }
22
+
23
+ .vuu-stacked {
24
+ &.has-h-splitter {
25
+ margin-top: calc(var(--vuu-layout-tabs-height) + 7px);
26
+ }
27
+
28
+ &:not(.has-h-splitter) {
29
+ margin-top: var(--vuu-layout-tabs-height);
30
+ }
31
+ }
32
+
33
+ .vuuGridLayoutItemHeader-close {
34
+ margin-left: auto;
35
+ }
36
+
37
+ .vuuGridLayoutStackedItemContent, .vuuGridLayoutItemContent {
38
+ flex: auto;
39
+ position: relative;
40
+ }
41
+
42
+ .vuuGridLayoutStackedItemContent > *, .vuuGridLayoutItemContent > * {
43
+ position: absolute;
44
+ inset: 0;
45
+ }
46
+
47
+ .vuuDropTarget-east {
48
+ --grid-dropzone-top: 0px;
49
+ --grid-dropzone-left: 50%;
50
+ --grid-dropzone-bottom: 0px;
51
+ --grid-dropzone-right: 0px;
52
+ }
53
+
54
+ .vuuDropTarget-west {
55
+ --grid-dropzone-top: 0px;
56
+ --grid-dropzone-left: 0px;
57
+ --grid-dropzone-bottom: 0px;
58
+ --grid-dropzone-right: 50%;
59
+ }
60
+
61
+ .vuuDropTarget-north {
62
+ --grid-dropzone-top: 0px;
63
+ --grid-dropzone-left: 0px;
64
+ --grid-dropzone-bottom: 50%;
65
+ --grid-dropzone-right: 0px;
66
+ }
67
+
68
+ .vuuDropTarget-south {
69
+ --grid-dropzone-top: 50%;
70
+ --grid-dropzone-left: 0px;
71
+ --grid-dropzone-bottom: 0px;
72
+ --grid-dropzone-right: 0px;
73
+ }
74
+
75
+ .vuuDropTarget-centre {
76
+ --grid-dropzone-top: 0px;
77
+ --grid-dropzone-left: 0px;
78
+ --grid-dropzone-bottom: 0px;
79
+ --grid-dropzone-right: 0px;
80
+ }
81
+
82
+ .vuu-stacked {
83
+ & > .vuuDropTarget-north, & > .vuuDropTarget-east, & > .vuuDropTarget-west {
84
+ --grid-dropzone-top: -32px;
85
+ }
86
+ }
87
+
88
+ .vuuDropTarget-centre:after, .vuuDropTarget-north:after, .vuuDropTarget-east:after, .vuuDropTarget-south:after, .vuuDropTarget-west:after {
89
+ content: "";
90
+ opacity: .3;
91
+ pointer-events: none;
92
+ top: var(--grid-dropzone-top);
93
+ right: var(--grid-dropzone-right);
94
+ bottom: var(--grid-dropzone-bottom);
95
+ left: var(--grid-dropzone-left);
96
+ z-index: 100;
97
+ background-color: #6495ed;
98
+ transition-property: top, left, right, bottom;
99
+ transition-duration: .3s;
100
+ transition-timing-function: ease-in-out;
101
+ position: absolute;
102
+ }
103
+
104
+ .vuuDropTarget-header:after {
105
+ content: "";
106
+ opacity: .3;
107
+ pointer-events: none;
108
+ z-index: 100;
109
+ background-color: red;
110
+ transition-property: top, left, right, bottom;
111
+ transition-duration: .3s;
112
+ transition-timing-function: ease-in-out;
113
+ position: absolute;
114
+ inset: 0;
115
+ }
116
+
117
+
118
+ `;
119
+ export default css;
@@ -0,0 +1,120 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { registerComponent } from "@vuu-ui/vuu-utils";
3
+ import { useIdMemo } from "@salt-ds/core";
4
+ import { useComponentCssInjection } from "@salt-ds/styles";
5
+ import { useWindow } from "@salt-ds/window";
6
+ import clsx from "clsx";
7
+ import { cloneElement } from "react";
8
+ import { DragDropProviderNext } from "./drag-drop-next/DragDropProviderNext.js";
9
+ import { getGridArea } from "./grid-layout-utils.js";
10
+ import { GridLayoutContext } from "./GridLayoutContext.js";
11
+ import { GridLayoutStackedItem } from "./GridLayoutStackedtem.js";
12
+ import { GridPlaceholder } from "./GridPlaceholder.js";
13
+ import { useGridLayout } from "./useGridLayout.js";
14
+ import { useGridSplitterResizing } from "./useGridSplitterResizing.js";
15
+ import { GridSplitter } from "./GridSplitter.js";
16
+ import GridLayout from "./GridLayout.css";
17
+ const classBase = "vuuGridLayout";
18
+ const startsAtHorizontalSplitter = (splitter, splitters)=>"vertical" === splitter.ariaOrientation && splitters.some((candidate)=>"horizontal" === candidate.ariaOrientation && candidate.row.start === splitter.row.start && candidate.column.start <= splitter.column.start && candidate.column.end > splitter.column.start);
19
+ const NO_DRAG_SOURCES = {};
20
+ const GridLayout_GridLayout = ({ id: idProp, children: childrenProp, className, "full-page": fullPage, colsAndRows, onClick, onChange, placeholderContent, rowResizeDistribution = "adjacent", style: styleProp, ...htmlAttributes })=>{
21
+ const targetWindow = useWindow();
22
+ useComponentCssInjection({
23
+ testId: "vuu-grid-layout",
24
+ css: GridLayout,
25
+ window: targetWindow
26
+ });
27
+ const id = useIdMemo(idProp);
28
+ const { children, containerCallback, dispatchGridLayoutAction, gridController, gridLayoutModel, gridModel, gridSnapshot, nonContentGridItems: { placeholders, splitters, stackIds }, onCancelDrag, onCancelTabDrag, onDetachTab, onDragEnd, onDragLeave, onDragPreview, onDragStart, onDrop, onDropStackedItem, stackTemplates } = useGridLayout({
29
+ children: childrenProp,
30
+ id,
31
+ colsAndRows,
32
+ onChange
33
+ });
34
+ const splitterLayoutProps = useGridSplitterResizing({
35
+ gridLayoutModel,
36
+ gridModel,
37
+ gridController,
38
+ id,
39
+ onClick,
40
+ rowResizeDistribution
41
+ });
42
+ const style = {
43
+ gridTemplateColumns: gridSnapshot.columns.map(({ size })=>size).join(" "),
44
+ gridTemplateRows: gridSnapshot.rows.map(({ size })=>size).join(" "),
45
+ ...styleProp
46
+ };
47
+ const snapshotItemById = new Map(gridSnapshot.items.map((item)=>[
48
+ item.id,
49
+ item
50
+ ]));
51
+ return /*#__PURE__*/ jsx(GridLayoutContext.Provider, {
52
+ value: {
53
+ dispatchGridLayoutAction,
54
+ gridController,
55
+ gridLayoutModel,
56
+ gridModel,
57
+ gridSnapshot,
58
+ id,
59
+ onDragEnd,
60
+ onDragLeave,
61
+ onDragPreview,
62
+ onDragStart,
63
+ onDrop
64
+ },
65
+ children: /*#__PURE__*/ jsx(DragDropProviderNext, {
66
+ dragSources: NO_DRAG_SOURCES,
67
+ onCancelDrag: onCancelDrag,
68
+ onCancelTabDrag: onCancelTabDrag,
69
+ onDetachTab: onDetachTab,
70
+ onDrop: onDropStackedItem,
71
+ children: /*#__PURE__*/ jsxs("div", {
72
+ ...htmlAttributes,
73
+ ...splitterLayoutProps,
74
+ id: id,
75
+ ref: containerCallback,
76
+ style: style,
77
+ className: clsx(classBase, className, {
78
+ vuuFullPage: fullPage
79
+ }),
80
+ children: [
81
+ stackIds.map((stackId)=>{
82
+ const template = stackTemplates.get(stackId);
83
+ return template ? /*#__PURE__*/ cloneElement(template, {
84
+ key: stackId
85
+ }) : /*#__PURE__*/ jsx(GridLayoutStackedItem, {
86
+ id: stackId
87
+ }, stackId);
88
+ }),
89
+ children,
90
+ placeholders.map(({ id: placeholderId, snapshotId })=>{
91
+ const placeholder = snapshotItemById.get(snapshotId);
92
+ if (!placeholder) throw Error(`[GridLayout] canonical placeholder #${placeholderId} not found`);
93
+ return /*#__PURE__*/ jsx(GridPlaceholder, {
94
+ id: placeholderId,
95
+ style: {
96
+ gridArea: `${placeholder.row.start}/${placeholder.column.start}/${placeholder.row.start + placeholder.row.span}/${placeholder.column.start + placeholder.column.span}`
97
+ },
98
+ children: placeholderContent
99
+ }, placeholderId);
100
+ }),
101
+ splitters.map((splitter)=>/*#__PURE__*/ jsx(GridSplitter, {
102
+ "aria-controls": splitter.controls,
103
+ ariaOrientation: splitter.ariaOrientation,
104
+ "data-resized-child-items-after": splitter.resizedChildItems.after.join(" "),
105
+ "data-resized-child-items-before": splitter.resizedChildItems.before.join(" "),
106
+ id: splitter.id,
107
+ offsetStart: startsAtHorizontalSplitter(splitter, splitters),
108
+ orientation: splitter.orientation,
109
+ style: {
110
+ gridArea: getGridArea(splitter)
111
+ }
112
+ }, splitter.id))
113
+ ]
114
+ })
115
+ })
116
+ });
117
+ };
118
+ GridLayout_GridLayout.displayName = "Grid";
119
+ registerComponent("Grid", GridLayout_GridLayout, "container");
120
+ export { GridLayout_GridLayout as GridLayout };
@@ -0,0 +1,58 @@
1
+ import { createContext, useContext } from "react";
2
+ const unconfiguredGridLayoutDispatch = (action)=>console.log(`dispatch ${action.type}, have you forgotten to provide a GridLayoutProvider ?`);
3
+ const isTypedComponentTemplate = (template)=>"component" in template;
4
+ const sourceIsComponent = (source)=>source?.type === "component";
5
+ const sourceIsTabbedComponent = (source)=>source?.type === "tabbed-component";
6
+ const sourceIsTemplate = (source)=>source?.type === "template";
7
+ const GridLayoutContext = createContext({
8
+ dispatchGridLayoutAction: unconfiguredGridLayoutDispatch,
9
+ id: "",
10
+ onDragLeave: ()=>void 0,
11
+ onDragPreview: ()=>false,
12
+ onDragStart: ()=>console.log("no GridLayoutProvider"),
13
+ onDrop: ()=>false
14
+ });
15
+ const useGridLayoutDispatch = ()=>{
16
+ const { dispatchGridLayoutAction } = useContext(GridLayoutContext);
17
+ return dispatchGridLayoutAction;
18
+ };
19
+ const useGridLayoutDropHandler = ()=>{
20
+ const { onDrop } = useContext(GridLayoutContext);
21
+ return onDrop;
22
+ };
23
+ const useGridLayoutDragPreviewHandler = ()=>{
24
+ const { onDragPreview } = useContext(GridLayoutContext);
25
+ return onDragPreview;
26
+ };
27
+ const useGridLayoutDragLeaveHandler = ()=>{
28
+ const { onDragLeave } = useContext(GridLayoutContext);
29
+ return onDragLeave;
30
+ };
31
+ const useGridLayoutDragEndHandler = ()=>{
32
+ const { onDragEnd } = useContext(GridLayoutContext);
33
+ return onDragEnd;
34
+ };
35
+ const useGridLayoutDragStartHandler = ()=>{
36
+ const { onDragStart } = useContext(GridLayoutContext);
37
+ return onDragStart;
38
+ };
39
+ const useGridModel = ()=>{
40
+ const { gridModel } = useContext(GridLayoutContext);
41
+ if (gridModel) return gridModel;
42
+ throw Error("[useGridModel] no gridModel, did you forget to use a GridLayout");
43
+ };
44
+ const useGridController = ()=>{
45
+ const { gridController } = useContext(GridLayoutContext);
46
+ if (gridController) return gridController;
47
+ throw Error("[useGridController] no gridController, did you forget to use a GridLayout");
48
+ };
49
+ const useGridSnapshot = ()=>{
50
+ const { gridSnapshot } = useContext(GridLayoutContext);
51
+ if (gridSnapshot) return gridSnapshot;
52
+ throw Error("[useGridSnapshot] no gridSnapshot, did you forget to use a GridLayout");
53
+ };
54
+ const useGridLayoutId = ()=>{
55
+ const { id } = useContext(GridLayoutContext);
56
+ return id;
57
+ };
58
+ export { GridLayoutContext, isTypedComponentTemplate, sourceIsComponent, sourceIsTabbedComponent, sourceIsTemplate, useGridController, useGridLayoutDispatch, useGridLayoutDragEndHandler, useGridLayoutDragLeaveHandler, useGridLayoutDragPreviewHandler, useGridLayoutDragStartHandler, useGridLayoutDropHandler, useGridLayoutId, useGridModel, useGridSnapshot };
@@ -0,0 +1,307 @@
1
+ import { normalizeGridSnapshot } from "./grid-snapshot-adapters.js";
2
+ import { toJsonValue } from "./json-value.js";
3
+ const GRID_LAYOUT_DOCUMENT_KIND = "grid-layout";
4
+ const GRID_LAYOUT_DOCUMENT_VERSION = 2;
5
+ const cloneAndFreeze = (value)=>{
6
+ if ("object" != typeof value || null === value) return value;
7
+ if (Array.isArray(value)) return Object.freeze(value.map(cloneAndFreeze));
8
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, entry])=>[
9
+ key,
10
+ cloneAndFreeze(entry)
11
+ ])));
12
+ };
13
+ const immutableGridLayoutDocument = (document)=>cloneAndFreeze(document);
14
+ class GridLayoutDocumentCodecError extends Error {
15
+ issue;
16
+ constructor(issue){
17
+ super(`${issue.path}: ${issue.message}`), this.issue = issue;
18
+ this.name = "GridLayoutDocumentCodecError";
19
+ }
20
+ }
21
+ const failure = (code, message, path, version)=>({
22
+ error: {
23
+ code,
24
+ message,
25
+ path,
26
+ version
27
+ },
28
+ ok: false
29
+ });
30
+ const isRecord = (value)=>"object" == typeof value && null !== value && !Array.isArray(value);
31
+ const compareIds = (left, right)=>left < right ? -1 : left > right ? 1 : 0;
32
+ const durableItem = ({ column, componentInstanceId, contentVisible, dropTarget, header, id, minHeight, minWidth, resizeable, row, title })=>({
33
+ column: {
34
+ ...column
35
+ },
36
+ ...void 0 === componentInstanceId ? {} : {
37
+ componentInstanceId
38
+ },
39
+ ...void 0 === contentVisible ? {} : {
40
+ contentVisible
41
+ },
42
+ ...void 0 === dropTarget ? {} : {
43
+ dropTarget
44
+ },
45
+ ...void 0 === header ? {} : {
46
+ header
47
+ },
48
+ id,
49
+ ...void 0 === minHeight ? {} : {
50
+ minHeight
51
+ },
52
+ ...void 0 === minWidth ? {} : {
53
+ minWidth
54
+ },
55
+ ...void 0 === resizeable ? {} : {
56
+ resizeable
57
+ },
58
+ row: {
59
+ ...row
60
+ },
61
+ ...void 0 === title ? {} : {
62
+ title
63
+ }
64
+ });
65
+ const persistedLayoutToSnapshot = (layout)=>normalizeGridSnapshot({
66
+ columns: layout.columns.map((size)=>({
67
+ size: size
68
+ })),
69
+ gridId: layout.id,
70
+ items: layout.items,
71
+ revision: 0,
72
+ rows: layout.rows.map((size)=>({
73
+ size: size
74
+ })),
75
+ stacks: layout.stacks
76
+ });
77
+ const encodeGridLayoutDocument = (snapshot, registry, { componentSettings, placeholderIds = [] })=>{
78
+ let normalized;
79
+ try {
80
+ normalized = normalizeGridSnapshot(snapshot);
81
+ } catch (cause) {
82
+ return failure("INVALID_DOCUMENT", cause instanceof Error ? cause.message : "invalid grid snapshot", "$.layout");
83
+ }
84
+ const placeholderIdSet = new Set(placeholderIds);
85
+ if (placeholderIdSet.size !== placeholderIds.length) return failure("INVALID_DOCUMENT", "placeholder ids must be unique", "$.layout.placeholderIds");
86
+ const layoutItemIds = new Set(normalized.items.map(({ id })=>id));
87
+ for (const placeholderId of placeholderIdSet)if (!layoutItemIds.has(placeholderId)) return failure("INVALID_DOCUMENT", `placeholder "${placeholderId}" has no matching layout item`, "$.layout.placeholderIds");
88
+ const componentReferenceIds = new Set(normalized.items.filter(({ id })=>!placeholderIdSet.has(id)).map(({ componentInstanceId, id })=>componentInstanceId ?? id));
89
+ const referencedComponents = new Map();
90
+ for (const { componentInstanceId, id } of normalized.items){
91
+ if (placeholderIdSet.has(id)) continue;
92
+ const componentId = componentInstanceId ?? id;
93
+ const previousItemId = referencedComponents.get(componentId);
94
+ if (previousItemId) return failure("DUPLICATE_COMPONENT_ID", `component "${componentId}" is referenced by items "${previousItemId}" and "${id}"`, `$.layout.items.${id}.componentInstanceId`);
95
+ referencedComponents.set(componentId, id);
96
+ }
97
+ const componentIds = new Set();
98
+ const components = [];
99
+ for (const component of [
100
+ ...componentSettings
101
+ ].sort((a, b)=>compareIds(a.id, b.id))){
102
+ if (componentIds.has(component.id)) return failure("DUPLICATE_COMPONENT_ID", `component id "${component.id}" is duplicated`, "$.components");
103
+ componentIds.add(component.id);
104
+ if (!componentReferenceIds.has(component.id)) return failure("UNEXPECTED_COMPONENT_SETTINGS", `component "${component.id}" has no matching layout item reference`, "$.components");
105
+ const encoded = registry.encode(component);
106
+ if (!encoded.ok) return {
107
+ error: {
108
+ code: "COMPONENT_SETTINGS_ERROR",
109
+ componentError: encoded.error,
110
+ message: encoded.error.message,
111
+ path: `$.components.${component.id}${encoded.error.path.slice(1)}`,
112
+ version: encoded.error.version
113
+ },
114
+ ok: false
115
+ };
116
+ components.push(encoded.value);
117
+ }
118
+ for (const { componentInstanceId, id } of normalized.items){
119
+ if (placeholderIdSet.has(id) && void 0 !== componentInstanceId) return failure("INVALID_DOCUMENT", `placeholder "${id}" must not reference a component`, `$.layout.items.${id}.componentInstanceId`);
120
+ const componentId = componentInstanceId ?? id;
121
+ if (!componentIds.has(componentId) && !placeholderIdSet.has(id)) return failure("MISSING_COMPONENT_SETTINGS", `layout item "${id}" references missing component "${componentId}"`, `$.layout.items.${id}`);
122
+ }
123
+ const document = {
124
+ components,
125
+ kind: GRID_LAYOUT_DOCUMENT_KIND,
126
+ layout: {
127
+ columns: normalized.columns.map(({ size })=>size),
128
+ id: normalized.gridId,
129
+ items: normalized.items.map(durableItem).sort((a, b)=>compareIds(a.id, b.id)),
130
+ placeholderIds: [
131
+ ...placeholderIdSet
132
+ ].sort(),
133
+ rows: normalized.rows.map(({ size })=>size),
134
+ stacks: normalized.stacks.map((stack)=>({
135
+ id: stack.id,
136
+ itemIds: [
137
+ ...stack.itemIds
138
+ ],
139
+ selectedItemId: stack.selectedItemId
140
+ })).sort((a, b)=>compareIds(a.id, b.id))
141
+ },
142
+ version: GRID_LAYOUT_DOCUMENT_VERSION
143
+ };
144
+ const json = toJsonValue(document);
145
+ return json.ok ? {
146
+ ok: true,
147
+ value: immutableGridLayoutDocument(document)
148
+ } : failure("INVALID_DOCUMENT", json.error.message, json.error.path);
149
+ };
150
+ const migrateDocument = (value)=>{
151
+ if ("number" != typeof value.version || !Number.isInteger(value.version)) return failure("INVALID_DOCUMENT", "document version must be an integer", "$.version");
152
+ if (value.version === GRID_LAYOUT_DOCUMENT_VERSION) return {
153
+ ok: true,
154
+ value
155
+ };
156
+ if (1 !== value.version) return failure("UNSUPPORTED_DOCUMENT_VERSION", `GridLayout document version ${String(value.version)} is not supported`, "$.version", "number" == typeof value.version ? value.version : void 0);
157
+ if (!isRecord(value.layout) || "string" != typeof value.layout.gridId) return failure("DOCUMENT_MIGRATION_FAILED", "version 1 layout.gridId must be a string", "$.layout.gridId", 1);
158
+ const { gridId, stacks = [], ...layout } = value.layout;
159
+ return {
160
+ ok: true,
161
+ value: {
162
+ ...value,
163
+ layout: {
164
+ ...layout,
165
+ id: gridId,
166
+ placeholderIds: [],
167
+ stacks
168
+ },
169
+ version: GRID_LAYOUT_DOCUMENT_VERSION
170
+ }
171
+ };
172
+ };
173
+ const parseComponent = (value, index)=>{
174
+ const path = `$.components[${index}]`;
175
+ if (!isRecord(value)) return failure("INVALID_DOCUMENT", "component must be an object", path);
176
+ const unexpectedField = Object.keys(value).find((field)=>![
177
+ "id",
178
+ "settings",
179
+ "type",
180
+ "version"
181
+ ].includes(field));
182
+ if (unexpectedField) return failure("INVALID_DOCUMENT", `unexpected component field "${unexpectedField}"`, `${path}.${unexpectedField}`);
183
+ if ("string" != typeof value.id || !value.id || "string" != typeof value.type || !value.type || !Number.isInteger(value.version) || "number" != typeof value.version) return failure("INVALID_DOCUMENT", "component id, type and integer version are required", path);
184
+ const settings = toJsonValue(value.settings, `${path}.settings`);
185
+ if (!settings.ok) return failure("INVALID_DOCUMENT", settings.error.message, settings.error.path);
186
+ return {
187
+ ok: true,
188
+ value: {
189
+ id: value.id,
190
+ settings: settings.value,
191
+ type: value.type,
192
+ version: value.version
193
+ }
194
+ };
195
+ };
196
+ const decodeGridLayoutDocument = (input, registry)=>{
197
+ const detached = toJsonValue(input);
198
+ if (!detached.ok || !isRecord(detached.value)) return failure("INVALID_DOCUMENT", detached.ok ? "document must be an object" : detached.error.message, detached.ok ? "$" : detached.error.path);
199
+ if (detached.value.kind !== GRID_LAYOUT_DOCUMENT_KIND) return failure("INVALID_DISCRIMINATOR", `expected document kind "${GRID_LAYOUT_DOCUMENT_KIND}"`, "$.kind");
200
+ const migrated = migrateDocument(detached.value);
201
+ if (!migrated.ok) return migrated;
202
+ const value = migrated.value;
203
+ const unexpectedDocumentField = Object.keys(value).find((field)=>![
204
+ "components",
205
+ "kind",
206
+ "layout",
207
+ "version"
208
+ ].includes(field));
209
+ if (unexpectedDocumentField) return failure("INVALID_DOCUMENT", `unexpected document field "${unexpectedDocumentField}"`, `$.${unexpectedDocumentField}`);
210
+ if (!isRecord(value.layout) || !Array.isArray(value.components)) return failure("INVALID_DOCUMENT", "document layout and components are required", "$");
211
+ const unexpectedLayoutField = Object.keys(value.layout).find((field)=>![
212
+ "columns",
213
+ "id",
214
+ "items",
215
+ "placeholderIds",
216
+ "rows",
217
+ "stacks"
218
+ ].includes(field));
219
+ if (unexpectedLayoutField) return failure("INVALID_DOCUMENT", `unexpected layout field "${unexpectedLayoutField}"`, `$.layout.${unexpectedLayoutField}`);
220
+ const { columns, id, items, placeholderIds, rows, stacks } = value.layout;
221
+ if ("string" != typeof id || !Array.isArray(columns) || !Array.isArray(rows) || !Array.isArray(items) || !Array.isArray(placeholderIds) || !Array.isArray(stacks)) return failure("INVALID_DOCUMENT", "layout id, columns, rows, items, placeholderIds and stacks are required", "$.layout");
222
+ let snapshot;
223
+ try {
224
+ snapshot = persistedLayoutToSnapshot({
225
+ columns: columns,
226
+ id,
227
+ items: items,
228
+ placeholderIds: placeholderIds,
229
+ rows: rows,
230
+ stacks: stacks
231
+ });
232
+ } catch (cause) {
233
+ return failure("INVALID_DOCUMENT", cause instanceof Error ? cause.message : "invalid canonical layout", "$.layout");
234
+ }
235
+ const itemIds = new Set(snapshot.items.map(({ id: itemId })=>itemId));
236
+ const parsedPlaceholderIds = new Set();
237
+ for(let index = 0; index < placeholderIds.length; index += 1){
238
+ const placeholderId = placeholderIds[index];
239
+ if ("string" != typeof placeholderId || !itemIds.has(placeholderId) || parsedPlaceholderIds.has(placeholderId)) return failure("INVALID_DOCUMENT", "placeholder id must identify one unique layout item", `$.layout.placeholderIds[${index}]`);
240
+ parsedPlaceholderIds.add(placeholderId);
241
+ }
242
+ const componentItemById = new Map();
243
+ for (const { componentInstanceId, id: itemId } of snapshot.items){
244
+ if (parsedPlaceholderIds.has(itemId)) {
245
+ if (void 0 !== componentInstanceId) return failure("INVALID_DOCUMENT", `placeholder "${itemId}" must not reference a component`, `$.layout.items.${itemId}.componentInstanceId`);
246
+ continue;
247
+ }
248
+ const componentId = componentInstanceId ?? itemId;
249
+ const previousItemId = componentItemById.get(componentId);
250
+ if (previousItemId) return failure("DUPLICATE_COMPONENT_ID", `component "${componentId}" is referenced by items "${previousItemId}" and "${itemId}"`, `$.layout.items.${itemId}.componentInstanceId`);
251
+ componentItemById.set(componentId, itemId);
252
+ }
253
+ const seen = new Set();
254
+ const components = [];
255
+ const encodedComponents = [];
256
+ for(let index = 0; index < value.components.length; index += 1){
257
+ const parsed = parseComponent(value.components[index], index);
258
+ if (!parsed.ok) return parsed;
259
+ if (seen.has(parsed.value.id)) return failure("DUPLICATE_COMPONENT_ID", `component id "${parsed.value.id}" is duplicated`, `$.components[${index}].id`);
260
+ if (!componentItemById.has(parsed.value.id)) return failure("UNEXPECTED_COMPONENT_SETTINGS", `component "${parsed.value.id}" has no matching layout item reference`, `$.components[${index}].id`);
261
+ seen.add(parsed.value.id);
262
+ encodedComponents.push(parsed.value);
263
+ const decoded = registry.decode(parsed.value);
264
+ if (!decoded.ok) return {
265
+ error: {
266
+ code: "COMPONENT_SETTINGS_ERROR",
267
+ componentError: decoded.error,
268
+ message: decoded.error.message,
269
+ path: `$.components[${index}]${decoded.error.path.slice(1)}`,
270
+ version: decoded.error.version
271
+ },
272
+ ok: false
273
+ };
274
+ components.push(decoded.value);
275
+ }
276
+ for (const [componentId, itemId] of componentItemById)if (!seen.has(componentId)) return failure("MISSING_COMPONENT_SETTINGS", `layout item "${itemId}" references missing component "${componentId}"`, `$.layout.items.${itemId}`);
277
+ for (const placeholderId of parsedPlaceholderIds)if (seen.has(placeholderId)) return failure("INVALID_DOCUMENT", `placeholder "${placeholderId}" also has component settings`, "$.layout.placeholderIds");
278
+ const document = {
279
+ components: encodedComponents.sort((a, b)=>compareIds(a.id, b.id)),
280
+ kind: GRID_LAYOUT_DOCUMENT_KIND,
281
+ layout: {
282
+ columns: snapshot.columns.map(({ size })=>size),
283
+ id: snapshot.gridId,
284
+ items: snapshot.items.map(durableItem).sort((a, b)=>compareIds(a.id, b.id)),
285
+ placeholderIds: [
286
+ ...parsedPlaceholderIds
287
+ ].sort(),
288
+ rows: snapshot.rows.map(({ size })=>size),
289
+ stacks: snapshot.stacks.map((stack)=>({
290
+ ...stack,
291
+ itemIds: [
292
+ ...stack.itemIds
293
+ ]
294
+ })).sort((a, b)=>compareIds(a.id, b.id))
295
+ },
296
+ version: GRID_LAYOUT_DOCUMENT_VERSION
297
+ };
298
+ return {
299
+ ok: true,
300
+ value: {
301
+ components,
302
+ document: immutableGridLayoutDocument(document),
303
+ snapshot
304
+ }
305
+ };
306
+ };
307
+ export { GRID_LAYOUT_DOCUMENT_KIND, GRID_LAYOUT_DOCUMENT_VERSION, GridLayoutDocumentCodecError, decodeGridLayoutDocument, encodeGridLayoutDocument, immutableGridLayoutDocument };