@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,395 @@
1
+ import { uuid } from "@vuu-ui/vuu-utils";
2
+ import { regeneratePlaceholders, removeGridItem, replaceGridItem, resizeTrackTo, resizeTracksAdjacent, resizeTracksProportionally, splitGridItem } from "./GridGeometry.js";
3
+ import { GridLayoutModel } from "./GridLayoutModel.js";
4
+ import { GridModelChildItem } from "./GridModel.js";
5
+ import { gridStackItemIds } from "./GridStack.js";
6
+ class GridCommandExecutionError extends Error {
7
+ code;
8
+ constructor({ code, message }){
9
+ super(message);
10
+ this.name = "GridCommandExecutionError";
11
+ this.code = code;
12
+ }
13
+ }
14
+ const assertNever = (value)=>{
15
+ throw Error(`Unhandled GridCommand: ${JSON.stringify(value)}`);
16
+ };
17
+ const success = (command)=>({
18
+ command: command.type,
19
+ ok: true
20
+ });
21
+ const failure = (command, code, message)=>({
22
+ command: command.type,
23
+ error: {
24
+ code,
25
+ message
26
+ },
27
+ ok: false
28
+ });
29
+ const geometryFailure = (command, { code, message })=>{
30
+ switch(code){
31
+ case "GEOMETRY_ERROR":
32
+ throw Error(message);
33
+ case "INVALID_TARGET":
34
+ return failure(command, "INVALID_TARGET", message);
35
+ case "ITEM_NOT_FOUND":
36
+ return failure(command, "ITEM_NOT_FOUND", message);
37
+ case "MEASUREMENT_REQUIRED":
38
+ return failure(command, "MEASUREMENT_REQUIRED", message);
39
+ case "NON_RESIZABLE":
40
+ return failure(command, "NON_RESIZABLE", message);
41
+ case "TRACK_NOT_FOUND":
42
+ return failure(command, "TRACK_NOT_FOUND", message);
43
+ default:
44
+ return failure(command, "INVALID_ITEM", message);
45
+ }
46
+ };
47
+ const stackFailure = (command, { code, message })=>failure(command, code, message);
48
+ const toPosition = ({ span, start })=>({
49
+ end: start + span,
50
+ start
51
+ });
52
+ const toChildItem = (item, stackId)=>new GridModelChildItem({
53
+ ...item,
54
+ column: toPosition(item.column),
55
+ row: toPosition(item.row),
56
+ stackId
57
+ });
58
+ const toConstraints = (constraints)=>constraints?.map(({ minimum, trackIndices })=>({
59
+ minimum,
60
+ trackIndices: [
61
+ ...trackIndices
62
+ ]
63
+ })) ?? [];
64
+ class LegacyGridCommandExecutor {
65
+ gridModel;
66
+ gridLayoutModel;
67
+ constructor(gridModel, gridLayoutModel = new GridLayoutModel(gridModel)){
68
+ this.gridModel = gridModel;
69
+ this.gridLayoutModel = gridLayoutModel;
70
+ }
71
+ execute(command) {
72
+ switch(command.type){
73
+ case "add-item":
74
+ {
75
+ const invalid = this.validateNewItem(command, command.item);
76
+ if (invalid) return invalid;
77
+ this.gridModel.addChildItem(toChildItem(command.item));
78
+ return success(command);
79
+ }
80
+ case "move-item":
81
+ {
82
+ const invalid = this.validatePair(command, command.itemId, command.targetId);
83
+ if (invalid) return invalid;
84
+ const stackedMember = this.rejectStackMember(command, command.itemId, command.targetId);
85
+ if (stackedMember) return stackedMember;
86
+ const geometry = this.gridModel.toGeometry();
87
+ const split = this.gridModel.runGeometry((measurements)=>splitGridItem(geometry, {
88
+ droppedItemId: command.itemId,
89
+ splitDirection: command.position,
90
+ targetItemId: command.targetId
91
+ }, measurements));
92
+ if (!split.ok) return geometryFailure(command, split.error);
93
+ this.gridLayoutModel.applySplitTransition(split.value);
94
+ this.gridModel.getChildItem(command.itemId, true).dragging = false;
95
+ return success(command);
96
+ }
97
+ case "replace-item":
98
+ {
99
+ const invalid = this.validatePair(command, command.itemId, command.targetId);
100
+ if (invalid) return invalid;
101
+ const stackedMember = this.rejectStackMember(command, command.itemId, command.targetId);
102
+ if (stackedMember) return stackedMember;
103
+ const replaced = replaceGridItem(this.gridModel.toGeometry(), {
104
+ droppedItemId: command.itemId,
105
+ targetItemId: command.targetId
106
+ });
107
+ if (!replaced.ok) return geometryFailure(command, replaced.error);
108
+ this.gridLayoutModel.applyReplaceTransition(replaced.value);
109
+ this.gridModel.getChildItem(command.itemId, true).dragging = false;
110
+ return success(command);
111
+ }
112
+ case "replace-stack-item":
113
+ {
114
+ const invalid = this.requireTab(command, command.stackId, command.itemId) ?? this.requireItem(command, command.targetId);
115
+ if (invalid) return invalid;
116
+ const target = this.gridModel.getChildItem(command.targetId, true);
117
+ if (target.stackId || "stacked-content" === target.type) return failure(command, "INVALID_TARGET", "A stack member can only replace a standalone grid item");
118
+ const removed = this.gridModel.removeStackItem(command.stackId, command.itemId);
119
+ if (!removed.ok) return stackFailure(command, removed.error);
120
+ const item = this.gridModel.getChildItem(command.itemId, true);
121
+ item.stackId = void 0;
122
+ item.contentDetached = void 0;
123
+ item.contentVisible = true;
124
+ item.dragging = false;
125
+ const replaced = replaceGridItem(this.gridModel.toGeometry(), {
126
+ droppedItemId: command.itemId,
127
+ targetItemId: command.targetId
128
+ });
129
+ if (!replaced.ok) return geometryFailure(command, replaced.error);
130
+ this.gridLayoutModel.applyReplaceTransition(replaced.value);
131
+ return success(command);
132
+ }
133
+ case "remove-item":
134
+ {
135
+ const invalid = this.requireItem(command, command.itemId);
136
+ if (invalid) return invalid;
137
+ return this.removeItem(command, command.itemId, command.reason);
138
+ }
139
+ case "resize-track":
140
+ {
141
+ const invalid = this.requireTracks(command, command.track, [
142
+ command.index
143
+ ]);
144
+ if (invalid) return invalid;
145
+ const resized = this.gridModel.tracks.runTrackGeometry(command.track, (tracks)=>resizeTrackTo(tracks, command.index, command.size, command.track));
146
+ if (!resized.ok) return geometryFailure(command, resized.error);
147
+ this.gridModel.tracks.applyTrackTransition(command.track, resized.value);
148
+ return success(command);
149
+ }
150
+ case "resize-tracks":
151
+ {
152
+ if (!Number.isFinite(command.delta)) return failure(command, "INVALID_ITEM", "Grid track resize delta must be finite");
153
+ if ("adjacent" === command.distribution && command.resizedTrackIndex === command.contraTrackIndex) return failure(command, "INVALID_TARGET", "Adjacent track resize requires two distinct tracks");
154
+ const indices = "adjacent" === command.distribution ? [
155
+ command.resizedTrackIndex,
156
+ command.contraTrackIndex
157
+ ] : [
158
+ ...command.beforeTrackIndices,
159
+ ...command.afterTrackIndices,
160
+ ...(command.beforeConstraints ?? []).flatMap(({ trackIndices })=>trackIndices),
161
+ ...(command.afterConstraints ?? []).flatMap(({ trackIndices })=>trackIndices)
162
+ ];
163
+ if ("proportional" === command.distribution && command.initialSizes && (command.initialSizes.length !== this.gridModel.tracks.getTracks(command.track).length || command.initialSizes.some((size)=>!Number.isFinite(size) || size < 0))) return failure(command, "INVALID_ITEM", `Initial ${command.track} sizes must contain one non-negative value per track`);
164
+ const invalid = this.requireTracks(command, command.track, indices) ?? this.validateResizeCommand(command) ?? this.applyMeasurements(command, command.track, indices, command.measuredSizes);
165
+ if (invalid) return invalid;
166
+ const resizedTracks = this.gridModel.tracks.runTrackGeometry(command.track, (tracks)=>"adjacent" === command.distribution ? resizeTracksAdjacent(tracks, {
167
+ contraTrackIndex: command.contraTrackIndex,
168
+ delta: command.delta,
169
+ resizedTrackIndex: command.resizedTrackIndex
170
+ }, command.track) : resizeTracksProportionally(tracks, {
171
+ afterConstraints: toConstraints(command.afterConstraints),
172
+ afterTrackIndices: command.afterTrackIndices,
173
+ beforeConstraints: toConstraints(command.beforeConstraints),
174
+ beforeTrackIndices: command.beforeTrackIndices,
175
+ delta: command.delta,
176
+ initialSizes: command.initialSizes
177
+ }, command.track));
178
+ if (!resizedTracks.ok) return geometryFailure(command, resizedTracks.error);
179
+ this.gridModel.tracks.applyTrackTransition(command.track, resizedTracks.value);
180
+ return success(command);
181
+ }
182
+ case "create-stack":
183
+ {
184
+ const invalid = this.validatePair(command, command.itemId, command.targetId);
185
+ if (invalid) return invalid;
186
+ if (this.gridModel.getChildItem(command.itemId, true).stackId || this.gridModel.getChildItem(command.targetId, true).stackId) return failure(command, "INVALID_TARGET", "A new stack requires two unstacked items");
187
+ const created = this.gridModel.createStack(command.targetId, command.itemId);
188
+ if (!created.ok) return stackFailure(command, created.error);
189
+ this.gridModel.getChildItem(command.itemId, true).dragging = false;
190
+ if (command.selectedItemId) {
191
+ const selected = this.gridModel.selectStackItem(created.value.stackId, command.selectedItemId);
192
+ if (!selected.ok) return stackFailure(command, selected.error);
193
+ }
194
+ return success(command);
195
+ }
196
+ case "add-stack-item":
197
+ {
198
+ const invalid = this.requireStack(command, command.stackId) ?? this.validateNewItem(command, command.item);
199
+ if (invalid) return invalid;
200
+ if (void 0 === command.position !== (void 0 === command.targetItemId)) return failure(command, "INVALID_TARGET", "A stack placement requires both position and targetItemId");
201
+ const stack = this.gridModel.getChildItem(command.stackId, true);
202
+ const added = this.gridModel.addStackItem(command.stackId, toChildItem({
203
+ ...command.item,
204
+ column: {
205
+ span: stack.column.end - stack.column.start,
206
+ start: stack.column.start
207
+ },
208
+ row: {
209
+ span: stack.row.end - stack.row.start,
210
+ start: stack.row.start
211
+ }
212
+ }, command.stackId), command.position && command.targetItemId ? {
213
+ position: command.position,
214
+ target: command.targetItemId
215
+ } : void 0);
216
+ if (!added.ok) return stackFailure(command, added.error);
217
+ return success(command);
218
+ }
219
+ case "remove-stack-item":
220
+ {
221
+ const invalid = this.requireTab(command, command.stackId, command.itemId);
222
+ if (invalid) return invalid;
223
+ return this.removeItem(command, command.itemId, "close");
224
+ }
225
+ case "move-item-to-stack":
226
+ {
227
+ const invalid = this.requireItem(command, command.itemId) ?? this.requireStack(command, command.stackId);
228
+ if (invalid) return invalid;
229
+ if (void 0 === command.position !== (void 0 === command.targetItemId)) return failure(command, "INVALID_TARGET", "A stack placement requires both position and targetItemId");
230
+ if (command.fromStackId) {
231
+ const tabFailure = this.requireTab(command, command.fromStackId, command.itemId);
232
+ if (tabFailure) return tabFailure;
233
+ const removed = this.gridModel.removeStackItem(command.fromStackId, command.itemId);
234
+ if (!removed.ok) return stackFailure(command, removed.error);
235
+ } else if (!this.gridModel.getChildItem(command.itemId, true).dragging) {
236
+ const removed = this.removeItem(command, command.itemId, "drag");
237
+ if (!removed.ok) return removed;
238
+ }
239
+ const item = this.gridModel.getChildItem(command.itemId, true);
240
+ item.stackId = void 0;
241
+ item.contentDetached = void 0;
242
+ item.dragging = false;
243
+ const added = this.gridModel.addStackMember(command.stackId, command.itemId, command.position && command.targetItemId ? {
244
+ position: command.position,
245
+ target: command.targetItemId
246
+ } : void 0);
247
+ if (!added.ok) return stackFailure(command, added.error);
248
+ const selected = this.gridModel.selectStackItem(command.stackId, command.itemId);
249
+ return selected.ok ? success(command) : stackFailure(command, selected.error);
250
+ }
251
+ case "move-stack-item-to-grid":
252
+ {
253
+ const invalid = this.requireTab(command, command.stackId, command.itemId) ?? this.requireItem(command, command.targetId);
254
+ if (invalid) return invalid;
255
+ const stackItemIds = gridStackItemIds(this.gridModel.getStackState(command.stackId));
256
+ const targetId = command.targetId === command.stackId && 2 === stackItemIds.length ? stackItemIds.find((itemId)=>itemId !== command.itemId) : command.targetId;
257
+ if (!targetId) return failure(command, "INVALID_TARGET", `Grid stack #${command.stackId} has no remaining split target`);
258
+ const removed = this.gridModel.removeStackItem(command.stackId, command.itemId);
259
+ if (!removed.ok) return stackFailure(command, removed.error);
260
+ const item = this.gridModel.getChildItem(command.itemId, true);
261
+ item.stackId = void 0;
262
+ item.contentDetached = void 0;
263
+ item.contentVisible = true;
264
+ item.dragging = false;
265
+ const split = this.gridModel.runGeometry((measurements)=>splitGridItem(this.gridModel.toGeometry(), {
266
+ droppedItemId: command.itemId,
267
+ splitDirection: command.position,
268
+ targetItemId: targetId
269
+ }, measurements));
270
+ if (!split.ok) return geometryFailure(command, split.error);
271
+ this.gridLayoutModel.applySplitTransition(split.value);
272
+ return success(command);
273
+ }
274
+ case "select-stack-item":
275
+ {
276
+ const invalid = this.requireTab(command, command.stackId, command.itemId);
277
+ if (invalid) return invalid;
278
+ const selected = this.gridModel.selectStackItem(command.stackId, command.itemId);
279
+ if (!selected.ok) return stackFailure(command, selected.error);
280
+ return success(command);
281
+ }
282
+ case "reorder-stack-item":
283
+ {
284
+ const invalid = this.requireTab(command, command.stackId, command.itemId) ?? this.requireTab(command, command.stackId, command.targetItemId);
285
+ if (invalid) return invalid;
286
+ const reordered = this.gridModel.reorderStackItem(command.stackId, {
287
+ activate: command.activate ?? false,
288
+ itemId: command.itemId,
289
+ placement: command.position,
290
+ targetItemId: command.targetItemId
291
+ });
292
+ if (!reordered.ok) return stackFailure(command, reordered.error);
293
+ return success(command);
294
+ }
295
+ case "rename-item":
296
+ {
297
+ const invalid = this.requireItem(command, command.itemId);
298
+ if (invalid) return invalid;
299
+ this.gridModel.updateChildTitle(command.itemId, command.title);
300
+ return success(command);
301
+ }
302
+ case "regenerate-placeholders":
303
+ this.gridModel.applyPlaceholderTransition(regeneratePlaceholders(this.gridModel.toGeometry(), uuid));
304
+ return success(command);
305
+ default:
306
+ return assertNever(command);
307
+ }
308
+ }
309
+ removeItem(command, itemId, reason) {
310
+ const geometry = this.gridModel.toGeometry();
311
+ const removed = this.gridModel.runGeometry((measurements)=>removeGridItem(geometry, {
312
+ itemId,
313
+ reason
314
+ }, {
315
+ createPlaceholderId: uuid,
316
+ measurements
317
+ }));
318
+ if (!removed.ok) return geometryFailure(command, removed.error);
319
+ this.gridLayoutModel.applyRemoveTransition(removed.value);
320
+ return success(command);
321
+ }
322
+ applyMeasurements(command, trackType, indices, measuredSizes) {
323
+ const tracks = this.gridModel.tracks.getTracks(trackType);
324
+ if (measuredSizes) {
325
+ if (measuredSizes.length !== tracks.length || measuredSizes.some((size)=>!Number.isFinite(size) || size < 0)) return failure(command, "INVALID_ITEM", `Measured ${trackType} sizes must contain one non-negative value per track`);
326
+ measuredSizes.forEach((size, index)=>{
327
+ tracks[index].measuredValue = size;
328
+ });
329
+ indices.forEach((index)=>{
330
+ if (tracks[index].isFraction) tracks[index].convertUnitsToPixels();
331
+ });
332
+ }
333
+ if (indices.some((index)=>tracks[index].isFraction && !tracks[index].isMeasured)) return failure(command, "MEASUREMENT_REQUIRED", `Fractional ${trackType} resize requires measuredSizes`);
334
+ }
335
+ validateResizeCommand(command) {
336
+ const tracks = this.gridModel.tracks.getTracks(command.track);
337
+ if ("adjacent" === command.distribution) {
338
+ const currentSize = (index)=>command.measuredSizes?.[index] ?? (tracks[index].isFraction ? void 0 : tracks[index].numericValue);
339
+ const resizedSize = currentSize(command.resizedTrackIndex);
340
+ const contraSize = currentSize(command.contraTrackIndex);
341
+ if (void 0 !== resizedSize && resizedSize + command.delta < 0 || void 0 !== contraSize && contraSize - command.delta < 0) return failure(command, "INVALID_ITEM", "Adjacent track resize cannot produce a negative track size");
342
+ return;
343
+ }
344
+ const before = new Set(command.beforeTrackIndices);
345
+ const after = new Set(command.afterTrackIndices);
346
+ if (before.size !== command.beforeTrackIndices.length || after.size !== command.afterTrackIndices.length || [
347
+ ...before
348
+ ].some((index)=>after.has(index))) return failure(command, "INVALID_TARGET", "Proportional track resize groups must contain unique, disjoint tracks");
349
+ for (const [constraints, group] of [
350
+ [
351
+ command.beforeConstraints ?? [],
352
+ before
353
+ ],
354
+ [
355
+ command.afterConstraints ?? [],
356
+ after
357
+ ]
358
+ ])for (const { minimum, trackIndices } of constraints)if (!Number.isFinite(minimum) || minimum < 0 || trackIndices.some((index)=>!group.has(index))) return failure(command, "INVALID_ITEM", "Grid track resize constraints must reference their own group and have non-negative minimums");
359
+ }
360
+ requireItem(command, itemId) {
361
+ if (!this.gridModel.getChildItem(itemId)) return failure(command, "ITEM_NOT_FOUND", `[GridModel] GridItem #${itemId} not found`);
362
+ }
363
+ requireStack(command, stackId) {
364
+ const stack = this.gridModel.getChildItem(stackId);
365
+ if (!stack || "stacked-content" !== stack.type) return failure(command, "STACK_NOT_FOUND", `Grid stack #${stackId} not found`);
366
+ }
367
+ requireTab(command, stackId, itemId) {
368
+ const invalidStack = this.requireStack(command, stackId);
369
+ if (invalidStack) return invalidStack;
370
+ if (!gridStackItemIds(this.gridModel.getStackState(stackId)).includes(itemId)) return failure(command, "TAB_NOT_FOUND", `Grid tab #${itemId} not found in stack #${stackId}`);
371
+ }
372
+ requireTracks(command, trackType, indices) {
373
+ const trackCount = this.gridModel.tracks.getTracks(trackType).length;
374
+ const missing = indices.find((index)=>!Number.isInteger(index) || index < 0 || index >= trackCount);
375
+ if (void 0 !== missing) return failure(command, "TRACK_NOT_FOUND", `Grid ${trackType} track #${missing} not found`);
376
+ }
377
+ validateNewItem(command, item) {
378
+ if (this.gridModel.getChildItem(item.id)) return failure(command, "DUPLICATE_ITEM_ID", `Grid item #${item.id} already exists`);
379
+ const { colCount, rowCount } = this.gridModel.tracks;
380
+ if (!item.id || !Number.isInteger(item.column.start) || !Number.isInteger(item.column.span) || !Number.isInteger(item.row.start) || !Number.isInteger(item.row.span) || item.column.start < 1 || item.row.start < 1 || item.column.span < 1 || item.row.span < 1 || item.column.start + item.column.span > colCount + 1 || item.row.start + item.row.span > rowCount + 1) return failure(command, "INVALID_ITEM", `Grid item #${item.id} has invalid grid coordinates`);
381
+ }
382
+ validatePair(command, itemId, targetId) {
383
+ const invalid = this.requireItem(command, itemId) ?? this.requireItem(command, targetId);
384
+ if (invalid) return invalid;
385
+ if (itemId === targetId) return failure(command, "INVALID_TARGET", `Grid item #${itemId} cannot target itself`);
386
+ }
387
+ rejectStackMember(command, ...itemIds) {
388
+ const stackedMember = itemIds.map((itemId)=>this.gridModel.getChildItem(itemId, true)).find(({ stackId })=>void 0 !== stackId);
389
+ if (stackedMember) return failure(command, "INVALID_TARGET", `Stack member #${stackedMember.id} must be moved through a stack command`);
390
+ }
391
+ }
392
+ const throwForGridCommandFailure = (result)=>{
393
+ if (!result.ok) throw new GridCommandExecutionError(result.error);
394
+ };
395
+ export { GridCommandExecutionError, LegacyGridCommandExecutor, throwForGridCommandFailure };
@@ -0,0 +1,163 @@
1
+ import { toJsonValue } from "./json-value.js";
2
+ const jsonIssue = (issue, component, code, version)=>({
3
+ code,
4
+ componentId: component.id,
5
+ componentType: component.type,
6
+ message: issue.message,
7
+ path: issue.path,
8
+ version
9
+ });
10
+ const codecError = (issue, component, code, version)=>({
11
+ code,
12
+ componentId: component.id,
13
+ componentType: component.type,
14
+ message: issue.message,
15
+ path: issue.path,
16
+ version
17
+ });
18
+ class GridComponentSettingsRegistry {
19
+ #codecs = new Map();
20
+ register(type, codec) {
21
+ if (!type) throw new Error("Grid component type must not be empty");
22
+ if (!Number.isInteger(codec.version) || codec.version < 1) throw new Error(`Grid component codec "${type}" has an invalid version`);
23
+ if (this.#codecs.has(type)) throw new Error(`Grid component codec "${type}" is already registered`);
24
+ this.#codecs.set(type, {
25
+ decode: codec.decode,
26
+ encode: (settings)=>codec.isSettings(settings) ? codec.encode(settings) : {
27
+ error: {
28
+ code: "INVALID_SETTINGS_TYPE",
29
+ message: `settings do not match component type "${type}"`,
30
+ path: "$"
31
+ },
32
+ ok: false
33
+ },
34
+ migrations: codec.migrations ?? {},
35
+ version: codec.version
36
+ });
37
+ }
38
+ encode(component) {
39
+ const codec = this.#codecs.get(component.type);
40
+ if (!codec) return {
41
+ error: {
42
+ code: "UNKNOWN_COMPONENT_TYPE",
43
+ componentId: component.id,
44
+ componentType: component.type,
45
+ message: `component type "${component.type}" is not registered`,
46
+ path: "$.type"
47
+ },
48
+ ok: false
49
+ };
50
+ const encoded = codec.encode(component.settings);
51
+ if (!encoded.ok) return {
52
+ error: codecError(encoded.error, component, "COMPONENT_ENCODE_FAILED", codec.version),
53
+ ok: false
54
+ };
55
+ const json = toJsonValue(encoded.value, "$.settings");
56
+ if (!json.ok) return {
57
+ error: jsonIssue(json.error, component, "COMPONENT_ENCODE_FAILED", codec.version),
58
+ ok: false
59
+ };
60
+ return {
61
+ ok: true,
62
+ value: {
63
+ id: component.id,
64
+ settings: json.value,
65
+ type: component.type,
66
+ version: codec.version
67
+ }
68
+ };
69
+ }
70
+ decode(component) {
71
+ const codec = this.#codecs.get(component.type);
72
+ if (!codec) return {
73
+ error: {
74
+ code: "UNKNOWN_COMPONENT_TYPE",
75
+ componentId: component.id,
76
+ componentType: component.type,
77
+ message: `component type "${component.type}" is not registered`,
78
+ path: "$.type",
79
+ version: component.version
80
+ },
81
+ ok: false
82
+ };
83
+ if (component.version > codec.version || component.version < 1) return {
84
+ error: {
85
+ code: "UNSUPPORTED_COMPONENT_VERSION",
86
+ componentId: component.id,
87
+ componentType: component.type,
88
+ message: `component settings version ${component.version} is not supported; current version is ${codec.version}`,
89
+ path: "$.version",
90
+ version: component.version
91
+ },
92
+ ok: false
93
+ };
94
+ let value = component.settings;
95
+ for(let version = component.version; version < codec.version; version += 1){
96
+ const migrate = codec.migrations[version];
97
+ if (!migrate) return {
98
+ error: {
99
+ code: "UNSUPPORTED_COMPONENT_VERSION",
100
+ componentId: component.id,
101
+ componentType: component.type,
102
+ message: `no migration from component settings version ${version}`,
103
+ path: "$.version",
104
+ version
105
+ },
106
+ ok: false
107
+ };
108
+ const migrated = migrate(value);
109
+ if (!migrated.ok) return {
110
+ error: codecError(migrated.error, component, "COMPONENT_MIGRATION_FAILED", version),
111
+ ok: false
112
+ };
113
+ const json = toJsonValue(migrated.value, "$.settings");
114
+ if (!json.ok) return {
115
+ error: jsonIssue(json.error, component, "COMPONENT_MIGRATION_FAILED", version),
116
+ ok: false
117
+ };
118
+ value = json.value;
119
+ }
120
+ const decoded = codec.decode(value);
121
+ return decoded.ok ? {
122
+ ok: true,
123
+ value: {
124
+ ...component,
125
+ settings: decoded.value,
126
+ version: codec.version
127
+ }
128
+ } : {
129
+ error: codecError(decoded.error, component, "COMPONENT_DECODE_FAILED", component.version),
130
+ ok: false
131
+ };
132
+ }
133
+ }
134
+ class GridComponentRendererRegistry {
135
+ #renderers = new Map();
136
+ register(type, isSettings, render) {
137
+ if (this.#renderers.has(type)) throw new Error(`Grid component renderer "${type}" is already registered`);
138
+ this.#renderers.set(type, {
139
+ isSettings,
140
+ render: (settings, componentId)=>{
141
+ if (!isSettings(settings)) throw new Error(`Decoded settings do not match renderer "${type}"`);
142
+ return render(settings, componentId);
143
+ }
144
+ });
145
+ }
146
+ render(component) {
147
+ const renderer = this.#renderers.get(component.type);
148
+ if (!renderer) throw new Error(`Grid component renderer "${component.type}" is not registered`);
149
+ return renderer.render(component.settings, component.id);
150
+ }
151
+ }
152
+ class GridLayoutContentRegistry {
153
+ #entries = new Map();
154
+ entries() {
155
+ return new Map(this.#entries);
156
+ }
157
+ replace(components, resolve) {
158
+ const next = new Map();
159
+ for (const component of components)next.set(component.id, resolve(component));
160
+ this.#entries = next;
161
+ }
162
+ }
163
+ export { GridComponentRendererRegistry, GridComponentSettingsRegistry, GridLayoutContentRegistry };