@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,1289 @@
1
+ import { EventEmitter, uuid } from "@vuu-ui/vuu-utils";
2
+ import { bisectTracks, computeSplitters, findBisectingTrack, findUnusedGridLines, getProportionalResizeAllowance, insertTrack, readTracks, regeneratePlaceholders, removeGridTrack, removeTrack, resizeTrackTo, resizeTracksAdjacent, resizeTracksProportionally, shiftItemsForInsertedTrack, shiftItemsForNewTrack, splitTrack, trackMetrics } from "./GridGeometry.js";
3
+ import { getGridArea, getGridPosition } from "./grid-layout-utils.js";
4
+ import { addGridStackItem, cloneGridStackState, createGridStack, gridStackSelectedIndex, normalizeGridStackState, removeGridStackItem, renameGridStackItem, reorderGridStackItem, selectGridStackItem } from "./GridStack.js";
5
+ const DEFAULT_MIN_GRID_ITEM_SIZE = 80;
6
+ const resolveMinimumGridItemSize = (minimum, styleMinimum)=>{
7
+ if (void 0 !== minimum) return minimum;
8
+ if ("number" == typeof styleMinimum) return styleMinimum;
9
+ if ("string" == typeof styleMinimum && /^-?\d+(?:\.\d+)?px$/.test(styleMinimum)) return Number.parseFloat(styleMinimum);
10
+ };
11
+ const isFractionUnit = (trackSize)=>"string" == typeof trackSize && trackSize.endsWith("fr");
12
+ const isPixelUnit = (trackSize)=>"string" == typeof trackSize && trackSize.endsWith("px");
13
+ const toGridItemUpdates = (updates)=>updates.map(({ column, id, row })=>[
14
+ id,
15
+ {
16
+ ...column ? {
17
+ column: {
18
+ end: column.end,
19
+ start: column.start
20
+ }
21
+ } : {},
22
+ ...row ? {
23
+ row: {
24
+ end: row.end,
25
+ start: row.start
26
+ }
27
+ } : {}
28
+ }
29
+ ]);
30
+ const assertValidTracks = (track, trackSizes)=>{
31
+ if (void 0 === trackSizes) console.warn(`[GridModel] ${track}s must be specified`);
32
+ };
33
+ const NonContentResetOptions = {
34
+ placeholders: false,
35
+ splitters: false
36
+ };
37
+ const isPlaceholder = (item)=>"placeholder" === item.type;
38
+ const isStackedItem = (i)=>!!i.stackId;
39
+ const toStackArea = ({ column, row })=>({
40
+ column: {
41
+ span: column.end - column.start,
42
+ start: column.start
43
+ },
44
+ row: {
45
+ span: row.end - row.start,
46
+ start: row.start
47
+ }
48
+ });
49
+ const toGridModelPosition = ({ span, start })=>({
50
+ end: start + span,
51
+ start
52
+ });
53
+ class ObservableGridPosition {
54
+ #id;
55
+ #start;
56
+ #end;
57
+ constructor(id, { start, end }){
58
+ this.#id = id;
59
+ this.#start = start;
60
+ this.#end = end;
61
+ }
62
+ get start() {
63
+ return this.#start;
64
+ }
65
+ set start(val) {
66
+ this.#start = val;
67
+ }
68
+ get end() {
69
+ return this.#end;
70
+ }
71
+ set end(val) {
72
+ this.#end = val;
73
+ }
74
+ toJSON() {
75
+ return {
76
+ start: this.#start,
77
+ end: this.#end
78
+ };
79
+ }
80
+ }
81
+ class GridModelChildItem {
82
+ id;
83
+ column;
84
+ componentInstanceId;
85
+ contentDetached;
86
+ contentVisible;
87
+ dropTarget;
88
+ header;
89
+ height;
90
+ horizontalSplitter = false;
91
+ minHeight;
92
+ minWidth;
93
+ stackId;
94
+ resizeable;
95
+ row;
96
+ title;
97
+ type;
98
+ verticalSplitter = false;
99
+ width;
100
+ #dragging = false;
101
+ constructor({ componentInstanceId, header, height, id, minHeight, minWidth, row, column, dropTarget, stackId, resizeable = false, title, type = "content", width, contentVisible = void 0 === stackId }){
102
+ this.componentInstanceId = componentInstanceId;
103
+ this.contentVisible = contentVisible;
104
+ this.dropTarget = dropTarget;
105
+ this.header = header;
106
+ this.height = height;
107
+ this.id = id;
108
+ this.minHeight = minHeight;
109
+ this.minWidth = minWidth;
110
+ this.row = new ObservableGridPosition(id, row);
111
+ this.column = new ObservableGridPosition(id, column);
112
+ this.stackId = stackId;
113
+ this.resizeable = resizeable;
114
+ this.title = title;
115
+ this.type = type;
116
+ this.width = width;
117
+ }
118
+ get dragging() {
119
+ return this.#dragging;
120
+ }
121
+ set dragging(isDragging) {
122
+ this.#dragging = isDragging;
123
+ }
124
+ get gridArea() {
125
+ return getGridArea(this);
126
+ }
127
+ }
128
+ class TabState extends EventEmitter {
129
+ id;
130
+ active;
131
+ tabs;
132
+ detachedTab = void 0;
133
+ constructor(id, active, tabs){
134
+ super(), this.id = id, this.active = active, this.tabs = tabs;
135
+ }
136
+ setActiveTab(value) {
137
+ this.active = this.indexOfTab(value);
138
+ this.emit("active-change", this);
139
+ }
140
+ setActiveTabById(id) {
141
+ this.active = this.tabs.findIndex((tab)=>tab.id === id);
142
+ this.emit("active-change", this);
143
+ }
144
+ detachTab(value) {
145
+ const nextActiveTab = this.getNextActiveTab();
146
+ if (nextActiveTab) {
147
+ this.detachedTab = this.tabs[this.indexOfTab(value)];
148
+ this.emit("tab-detached", this);
149
+ this.setActiveTab(nextActiveTab.label);
150
+ }
151
+ }
152
+ restoreDetachedTab(value) {
153
+ if (this.detachedTab?.label === value) {
154
+ this.detachedTab = void 0;
155
+ this.setActiveTab(value);
156
+ }
157
+ }
158
+ setNextActiveTab() {
159
+ const nextActiveTab = this.getNextActiveTab();
160
+ if (nextActiveTab) this.setActiveTab(nextActiveTab.label);
161
+ }
162
+ moveTab(tab, { position, target }, activateTab = false) {
163
+ const { label: activeTab } = this.activeTab;
164
+ const indexOfMovedTab = this.indexOfTab(tab.label);
165
+ const newTabs = this.tabs.slice();
166
+ const [movedTab] = newTabs.splice(indexOfMovedTab, 1);
167
+ const indexOfTargetTab = this.indexOfTab(target, newTabs);
168
+ if ("after" === position) newTabs.splice(indexOfTargetTab + 1, 0, movedTab);
169
+ else newTabs.splice(indexOfTargetTab, 0, movedTab);
170
+ this.tabs = newTabs;
171
+ if (activateTab) this.active = this.indexOfTab(tab.label);
172
+ else this.active = this.indexOfTab(activeTab);
173
+ this.emit("tabs-change", this.id, this.active, this.tabs);
174
+ }
175
+ moveTabById(tabId, targetId, position, activateTab = false) {
176
+ const activeTabId = this.activeTab.id;
177
+ const newTabs = this.tabs.slice();
178
+ const indexOfMovedTab = newTabs.findIndex(({ id })=>id === tabId);
179
+ const [movedTab] = newTabs.splice(indexOfMovedTab, 1);
180
+ const indexOfTargetTab = newTabs.findIndex(({ id })=>id === targetId);
181
+ newTabs.splice("after" === position ? indexOfTargetTab + 1 : indexOfTargetTab, 0, movedTab);
182
+ this.tabs = newTabs;
183
+ this.active = this.tabs.findIndex(({ id })=>id === (activateTab ? tabId : activeTabId));
184
+ this.emit("tabs-change", this.id, this.active, this.tabs);
185
+ }
186
+ addTab(tab, dropPosition) {
187
+ if (dropPosition) {
188
+ const { position, target } = dropPosition;
189
+ const pos = this.indexOfTab(target);
190
+ const newTabs = this.tabs.slice();
191
+ newTabs.splice("after" === position ? pos + 1 : pos, 0, tab);
192
+ this.active = newTabs.indexOf(tab);
193
+ this.tabs = newTabs;
194
+ this.emit("tab-added", this, tab);
195
+ } else {
196
+ const newTabs = this.tabs.concat([
197
+ tab
198
+ ]);
199
+ this.tabs = newTabs;
200
+ if (-1 === this.active) this.setActiveTab(tab.label);
201
+ }
202
+ }
203
+ removeTab(id) {
204
+ const activeTabId = this.activeTab.id;
205
+ const previousActive = this.active;
206
+ this.tabs = this.tabs.filter((tab)=>tab.id !== id);
207
+ this.active = activeTabId === id ? Math.min(previousActive, this.tabs.length - 1) : this.tabs.findIndex((tab)=>tab.id === activeTabId);
208
+ if (1 === this.tabs.length) this.emit("tabs-removed", this.id);
209
+ else this.emit("tabs-change", this.id, this.active, this.tabs);
210
+ }
211
+ renameTab(id, label) {
212
+ const tabIndex = this.tabs.findIndex((tab)=>tab.id === id);
213
+ if (-1 !== tabIndex) {
214
+ this.tabs[tabIndex] = {
215
+ id,
216
+ label
217
+ };
218
+ this.emit("tabs-change", this.id, this.active, this.tabs);
219
+ } else throw Error(`[TabState] cannot rename tab #${id} => '${label}', tab not found`);
220
+ }
221
+ get activeTab() {
222
+ return this.tabs[this.active];
223
+ }
224
+ getNextActiveTab() {
225
+ const pos = this.tabs.indexOf(this.activeTab);
226
+ return pos > 0 ? this.tabs[pos - 1] : this.tabs[pos + 1];
227
+ }
228
+ indexOfTab(label, tabs = this.tabs) {
229
+ return tabs.findIndex((tab)=>tab.label === label);
230
+ }
231
+ }
232
+ class GridTrack {
233
+ static fromTrackSize = (trackSize)=>new GridTrack(trackSize);
234
+ #measuredValue = -1;
235
+ #fractions = -1;
236
+ #pixels = -1;
237
+ constructor(trackSize){
238
+ this.trackSize = trackSize;
239
+ }
240
+ get hasBeenMeasured() {
241
+ return -1 !== this.#measuredValue;
242
+ }
243
+ get isFraction() {
244
+ return -1 !== this.#fractions;
245
+ }
246
+ get isPixelValue() {
247
+ return -1 !== this.#pixels;
248
+ }
249
+ get isMeasured() {
250
+ return -1 !== this.measuredValue;
251
+ }
252
+ get measuredValue() {
253
+ return this.#measuredValue;
254
+ }
255
+ set measuredValue(value) {
256
+ this.#measuredValue = value;
257
+ }
258
+ convertUnitsToPixels() {
259
+ if (this.hasBeenMeasured) {
260
+ this.#pixels = this.#measuredValue;
261
+ this.#fractions = -1;
262
+ } else throw Error("[GridTrack] convertUnitToPixels, tracks must be measured before calling this method");
263
+ }
264
+ get hasNumericValue() {
265
+ return this.isPixelValue || this.isMeasured;
266
+ }
267
+ get numericValue() {
268
+ if (this.isPixelValue) return this.#pixels;
269
+ if (-1 !== this.#measuredValue) return this.#measuredValue;
270
+ throw Error("[GridTrack] getter numericValue, trackSize is neither numeric or a pixel value");
271
+ }
272
+ get trackSize() {
273
+ return -1 !== this.#fractions ? `${this.#fractions}fr` : `${this.#pixels}px`;
274
+ }
275
+ set trackSize(trackSize) {
276
+ if (isPixelUnit(trackSize)) {
277
+ this.#pixels = parseFloat(trackSize);
278
+ this.#fractions = -1;
279
+ } else {
280
+ this.#fractions = parseFloat(trackSize);
281
+ this.#pixels = -1;
282
+ }
283
+ this.#measuredValue = -1;
284
+ }
285
+ addFraction(trackSize) {
286
+ if (this.isFraction && isFractionUnit(trackSize)) this.#fractions += parseFloat(trackSize);
287
+ else throw Error("Track.addFraction, both trackSize values must be fractions");
288
+ }
289
+ increment(value) {
290
+ if (this.isFraction && !this.isMeasured) throw Error(`[GridTrack] increment, value ${this.trackSize} cannot be incremented unless measured first`);
291
+ if (this.isFraction) this.convertUnitsToPixels();
292
+ this.#pixels += value;
293
+ }
294
+ toString = ()=>this.trackSize;
295
+ }
296
+ class GridTracks extends EventEmitter {
297
+ gridId;
298
+ #columns;
299
+ #rows;
300
+ constructor(gridId, columns, rows){
301
+ super(), this.gridId = gridId;
302
+ this.#columns = columns.map(GridTrack.fromTrackSize);
303
+ this.#rows = rows.map(GridTrack.fromTrackSize);
304
+ }
305
+ get columns() {
306
+ return this.#columns.map((col)=>col.trackSize);
307
+ }
308
+ set columns(columns) {
309
+ this.#columns = columns.map(GridTrack.fromTrackSize);
310
+ this.emit("grid-track-resize", "column", this.#columns);
311
+ }
312
+ get rows() {
313
+ return this.#rows.map((col)=>col.trackSize);
314
+ }
315
+ set rows(rows) {
316
+ this.#rows = rows.map(GridTrack.fromTrackSize);
317
+ this.emit("grid-track-resize", "row", this.#rows);
318
+ }
319
+ get colCount() {
320
+ return this.#columns.length;
321
+ }
322
+ get rowCount() {
323
+ return this.#rows.length;
324
+ }
325
+ getTracks(trackType) {
326
+ return "column" === trackType ? this.#columns : this.#rows;
327
+ }
328
+ createCheckpoint() {
329
+ const checkpointTrack = (track)=>({
330
+ measuredValue: track.measuredValue,
331
+ size: track.trackSize,
332
+ track
333
+ });
334
+ return {
335
+ columns: this.#columns.map(checkpointTrack),
336
+ rows: this.#rows.map(checkpointTrack)
337
+ };
338
+ }
339
+ restoreCheckpoint({ columns, rows }) {
340
+ const restoreTrack = ({ measuredValue, size, track })=>{
341
+ track.trackSize = size;
342
+ if (-1 !== measuredValue) track.measuredValue = measuredValue;
343
+ return track;
344
+ };
345
+ this.#columns = columns.map(restoreTrack);
346
+ this.#rows = rows.map(restoreTrack);
347
+ this.emit("grid-track-resize", "column", this.#columns);
348
+ this.emit("grid-track-resize", "row", this.#rows);
349
+ }
350
+ measure(trackType) {
351
+ const el = document.getElementById(this.gridId);
352
+ if (el) {
353
+ const tracks = this.getTracks(trackType);
354
+ const measuredTracks = getComputedStyle(el).getPropertyValue(`grid-template-${trackType}s`).split(" ").map((value)=>parseFloat(value));
355
+ measuredTracks.forEach((val, i)=>{
356
+ void 0 === tracks[i] || (tracks[i].measuredValue = val);
357
+ });
358
+ } else throw Error(`[GridTracks] measure no grid element found #${this.gridId}`);
359
+ }
360
+ getTrackMetrics(trackType) {
361
+ return trackMetrics(this.getTracks(trackType).map((track)=>track.measuredValue));
362
+ }
363
+ readGeometryTracks(trackType) {
364
+ return readTracks(this.getTracks(trackType).map(({ trackSize })=>trackSize), this.getTrackMetrics(trackType));
365
+ }
366
+ runTrackGeometry(trackType, compute) {
367
+ const result = compute(this.readGeometryTracks(trackType));
368
+ if (!result.ok && "MEASUREMENT_REQUIRED" === result.error.code) {
369
+ this.measure(trackType);
370
+ return compute(this.readGeometryTracks(trackType));
371
+ }
372
+ return result;
373
+ }
374
+ runTrackTransition(trackType, compute) {
375
+ const result = this.runTrackGeometry(trackType, compute);
376
+ if (!result.ok) throw Error(result.error.message);
377
+ return result.value;
378
+ }
379
+ applyTrackTransition(trackType, { sources, tracks: nextTracks }) {
380
+ const currentTracks = this.getTracks(trackType);
381
+ const claimedSources = new Set();
382
+ const tracks = nextTracks.map(({ measured, size }, index)=>{
383
+ const source = sources[index] ?? -1;
384
+ const existingTrack = -1 === source || claimedSources.has(source) ? void 0 : currentTracks[source];
385
+ if (existingTrack) claimedSources.add(source);
386
+ const track = existingTrack ?? new GridTrack(size);
387
+ if (track.trackSize !== size || track.measuredValue !== measured) {
388
+ track.trackSize = size;
389
+ if (-1 !== measured) track.measuredValue = measured;
390
+ }
391
+ return track;
392
+ });
393
+ if ("column" === trackType) this.#columns = tracks;
394
+ else this.#rows = tracks;
395
+ this.emit("grid-track-resize", trackType, tracks);
396
+ }
397
+ getBisectingTrack(trackType, startIndex, endIndex) {
398
+ return this.runTrackTransition(trackType, (tracks)=>findBisectingTrack(tracks, startIndex, endIndex, trackType));
399
+ }
400
+ splitTrack(trackType, trackIndex) {
401
+ this.applyTrackTransition(trackType, this.runTrackTransition(trackType, (tracks)=>splitTrack(tracks, trackIndex, trackType)));
402
+ }
403
+ splitTracks(trackType, fromTrackLine, toTrackLine) {
404
+ const { newTrackIndex, transition } = this.runTrackTransition(trackType, (tracks)=>bisectTracks(tracks, fromTrackLine, toTrackLine, trackType));
405
+ this.applyTrackTransition(trackType, transition);
406
+ return newTrackIndex;
407
+ }
408
+ insertTrack(trackType, { index, position }, trackSize) {
409
+ const transition = this.runTrackTransition(trackType, (tracks)=>insertTrack(tracks, {
410
+ index,
411
+ position
412
+ }, trackSize, trackType));
413
+ if (transition) this.applyTrackTransition(trackType, transition);
414
+ }
415
+ removeTrack(trackType, index, assignDirection = "fwd") {
416
+ this.applyTrackTransition(trackType, this.runTrackTransition(trackType, (tracks)=>removeTrack(tracks, index, assignDirection, trackType)));
417
+ }
418
+ resizeTo(trackType, trackIndex, value) {
419
+ this.applyTrackTransition(trackType, this.runTrackTransition(trackType, (tracks)=>resizeTrackTo(tracks, trackIndex, value, trackType)));
420
+ }
421
+ resizeBy(trackType, trackIndex, contraTrackIndex, value) {
422
+ this.applyTrackTransition(trackType, this.runTrackTransition(trackType, (tracks)=>resizeTracksAdjacent(tracks, {
423
+ contraTrackIndex,
424
+ delta: value,
425
+ resizedTrackIndex: trackIndex
426
+ }, trackType)));
427
+ }
428
+ resizeGroupsProportionally(trackType, beforeTrackIndices, afterTrackIndices, value, beforeConstraints = [], afterConstraints = [], initialTrackSizes) {
429
+ this.applyTrackTransition(trackType, this.runTrackTransition(trackType, (tracks)=>resizeTracksProportionally(tracks, {
430
+ afterConstraints,
431
+ afterTrackIndices,
432
+ beforeConstraints,
433
+ beforeTrackIndices,
434
+ delta: value,
435
+ initialSizes: initialTrackSizes
436
+ }, trackType)));
437
+ }
438
+ getProportionalResizeAllowance(trackType, trackIndices, constraints) {
439
+ return this.runTrackTransition(trackType, (tracks)=>getProportionalResizeAllowance(tracks, trackIndices, constraints, trackType));
440
+ }
441
+ get css() {
442
+ return {
443
+ gridTemplateColumns: this.#columns.join(" "),
444
+ gridTemplateRows: this.#rows.join(" ")
445
+ };
446
+ }
447
+ serialize() {
448
+ return {
449
+ cols: this.columns,
450
+ rows: this.rows
451
+ };
452
+ }
453
+ toString() {
454
+ return `
455
+ grid-template-columns: ${this.#columns.join(" ")}
456
+ grid-template-rows: ${this.#rows.join(" ")}
457
+ `;
458
+ }
459
+ }
460
+ class GridModel extends EventEmitter {
461
+ id;
462
+ tracks;
463
+ #childItems = [];
464
+ #index = new Map();
465
+ #tabState = new Map();
466
+ #stackStates = new Map();
467
+ createCheckpoint() {
468
+ return {
469
+ childItems: this.#childItems.map((item)=>({
470
+ item,
471
+ state: {
472
+ column: {
473
+ end: item.column.end,
474
+ start: item.column.start
475
+ },
476
+ contentDetached: item.contentDetached,
477
+ contentVisible: item.contentVisible,
478
+ dragging: item.dragging,
479
+ dropTarget: item.dropTarget,
480
+ header: item.header,
481
+ height: item.height,
482
+ horizontalSplitter: item.horizontalSplitter,
483
+ minHeight: item.minHeight,
484
+ minWidth: item.minWidth,
485
+ resizeable: item.resizeable,
486
+ row: {
487
+ end: item.row.end,
488
+ start: item.row.start
489
+ },
490
+ stackId: item.stackId,
491
+ title: item.title,
492
+ type: item.type,
493
+ verticalSplitter: item.verticalSplitter,
494
+ width: item.width
495
+ }
496
+ })),
497
+ tabStates: [
498
+ ...this.#tabState.values()
499
+ ].map((tabState)=>({
500
+ active: tabState.active,
501
+ detachedTab: tabState.detachedTab ? {
502
+ ...tabState.detachedTab
503
+ } : void 0,
504
+ tabState,
505
+ tabs: tabState.tabs.map((tab)=>({
506
+ ...tab
507
+ }))
508
+ })),
509
+ stackStates: [
510
+ ...this.#stackStates
511
+ ].map(([stackId, state])=>[
512
+ stackId,
513
+ cloneGridStackState(state)
514
+ ]),
515
+ tracks: this.tracks.createCheckpoint()
516
+ };
517
+ }
518
+ restoreCheckpoint({ childItems, stackStates, tabStates, tracks }) {
519
+ const currentStacks = new Map(this.#childItems.filter(({ type })=>"stacked-content" === type).map((item)=>[
520
+ item.id,
521
+ item
522
+ ]));
523
+ const currentTabStates = new Map([
524
+ ...this.#tabState
525
+ ].map(([id, { active, tabs }])=>[
526
+ id,
527
+ {
528
+ active,
529
+ tabs: tabs.map((tab)=>({
530
+ ...tab
531
+ }))
532
+ }
533
+ ]));
534
+ this.tracks.restoreCheckpoint(tracks);
535
+ this.#childItems = childItems.map(({ item, state })=>{
536
+ item.column.start = state.column.start;
537
+ item.column.end = state.column.end;
538
+ item.contentDetached = state.contentDetached;
539
+ item.contentVisible = state.contentVisible;
540
+ item.dragging = state.dragging;
541
+ item.dropTarget = state.dropTarget;
542
+ item.header = state.header;
543
+ item.height = state.height;
544
+ item.horizontalSplitter = state.horizontalSplitter;
545
+ item.minHeight = state.minHeight;
546
+ item.minWidth = state.minWidth;
547
+ item.resizeable = state.resizeable;
548
+ item.row.start = state.row.start;
549
+ item.row.end = state.row.end;
550
+ item.stackId = state.stackId;
551
+ item.title = state.title;
552
+ item.type = state.type;
553
+ item.verticalSplitter = state.verticalSplitter;
554
+ item.width = state.width;
555
+ return item;
556
+ });
557
+ this.#index = new Map(this.#childItems.map((item)=>[
558
+ item.id,
559
+ item
560
+ ]));
561
+ this.#tabState = new Map(tabStates.map(({ active, detachedTab, tabs, tabState })=>{
562
+ tabState.active = active;
563
+ tabState.detachedTab = detachedTab ? {
564
+ ...detachedTab
565
+ } : void 0;
566
+ tabState.tabs = tabs.map((tab)=>({
567
+ ...tab
568
+ }));
569
+ return [
570
+ tabState.id,
571
+ tabState
572
+ ];
573
+ }));
574
+ this.#stackStates = new Map(stackStates.map(([stackId, state])=>[
575
+ stackId,
576
+ cloneGridStackState(state)
577
+ ]));
578
+ const restoredStacks = new Map(this.#childItems.filter(({ type })=>"stacked-content" === type).map((item)=>[
579
+ item.id,
580
+ item
581
+ ]));
582
+ for (const stackId of currentStacks.keys())if (!restoredStacks.has(stackId)) this.emit("tabs-removed", stackId);
583
+ for (const [stackId, stackItem] of restoredStacks)if (currentStacks.has(stackId)) {
584
+ const restored = this.#tabState.get(stackId);
585
+ const current = currentTabStates.get(stackId);
586
+ if (restored && current && (restored.active !== current.active || JSON.stringify(restored.tabs) !== JSON.stringify(current.tabs))) {
587
+ this.emit("tabs-change", stackId, restored.active, restored.tabs.map((tab)=>({
588
+ ...tab
589
+ })));
590
+ this.emit("tab-selection-change", stackId, restored.active);
591
+ }
592
+ } else this.emit("tabs-created", stackItem);
593
+ }
594
+ constructor(id, { cols, gridLayoutItems, rows }){
595
+ super(), this.id = id;
596
+ assertValidTracks("col", cols);
597
+ assertValidTracks("row", rows);
598
+ this.tracks = new GridTracks(id, cols, rows);
599
+ if (gridLayoutItems) this.addChildItems(gridLayoutItems);
600
+ }
601
+ get childItems() {
602
+ return this.#childItems;
603
+ }
604
+ handleTabsChange = (stackId, active, tabs)=>{
605
+ const activeTab = tabs[active];
606
+ this.activateStackedChildItem(stackId, activeTab);
607
+ this.emit("tabs-change", stackId, active, tabs);
608
+ };
609
+ handleTabsRemoved = (stackId)=>{
610
+ this.removeChildItem(stackId, "unstack");
611
+ this.emit("tabs-removed", stackId);
612
+ };
613
+ handleTabSelectionChange = ({ active, activeTab, id: stackId })=>{
614
+ this.activateStackedChildItem(stackId, activeTab);
615
+ this.emit("tab-selection-change", stackId, active);
616
+ };
617
+ handleTabAdded = ({ activeTab, id, tabs })=>{
618
+ const active = tabs.indexOf(activeTab);
619
+ const stackItem = this.getChildItem(id, true);
620
+ const stackedItem = this.getChildItem(activeTab.id, true);
621
+ stackedItem.stackId = id;
622
+ this.updateChildColumn(activeTab.id, stackItem.column);
623
+ this.updateChildRow(activeTab.id, stackItem.row);
624
+ this.activateStackedChildItem(id, activeTab);
625
+ this.emit("child-position-updates", [
626
+ [
627
+ activeTab.id,
628
+ {
629
+ column: stackItem.column,
630
+ row: stackItem.row
631
+ }
632
+ ]
633
+ ]);
634
+ this.emit("tabs-change", id, active, tabs);
635
+ };
636
+ handleTabDetached = ({ activeTab, id: stackId })=>{
637
+ this.detachStackedChildItem(stackId, activeTab);
638
+ };
639
+ getTabState = (tabsId, fallbackAction = "throw")=>{
640
+ const tabState = this.#tabState.get(tabsId);
641
+ if (tabState) return tabState;
642
+ if ("create" === fallbackAction) {
643
+ this.setTabState(tabsId, [], -1);
644
+ return this.getTabState(tabsId, "throw");
645
+ }
646
+ throw Error(`[GridModel#${this.id}] getTabState, no tabState found for tabs #${tabsId} and no initialisation params provided`);
647
+ };
648
+ setTabState(stackId, childItems, activeItem = 0) {
649
+ const tabs = childItems.map(({ id, title }, index)=>({
650
+ id,
651
+ label: title ?? `Label-${index + 1}`
652
+ }));
653
+ const tabState = this.#createTabState(stackId, tabs, activeItem);
654
+ if (tabs[activeItem]) this.activateStackedChildItem(stackId, tabs[activeItem]);
655
+ return tabState;
656
+ }
657
+ #createTabState(stackId, tabs, active) {
658
+ if (this.#tabState.get(stackId)) throw Error(`[GridModel] setTabState already created for ${stackId}`);
659
+ const tabState = new TabState(stackId, active, tabs);
660
+ tabState.on("active-change", this.handleTabSelectionChange);
661
+ tabState.on("tab-added", this.handleTabAdded);
662
+ tabState.on("tab-detached", this.handleTabDetached);
663
+ tabState.on("tabs-change", this.handleTabsChange);
664
+ tabState.on("tabs-removed", this.handleTabsRemoved);
665
+ this.#tabState.set(stackId, tabState);
666
+ return tabState;
667
+ }
668
+ getStackState = (stackId)=>{
669
+ const canonical = this.#stackStates.get(stackId) ?? this.#hydrateStack(stackId);
670
+ const anchor = this.getChildItem(stackId) ?? this.getChildItem(canonical.members[0]?.id ?? "");
671
+ const state = normalizeGridStackState({
672
+ ...canonical,
673
+ area: anchor ? toStackArea(anchor) : canonical.area,
674
+ metadata: anchor ? {
675
+ minHeight: anchor.minHeight,
676
+ minWidth: anchor.minWidth,
677
+ resizeable: anchor.resizeable
678
+ } : canonical.metadata
679
+ });
680
+ this.#stackStates.set(stackId, state);
681
+ return state;
682
+ };
683
+ #hydrateStack(stackId) {
684
+ const { active, tabs } = this.getTabState(stackId);
685
+ return {
686
+ area: {
687
+ column: {
688
+ span: 1,
689
+ start: 1
690
+ },
691
+ row: {
692
+ span: 1,
693
+ start: 1
694
+ }
695
+ },
696
+ id: stackId,
697
+ members: tabs.map(({ id, label })=>({
698
+ id,
699
+ label,
700
+ title: this.getChildItem(id)?.title
701
+ })),
702
+ metadata: {},
703
+ selectedItemId: tabs[active]?.id ?? ""
704
+ };
705
+ }
706
+ getStackStates() {
707
+ return this.#childItems.filter(({ type })=>"stacked-content" === type).map(({ id })=>this.getStackState(id));
708
+ }
709
+ applyStackTransition(transition) {
710
+ const { operation, state } = transition;
711
+ if (transition.dissolved) this.#stackStates.delete(state.id);
712
+ else this.#stackStates.set(state.id, state);
713
+ switch(operation){
714
+ case "create":
715
+ {
716
+ const [reference] = state.members;
717
+ const referenceItem = this.getChildItem(reference.id, true);
718
+ const stackItem = new GridModelChildItem({
719
+ column: toGridModelPosition(state.area.column),
720
+ id: state.id,
721
+ minHeight: state.metadata.minHeight,
722
+ minWidth: state.metadata.minWidth,
723
+ resizeable: state.metadata.resizeable,
724
+ row: toGridModelPosition(state.area.row),
725
+ type: "stacked-content"
726
+ });
727
+ stackItem.horizontalSplitter = referenceItem.horizontalSplitter;
728
+ stackItem.verticalSplitter = referenceItem.verticalSplitter;
729
+ this.#createTabState(state.id, state.members.map(({ id, label })=>({
730
+ id,
731
+ label
732
+ })), gridStackSelectedIndex(state));
733
+ this.#registerChildItem(stackItem);
734
+ for (const { id } of state.members){
735
+ const member = this.getChildItem(id, true);
736
+ member.horizontalSplitter = referenceItem.horizontalSplitter;
737
+ member.verticalSplitter = referenceItem.verticalSplitter;
738
+ }
739
+ this.#applyStackPlacement(state);
740
+ this.#applyStackSelection(state);
741
+ this.emit("tabs-created", stackItem);
742
+ return;
743
+ }
744
+ case "add":
745
+ {
746
+ this.#applyStackPlacement(state);
747
+ const tabState = this.#writeTabState(state);
748
+ this.#applyStackSelection(state);
749
+ if (transition.positioned) {
750
+ const [{ id: addedId }] = transition.added;
751
+ const { column, row } = this.getChildItem(addedId, true);
752
+ this.emit("child-position-updates", [
753
+ [
754
+ addedId,
755
+ {
756
+ column,
757
+ row
758
+ }
759
+ ]
760
+ ]);
761
+ this.emit("tabs-change", state.id, tabState.active, tabState.tabs);
762
+ }
763
+ return;
764
+ }
765
+ case "remove":
766
+ {
767
+ const tabState = this.#writeTabState(state);
768
+ if (transition.dissolved) {
769
+ this.removeChildItem(state.id, "unstack");
770
+ this.emit("tabs-removed", state.id);
771
+ } else {
772
+ this.#applyStackSelection(state);
773
+ this.emit("tabs-change", state.id, tabState.active, tabState.tabs);
774
+ }
775
+ return;
776
+ }
777
+ case "rename":
778
+ case "reorder":
779
+ {
780
+ const tabState = this.#writeTabState(state);
781
+ this.#applyStackSelection(state);
782
+ this.emit("tabs-change", state.id, tabState.active, tabState.tabs);
783
+ return;
784
+ }
785
+ case "select":
786
+ {
787
+ const tabState = this.#writeTabState(state);
788
+ this.#applyStackSelection(state);
789
+ this.emit("tab-selection-change", state.id, tabState.active);
790
+ return;
791
+ }
792
+ default:
793
+ throw Error(`[GridModel] unhandled stack transition ${operation}`);
794
+ }
795
+ }
796
+ createStack(targetId, itemId, stackId = uuid()) {
797
+ const targetChild = this.getChildItem(targetId, true);
798
+ const stackedChild = this.getChildItem(itemId, true);
799
+ const result = createGridStack({
800
+ area: toStackArea(targetChild),
801
+ id: stackId,
802
+ members: [
803
+ targetChild,
804
+ stackedChild
805
+ ].map(({ id, title }, index)=>({
806
+ id,
807
+ label: title ?? `Label-${index + 1}`,
808
+ title
809
+ })),
810
+ metadata: {
811
+ minHeight: targetChild.minHeight,
812
+ minWidth: targetChild.minWidth,
813
+ resizeable: targetChild.resizeable
814
+ }
815
+ });
816
+ if (result.ok) this.applyStackTransition(result.value);
817
+ return result;
818
+ }
819
+ addStackItem(stackId, childItem, dropPosition) {
820
+ const result = this.#addStackTransition(stackId, childItem, dropPosition);
821
+ if (result.ok) {
822
+ this.#registerChildItem(childItem);
823
+ this.applyStackTransition(result.value);
824
+ }
825
+ return result;
826
+ }
827
+ addStackMember(stackId, itemId, dropPosition) {
828
+ const result = this.#addStackTransition(stackId, this.getChildItem(itemId, true), dropPosition);
829
+ if (result.ok) this.applyStackTransition(result.value);
830
+ return result;
831
+ }
832
+ #addStackTransition(stackId, { id, title }, dropPosition) {
833
+ const state = this.getStackState(stackId);
834
+ const position = dropPosition ? {
835
+ placement: dropPosition.position,
836
+ targetItemId: state.members.find(({ label })=>label === dropPosition.target)?.id ?? dropPosition.target
837
+ } : void 0;
838
+ return addGridStackItem(state, {
839
+ member: {
840
+ id,
841
+ label: title ?? id,
842
+ title
843
+ },
844
+ position
845
+ });
846
+ }
847
+ removeStackItem(stackId, itemId) {
848
+ const result = removeGridStackItem(this.getStackState(stackId), {
849
+ itemId
850
+ });
851
+ if (result.ok) this.applyStackTransition(result.value);
852
+ return result;
853
+ }
854
+ selectStackItem(stackId, itemId) {
855
+ const result = selectGridStackItem(this.getStackState(stackId), {
856
+ itemId
857
+ });
858
+ if (result.ok) this.applyStackTransition(result.value);
859
+ return result;
860
+ }
861
+ reorderStackItem(stackId, request) {
862
+ const result = reorderGridStackItem(this.getStackState(stackId), request);
863
+ if (result.ok) this.applyStackTransition(result.value);
864
+ return result;
865
+ }
866
+ #renameStackItem(stackId, itemId, title) {
867
+ const result = renameGridStackItem(this.getStackState(stackId), {
868
+ itemId,
869
+ title
870
+ });
871
+ if (result.ok) this.applyStackTransition(result.value);
872
+ return result;
873
+ }
874
+ #writeTabState(state) {
875
+ const tabState = this.getTabState(state.id);
876
+ tabState.tabs = state.members.map(({ id, label })=>({
877
+ id,
878
+ label
879
+ }));
880
+ tabState.active = gridStackSelectedIndex(state);
881
+ return tabState;
882
+ }
883
+ #applyStackPlacement(state) {
884
+ const column = toGridModelPosition(state.area.column);
885
+ const row = toGridModelPosition(state.area.row);
886
+ for (const { id } of state.members){
887
+ const member = this.getChildItem(id);
888
+ if (member) {
889
+ member.stackId = state.id;
890
+ this.updateChildColumn(id, column);
891
+ this.updateChildRow(id, row);
892
+ }
893
+ }
894
+ }
895
+ #applyStackSelection(state) {
896
+ const selected = state.members.find(({ id })=>id === state.selectedItemId);
897
+ if (selected) this.activateStackedChildItem(state.id, selected);
898
+ }
899
+ activateStackedChildItem(stackId, { id }) {
900
+ const stackedChildren = this.getStackedChildItems(stackId);
901
+ stackedChildren.forEach((child)=>{
902
+ if (child.id === id) {
903
+ child.contentVisible = true;
904
+ child.contentDetached = void 0;
905
+ } else child.contentVisible = false;
906
+ });
907
+ }
908
+ detachStackedChildItem(stackId, { id }) {
909
+ const stackedChildren = this.getStackedChildItems(stackId);
910
+ const detachedChild = stackedChildren.find(({ id: itemId })=>itemId === id);
911
+ if (detachedChild) detachedChild.contentDetached = true;
912
+ }
913
+ detachTab(stackId, label) {
914
+ const tabState = this.getTabState(stackId);
915
+ const state = this.getStackState(stackId);
916
+ const selectedIndex = gridStackSelectedIndex(state);
917
+ if (state.members[selectedIndex]?.label !== label) return;
918
+ const next = state.members[selectedIndex - 1] ?? state.members[selectedIndex + 1];
919
+ if (!next) return;
920
+ tabState.detachedTab = {
921
+ ...tabState.tabs[selectedIndex]
922
+ };
923
+ this.detachStackedChildItem(stackId, state.members[selectedIndex]);
924
+ const result = this.selectStackItem(stackId, next.id);
925
+ if (!result.ok) throw Error(result.error.message);
926
+ }
927
+ restoreDetachedTab(stackId, label) {
928
+ const tabState = this.getTabState(stackId);
929
+ const detachedTab = tabState.detachedTab;
930
+ if (detachedTab?.label === label) {
931
+ tabState.detachedTab = void 0;
932
+ const result = this.selectStackItem(stackId, detachedTab.id);
933
+ if (!result.ok) throw Error(result.error.message);
934
+ }
935
+ }
936
+ moveItemWithinTabs(tabsId, tab, { position, target }, selectMovedTab) {
937
+ const state = this.getStackState(tabsId);
938
+ const result = this.reorderStackItem(tabsId, {
939
+ activate: selectMovedTab,
940
+ itemId: tab.id,
941
+ placement: position,
942
+ targetItemId: state.members.find(({ label })=>label === target)?.id ?? target
943
+ });
944
+ if (!result.ok) throw Error(result.error.message);
945
+ }
946
+ moveItemBetweenTabs(fromTabsId, toTabsId, tab, dropPosition) {
947
+ const removed = this.removeStackItem(fromTabsId, tab.id);
948
+ if (!removed.ok) throw Error(removed.error.message);
949
+ const added = this.addStackMember(toTabsId, tab.id, dropPosition);
950
+ if (!added.ok) throw Error(added.error.message);
951
+ }
952
+ notifyChange() {
953
+ this.emit("grid-layout-change", this.id, this.toGridLayoutDescriptor());
954
+ }
955
+ restoreLayout({ cols, gridLayoutItems = {}, rows }) {
956
+ this.clearPlaceholders();
957
+ this.tracks.columns = cols;
958
+ this.tracks.rows = rows;
959
+ for (const [id, { gridArea }] of Object.entries(gridLayoutItems)){
960
+ const child = this.getChildItem(id);
961
+ if (child) {
962
+ const { column, row } = getGridPosition(gridArea);
963
+ this.updateChildColumn(id, column);
964
+ this.updateChildRow(id, row);
965
+ child.dragging = false;
966
+ }
967
+ }
968
+ this.createPlaceholders();
969
+ }
970
+ toGridLayoutDescriptor() {
971
+ return {
972
+ ...this.tracks.serialize(),
973
+ gridLayoutItems: this.#childItems.reduce((result, { id, column, componentInstanceId, contentVisible, dropTarget, header, minHeight, minWidth, resizeable, row, stackId, title, type, dragging })=>{
974
+ if ("stacked-content" !== type && !(dragging && !stackId)) result[id] = {
975
+ ...void 0 === componentInstanceId ? {} : {
976
+ componentId: componentInstanceId
977
+ },
978
+ contentVisible,
979
+ dropTarget,
980
+ gridArea: `${row.start}/${column.start}/${row.end}/${column.end}`,
981
+ header,
982
+ minHeight,
983
+ minWidth,
984
+ resizeable,
985
+ stackId,
986
+ title
987
+ };
988
+ return result;
989
+ }, {})
990
+ };
991
+ }
992
+ splitGridTrack(trackType, trackIndex) {
993
+ this.tracks.splitTrack(trackType, trackIndex);
994
+ this.updateChildItemsToAccommodateNewTrack(trackType, trackIndex);
995
+ }
996
+ splitGridTracks(trackType, fromTrackLine, toTrackLine) {
997
+ const trackIndex = this.tracks.splitTracks(trackType, fromTrackLine, toTrackLine);
998
+ this.updateChildItemsToAccommodateNewTrack(trackType, trackIndex);
999
+ return trackIndex;
1000
+ }
1001
+ updateChildItemsToAccommodateNewTrack(trackType, trackIndex) {
1002
+ this.applyUpdates(toGridItemUpdates(shiftItemsForNewTrack(this.toGeometry().items, trackType, trackIndex)));
1003
+ }
1004
+ insertGridTrack(trackType, { index, position }, trackSize) {
1005
+ this.tracks.insertTrack(trackType, {
1006
+ index,
1007
+ position
1008
+ }, trackSize);
1009
+ const updates = toGridItemUpdates(shiftItemsForInsertedTrack(this.toGeometry().items, trackType, index, position));
1010
+ this.applyUpdates(updates);
1011
+ this.emit("child-position-updates", updates, {
1012
+ splitters: true
1013
+ });
1014
+ }
1015
+ removeGridTrack(trackType, trackIndex, assignDirection, updateChildItems = true) {
1016
+ const geometry = this.toGeometry();
1017
+ const result = this.runGeometry((measurements)=>removeGridTrack(geometry, {
1018
+ assignDirection,
1019
+ trackIndex,
1020
+ trackType,
1021
+ updateItems: updateChildItems
1022
+ }, measurements));
1023
+ if (!result.ok) throw Error(result.error.message);
1024
+ const { columns, rows, updates } = result.value;
1025
+ const trackTransition = "column" === trackType ? columns : rows;
1026
+ if (trackTransition) this.tracks.applyTrackTransition(trackType, trackTransition);
1027
+ if (updateChildItems) {
1028
+ const itemUpdates = toGridItemUpdates(updates);
1029
+ this.applyUpdates(itemUpdates);
1030
+ this.emit("child-position-updates", itemUpdates, {
1031
+ splitters: true
1032
+ });
1033
+ }
1034
+ }
1035
+ toGeometry() {
1036
+ return {
1037
+ columns: this.tracks.columns,
1038
+ items: this.#childItems.map(({ column, dragging, id, resizeable, row, stackId, type })=>({
1039
+ column: {
1040
+ end: column.end,
1041
+ start: column.start
1042
+ },
1043
+ dragging,
1044
+ id,
1045
+ resizeable,
1046
+ row: {
1047
+ end: row.end,
1048
+ start: row.start
1049
+ },
1050
+ stackId,
1051
+ type
1052
+ })),
1053
+ rows: this.tracks.rows
1054
+ };
1055
+ }
1056
+ getMeasurements() {
1057
+ return {
1058
+ column: this.tracks.getTrackMetrics("column"),
1059
+ row: this.tracks.getTrackMetrics("row")
1060
+ };
1061
+ }
1062
+ runGeometry(compute) {
1063
+ const result = compute(this.getMeasurements());
1064
+ if (result.ok || "MEASUREMENT_REQUIRED" !== result.error.code || void 0 === result.error.trackType) return result;
1065
+ this.tracks.measure(result.error.trackType);
1066
+ return compute(this.getMeasurements());
1067
+ }
1068
+ applyGeometryPositions(geometry) {
1069
+ for (const { column, id, row } of geometry.items){
1070
+ const childItem = this.getChildItem(id);
1071
+ if (void 0 !== childItem) {
1072
+ if (childItem.column.start !== column.start || childItem.column.end !== column.end) this.updateChildColumn(id, {
1073
+ end: column.end,
1074
+ start: column.start
1075
+ });
1076
+ if (childItem.row.start !== row.start || childItem.row.end !== row.end) this.updateChildRow(id, {
1077
+ end: row.end,
1078
+ start: row.start
1079
+ });
1080
+ }
1081
+ }
1082
+ }
1083
+ applyUpdates(updates) {
1084
+ updates?.forEach(([id, { column: columnPosition, row: rowPosition }])=>{
1085
+ if (columnPosition) this.updateChildColumn(id, columnPosition);
1086
+ if (rowPosition) this.updateChildRow(id, rowPosition);
1087
+ });
1088
+ }
1089
+ addChildItems(childItems) {
1090
+ for (const [id, { componentId, contentVisible, dropTarget, header, minHeight, minWidth, resizeable, stackId, title, ...item }] of Object.entries(childItems)){
1091
+ const { column, row } = getGridPosition(item.gridArea);
1092
+ this.addChildItem(new GridModelChildItem({
1093
+ contentVisible,
1094
+ componentInstanceId: componentId,
1095
+ id,
1096
+ column,
1097
+ dropTarget,
1098
+ header,
1099
+ minHeight,
1100
+ minWidth,
1101
+ resizeable,
1102
+ row,
1103
+ stackId,
1104
+ title
1105
+ }));
1106
+ }
1107
+ }
1108
+ addChildItem(childItem, dropPosition) {
1109
+ if (childItem.stackId && this.getChildItem(childItem.stackId)) {
1110
+ const result = this.addStackItem(childItem.stackId, childItem, dropPosition);
1111
+ if (!result.ok) throw Error(result.error.message);
1112
+ return;
1113
+ }
1114
+ this.#registerChildItem(childItem);
1115
+ }
1116
+ #registerChildItem(childItem) {
1117
+ this.#childItems.push(childItem);
1118
+ this.#index.set(childItem.id, childItem);
1119
+ }
1120
+ updateChildTitle(childItemId, title) {
1121
+ const childItem = this.getChildItem(childItemId, true);
1122
+ if (title !== childItem.title) {
1123
+ childItem.title = title;
1124
+ if (childItem.stackId) {
1125
+ const result = this.#renameStackItem(childItem.stackId, childItemId, title);
1126
+ if (!result.ok) throw Error(`[TabState] cannot rename tab #${childItemId} => '${title}', tab not found`);
1127
+ }
1128
+ }
1129
+ }
1130
+ updateChildColumn(childItemId, { start, end }) {
1131
+ const childItem = this.getChildItem(childItemId, true);
1132
+ const { start: previousStart, end: previousEnd } = childItem.column;
1133
+ if (start !== previousStart) childItem.column.start = start;
1134
+ if (end !== previousEnd) childItem.column.end = end;
1135
+ if ("stacked-content" === childItem.type) {
1136
+ const stackedChildItems = this.getStackedChildItems(childItemId);
1137
+ stackedChildItems.forEach((childItem)=>{
1138
+ this.updateChildColumn(childItem.id, {
1139
+ start,
1140
+ end
1141
+ });
1142
+ });
1143
+ }
1144
+ }
1145
+ updateChildRow(childItemId, { start, end }) {
1146
+ const childItem = this.getChildItem(childItemId, true);
1147
+ const { start: previousStart, end: previousEnd } = childItem.row;
1148
+ if (start !== previousStart) childItem.row.start = start;
1149
+ if (end !== previousEnd) childItem.row.end = end;
1150
+ if ("stacked-content" === childItem.type) {
1151
+ const stackedChildItems = this.getStackedChildItems(childItemId);
1152
+ stackedChildItems.forEach((childItem)=>{
1153
+ this.updateChildRow(childItem.id, {
1154
+ start,
1155
+ end
1156
+ });
1157
+ });
1158
+ }
1159
+ }
1160
+ removeChildItem(childItemId, reason) {
1161
+ const childItem = this.getChildItem(childItemId, true);
1162
+ if ("drag" === reason) childItem.dragging = true;
1163
+ else {
1164
+ const indexOfDoomedItem = this.#childItems.indexOf(childItem);
1165
+ this.#childItems.splice(indexOfDoomedItem, 1);
1166
+ this.#index.delete(childItemId);
1167
+ if ("stacked-content" === childItem.type) {
1168
+ this.#stackStates.delete(childItemId);
1169
+ const stackedChildItems = this.getStackedChildItems(childItemId);
1170
+ stackedChildItems.forEach((childItem)=>{
1171
+ childItem.stackId = void 0;
1172
+ childItem.contentVisible = true;
1173
+ });
1174
+ }
1175
+ }
1176
+ }
1177
+ stackChildItems(targetId, stackedChildId) {
1178
+ const result = this.createStack(targetId, stackedChildId);
1179
+ if (!result.ok) throw Error(result.error.message);
1180
+ }
1181
+ getChildItem(childItemId, throwIfNotFound = false) {
1182
+ const gridItem = this.#index.get(childItemId);
1183
+ if (gridItem) return gridItem;
1184
+ if (throwIfNotFound) throw Error(`[GridModel] GridItem #${childItemId} not found`);
1185
+ }
1186
+ getStackedChildItems(stackId) {
1187
+ if (stackId) return this.childItems.filter((childItem)=>childItem.stackId === stackId);
1188
+ {
1189
+ const stackedChildren = this.childItems.filter(isStackedItem);
1190
+ return Map.groupBy(stackedChildren, ({ stackId })=>stackId);
1191
+ }
1192
+ }
1193
+ validateChildId(childItemId) {
1194
+ if (-1 !== this.#childItems.findIndex(({ id })=>id === childItemId)) return childItemId;
1195
+ throw Error(`[GridModel] validateChildId #${childItemId}`);
1196
+ }
1197
+ clearPlaceholders() {
1198
+ const placeHolders = this.getPlaceholders();
1199
+ placeHolders.forEach((placeholder)=>this.removeChildItem(placeholder.id, "placeholder"));
1200
+ }
1201
+ findUnusedGridLines() {
1202
+ const [unusedColLines, unusedRowLines] = findUnusedGridLines(this.toGeometry());
1203
+ return [
1204
+ [
1205
+ ...unusedColLines
1206
+ ],
1207
+ [
1208
+ ...unusedRowLines
1209
+ ]
1210
+ ];
1211
+ }
1212
+ createPlaceholders() {
1213
+ this.applyPlaceholderTransition(regeneratePlaceholders(this.toGeometry(), uuid));
1214
+ }
1215
+ applyPlaceholderTransition({ added, removedIds }) {
1216
+ removedIds.forEach((id)=>{
1217
+ if (this.getChildItem(id)) this.removeChildItem(id, "placeholder");
1218
+ });
1219
+ added.forEach(({ column, id, resizeable, row, type })=>{
1220
+ this.addChildItem(new GridModelChildItem({
1221
+ column: {
1222
+ end: column.end,
1223
+ start: column.start
1224
+ },
1225
+ id,
1226
+ resizeable,
1227
+ row: {
1228
+ end: row.end,
1229
+ start: row.start
1230
+ },
1231
+ type
1232
+ }));
1233
+ });
1234
+ }
1235
+ getPlaceholders() {
1236
+ return this.childItems.filter(isPlaceholder);
1237
+ }
1238
+ getSplitters() {
1239
+ const { horizontalSplitterItemIds, splitters, verticalSplitterItemIds } = computeSplitters(this.toGeometry());
1240
+ const horizontal = new Set(horizontalSplitterItemIds);
1241
+ const vertical = new Set(verticalSplitterItemIds);
1242
+ this.#childItems.forEach((childItem)=>{
1243
+ childItem.horizontalSplitter = horizontal.has(childItem.id);
1244
+ childItem.verticalSplitter = vertical.has(childItem.id);
1245
+ });
1246
+ return splitters.map(({ column, resizedChildItems, resizedGridTracks, row, ...splitter })=>({
1247
+ ...splitter,
1248
+ column: {
1249
+ end: column.end,
1250
+ start: column.start
1251
+ },
1252
+ resizedChildItems: {
1253
+ after: [
1254
+ ...resizedChildItems.after
1255
+ ],
1256
+ before: [
1257
+ ...resizedChildItems.before
1258
+ ]
1259
+ },
1260
+ resizedGridTracks: [
1261
+ ...resizedGridTracks
1262
+ ],
1263
+ row: {
1264
+ end: row.end,
1265
+ start: row.start
1266
+ }
1267
+ }));
1268
+ }
1269
+ toDebugString() {
1270
+ return this.#childItems.map((c)=>`${c.id.padEnd(10)} col ${c.column.start}/${c.column.end}, row ${c.row.start}/${c.row.end}`).join("\n");
1271
+ }
1272
+ findByColumnStart(col, childItems = this.#childItems) {
1273
+ const childItemsStartingAtCol = childItems.filter(({ column: { start } })=>start === col);
1274
+ return 0 === childItemsStartingAtCol.length ? void 0 : childItemsStartingAtCol;
1275
+ }
1276
+ findByColumnEnd(col, childItems = this.#childItems) {
1277
+ const childItemsEndingAtCol = childItems.filter(({ column: { end } })=>end === col);
1278
+ return 0 === childItemsEndingAtCol.length ? void 0 : childItemsEndingAtCol;
1279
+ }
1280
+ findByRowStart(row, childItems = this.#childItems) {
1281
+ const childItemsStartingAtRow = childItems.filter(({ row: { start } })=>start === row);
1282
+ return 0 === childItemsStartingAtRow.length ? void 0 : childItemsStartingAtRow;
1283
+ }
1284
+ findByRowEnd(row, childItems = this.#childItems) {
1285
+ const childItemsEndingAtRow = childItems.filter(({ row: { end } })=>end === row);
1286
+ return 0 === childItemsEndingAtRow.length ? void 0 : childItemsEndingAtRow;
1287
+ }
1288
+ }
1289
+ export { DEFAULT_MIN_GRID_ITEM_SIZE, GridModel, GridModelChildItem, GridTrack, GridTracks, NonContentResetOptions, TabState, isFractionUnit, isPixelUnit, isStackedItem, resolveMinimumGridItemSize, toGridItemUpdates };