@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,1298 @@
1
+ const NO_TRACK_METRICS = {
2
+ measured: []
3
+ };
4
+ const trackMetrics = (measured)=>({
5
+ measured: [
6
+ ...measured
7
+ ]
8
+ });
9
+ const NO_MEASUREMENTS = {};
10
+ const ok = (value)=>({
11
+ ok: true,
12
+ value
13
+ });
14
+ const failure = (code, message, trackType)=>({
15
+ error: {
16
+ code,
17
+ message,
18
+ trackType
19
+ },
20
+ ok: false
21
+ });
22
+ const measurementRequired = (trackType)=>failure("MEASUREMENT_REQUIRED", `[GridGeometry] ${trackType} tracks must be measured before this transition`, trackType);
23
+ const isFractionTrackSize = (size)=>"string" == typeof size && size.endsWith("fr");
24
+ const isPixelTrackSize = (size)=>"string" == typeof size && size.endsWith("px");
25
+ const fractionSize = (value)=>`${value}fr`;
26
+ const pixelSize = (value)=>`${value}px`;
27
+ const isFraction = ({ size })=>isFractionTrackSize(size);
28
+ const isPixelValue = ({ size })=>isPixelTrackSize(size);
29
+ const isMeasured = ({ measured })=>-1 !== measured;
30
+ const hasNumericValue = (track)=>isPixelValue(track) || isMeasured(track);
31
+ const numericValue = (track)=>isPixelValue(track) ? parseFloat(track.size) : track.measured;
32
+ const sizedTrack = (size)=>({
33
+ measured: -1,
34
+ size
35
+ });
36
+ const incrementTrack = (track, value, trackType)=>{
37
+ if (isFraction(track)) {
38
+ if (!isMeasured(track)) return measurementRequired(trackType);
39
+ return ok({
40
+ measured: track.measured,
41
+ size: pixelSize(track.measured + value)
42
+ });
43
+ }
44
+ return ok({
45
+ measured: track.measured,
46
+ size: pixelSize(parseFloat(track.size) + value)
47
+ });
48
+ };
49
+ const readTracks = (sizes, metrics = NO_TRACK_METRICS)=>sizes.map((size, index)=>({
50
+ measured: metrics.measured[index] ?? -1,
51
+ size
52
+ }));
53
+ const trackSizes = (tracks)=>tracks.map(({ size })=>size);
54
+ const geometryTracks = (geometry, trackType)=>"column" === trackType ? geometry.columns : geometry.rows;
55
+ const identitySources = (length)=>Array.from({
56
+ length
57
+ }, (_, index)=>index);
58
+ const transition = (tracks, sources)=>({
59
+ sources: [
60
+ ...sources
61
+ ],
62
+ tracks: [
63
+ ...tracks
64
+ ]
65
+ });
66
+ const composeTransitions = (first, second)=>void 0 === first ? second : {
67
+ sources: second.sources.map((source)=>-1 === source ? -1 : first.sources[source] ?? -1),
68
+ tracks: second.tracks
69
+ };
70
+ const splitTrack = (tracks, trackIndex, trackType)=>{
71
+ const targetTrack = tracks[trackIndex];
72
+ if (void 0 === targetTrack) return failure("TRACK_NOT_FOUND", `[GridGeometry] no ${trackType} track #${trackIndex}`);
73
+ const sources = identitySources(tracks.length);
74
+ if (isFraction(targetTrack)) {
75
+ const splitTrackSize = targetTrack.size;
76
+ const doubled = tracks.map((track)=>isFraction(track) ? sizedTrack(fractionSize(2 * parseFloat(track.size))) : track);
77
+ doubled[trackIndex] = sizedTrack(splitTrackSize);
78
+ return ok(transition([
79
+ ...doubled.slice(0, trackIndex),
80
+ {
81
+ measured: -1,
82
+ size: splitTrackSize
83
+ },
84
+ ...doubled.slice(trackIndex)
85
+ ], [
86
+ ...sources.slice(0, trackIndex),
87
+ -1,
88
+ ...sources.slice(trackIndex)
89
+ ]));
90
+ }
91
+ const sizeOfNewTrack = Math.floor(numericValue(targetTrack) / 2);
92
+ const reduced = incrementTrack(targetTrack, -sizeOfNewTrack, trackType);
93
+ if (!reduced.ok) return reduced;
94
+ const next = [
95
+ ...tracks
96
+ ];
97
+ next[trackIndex] = reduced.value;
98
+ return ok(transition([
99
+ ...next.slice(0, trackIndex),
100
+ {
101
+ measured: -1,
102
+ size: pixelSize(sizeOfNewTrack)
103
+ },
104
+ ...next.slice(trackIndex)
105
+ ], [
106
+ ...sources.slice(0, trackIndex),
107
+ -1,
108
+ ...sources.slice(trackIndex)
109
+ ]));
110
+ };
111
+ const findBisectingTrack = (tracks, startIndex, endIndex, trackType)=>{
112
+ const tracksInRange = tracks.slice(startIndex - 1, endIndex);
113
+ if (tracksInRange.every(isFraction)) {
114
+ const halfSize = tracksInRange.reduce((sum, track)=>sum + parseFloat(track.size), 0) / 2;
115
+ let size = 0;
116
+ for(let i = startIndex - 1; i < endIndex - 1; i++){
117
+ size += parseFloat(tracks[i].size);
118
+ if (Math.abs(halfSize - size) < Number.EPSILON) return ok(i + 2);
119
+ }
120
+ return ok(-1);
121
+ }
122
+ if (!tracksInRange.every(hasNumericValue)) return measurementRequired(trackType);
123
+ let size = 0;
124
+ for(let i = startIndex - 1; i < endIndex - 1; i++)size += numericValue(tracks[i]);
125
+ const halfSize = size / 2;
126
+ size = 0;
127
+ for(let i = startIndex - 1; i < endIndex - 1; i++){
128
+ size += numericValue(tracks[i]);
129
+ if (Math.abs(halfSize - size) < 1) return ok(i + 2);
130
+ }
131
+ return ok(-1);
132
+ };
133
+ const bisectTracks = (tracks, fromTrackLine, toTrackLine, trackType)=>{
134
+ const rangeStart = fromTrackLine - 1;
135
+ const rangeEnd = toTrackLine - 1;
136
+ const tracksInRange = tracks.slice(rangeStart, rangeEnd);
137
+ const sources = identitySources(tracks.length);
138
+ if (tracksInRange.every(isFraction)) {
139
+ const halfSize = tracksInRange.reduce((sum, track)=>sum + parseFloat(track.size), 0) / 2;
140
+ let size = 0;
141
+ for(let i = rangeStart; i < rangeEnd; i++){
142
+ const trackSize = parseFloat(tracks[i].size);
143
+ if (size + trackSize > halfSize) {
144
+ const firstSize = halfSize - size;
145
+ const secondSize = trackSize - firstSize;
146
+ return ok({
147
+ newTrackIndex: i,
148
+ transition: transition([
149
+ ...tracks.slice(0, i),
150
+ {
151
+ measured: -1,
152
+ size: fractionSize(firstSize)
153
+ },
154
+ {
155
+ measured: -1,
156
+ size: fractionSize(secondSize)
157
+ },
158
+ ...tracks.slice(i + 1)
159
+ ], [
160
+ ...sources.slice(0, i),
161
+ -1,
162
+ -1,
163
+ ...sources.slice(i + 1)
164
+ ])
165
+ });
166
+ }
167
+ size += trackSize;
168
+ }
169
+ return failure("GEOMETRY_ERROR", "[GridTracks] fractional tracks could not be bisected", trackType);
170
+ }
171
+ if (!tracksInRange.every(hasNumericValue)) return measurementRequired(trackType);
172
+ let newTrackIndex = 0;
173
+ const newTracks = [];
174
+ const newSources = [];
175
+ let size = 0;
176
+ for(let i = fromTrackLine - 1; i < toTrackLine - 1; i++)size += numericValue(tracks[i]);
177
+ let halfTrack = Math.floor(size / 2);
178
+ for(let i = 0; i < tracks.length; i++)if (i < fromTrackLine - 1) {
179
+ newTracks.push(tracks[i]);
180
+ newSources.push(i);
181
+ } else if (i < toTrackLine - 1) {
182
+ const trackValue = numericValue(tracks[i]);
183
+ if (trackValue < halfTrack) {
184
+ newTracks.push(tracks[i]);
185
+ newSources.push(i);
186
+ halfTrack -= trackValue;
187
+ } else if (halfTrack) {
188
+ newTrackIndex = newTracks.length;
189
+ newTracks.push({
190
+ measured: -1,
191
+ size: pixelSize(halfTrack)
192
+ });
193
+ newSources.push(-1);
194
+ newTracks.push({
195
+ measured: -1,
196
+ size: pixelSize(trackValue - halfTrack)
197
+ });
198
+ newSources.push(-1);
199
+ halfTrack = 0;
200
+ } else {
201
+ newTracks.push(tracks[i]);
202
+ newSources.push(i);
203
+ }
204
+ } else {
205
+ newTracks.push(tracks[i]);
206
+ newSources.push(i);
207
+ }
208
+ return ok({
209
+ newTrackIndex,
210
+ transition: transition(newTracks, newSources)
211
+ });
212
+ };
213
+ const insertTrack = (tracks, { index, position }, size, trackType)=>{
214
+ const reducedIndex = "before" === position ? index - 1 : index;
215
+ const reducedTrack = tracks[reducedIndex];
216
+ if (void 0 === reducedTrack) return ok(void 0);
217
+ const reduced = incrementTrack(reducedTrack, -size, trackType);
218
+ if (!reduced.ok) return reduced;
219
+ const next = [
220
+ ...tracks
221
+ ];
222
+ next[reducedIndex] = reduced.value;
223
+ const sources = identitySources(tracks.length);
224
+ return ok(transition([
225
+ ...next.slice(0, index),
226
+ {
227
+ measured: -1,
228
+ size: pixelSize(Math.abs(size))
229
+ },
230
+ ...next.slice(index)
231
+ ], [
232
+ ...sources.slice(0, index),
233
+ -1,
234
+ ...sources.slice(index)
235
+ ]));
236
+ };
237
+ const removeTrack = (tracks, index, assignDirection = "fwd", trackType = "column")=>{
238
+ const assignFwd = 0 === index || "fwd" === assignDirection;
239
+ const contraIndex = assignFwd ? index + 1 : index - 1;
240
+ const contraTrack = tracks[contraIndex];
241
+ const removedTrack = tracks[index];
242
+ if (void 0 === contraTrack || void 0 === removedTrack) return failure("TRACK_NOT_FOUND", `[GridGeometry] no ${trackType} track to absorb removal of track #${index}`, trackType);
243
+ const otherTracksAreFractions = tracks.some((track, i)=>i !== index && i !== contraIndex && isFraction(track));
244
+ let nextContraTrack = contraTrack;
245
+ if (isFraction(contraTrack) && isFraction(removedTrack)) {
246
+ if (otherTracksAreFractions) nextContraTrack = {
247
+ measured: contraTrack.measured,
248
+ size: fractionSize(parseFloat(contraTrack.size) + parseFloat(removedTrack.size))
249
+ };
250
+ } else if (isFraction(contraTrack)) {
251
+ if (otherTracksAreFractions) {
252
+ if (!isMeasured(contraTrack) || !hasNumericValue(removedTrack)) return measurementRequired(trackType);
253
+ const incremented = incrementTrack(contraTrack, numericValue(removedTrack), trackType);
254
+ if (!incremented.ok) return incremented;
255
+ nextContraTrack = incremented.value;
256
+ }
257
+ } else {
258
+ if (!hasNumericValue(removedTrack)) return measurementRequired(trackType);
259
+ const incremented = incrementTrack(contraTrack, numericValue(removedTrack), trackType);
260
+ if (!incremented.ok) return incremented;
261
+ nextContraTrack = incremented.value;
262
+ }
263
+ const next = [
264
+ ...tracks
265
+ ];
266
+ next[contraIndex] = nextContraTrack;
267
+ const sources = identitySources(tracks.length);
268
+ next.splice(index, 1);
269
+ sources.splice(index, 1);
270
+ return ok(transition(next, sources));
271
+ };
272
+ const resizeTrackTo = (tracks, trackIndex, size, trackType = "column")=>{
273
+ if (void 0 === tracks[trackIndex]) return failure("TRACK_NOT_FOUND", `[GridGeometry] no ${trackType} track #${trackIndex}`, trackType);
274
+ const next = [
275
+ ...tracks
276
+ ];
277
+ next[trackIndex] = sizedTrack(size);
278
+ return ok(transition(next, identitySources(tracks.length)));
279
+ };
280
+ const resizeTracksAdjacent = (tracks, { contraTrackIndex, delta, resizedTrackIndex }, trackType)=>{
281
+ const resizeTrack = tracks[resizedTrackIndex];
282
+ const contraTrack = tracks[contraTrackIndex];
283
+ if (void 0 === resizeTrack || void 0 === contraTrack) return failure("TRACK_NOT_FOUND", "[GridTracks] resize, no track at index position", trackType);
284
+ const resized = incrementTrack(resizeTrack, delta, trackType);
285
+ if (!resized.ok) return resized;
286
+ const contra = incrementTrack(contraTrack, -delta, trackType);
287
+ if (!contra.ok) return contra;
288
+ const next = [
289
+ ...tracks
290
+ ];
291
+ next[resizedTrackIndex] = resized.value;
292
+ next[contraTrackIndex] = contra.value;
293
+ return ok(transition(next, identitySources(tracks.length)));
294
+ };
295
+ const getMinimumConstrainedSizes = (trackIndices, constraints)=>{
296
+ const prefixSizes = Array.from({
297
+ length: trackIndices.length + 1
298
+ }, ()=>0);
299
+ for(let end = 1; end <= trackIndices.length; end += 1){
300
+ prefixSizes[end] = prefixSizes[end - 1];
301
+ for (const constraint of constraints){
302
+ const localIndices = constraint.trackIndices.map((trackIndex)=>trackIndices.indexOf(trackIndex)).filter((index)=>-1 !== index).sort((a, b)=>a - b);
303
+ if (localIndices.at(-1) === end - 1) prefixSizes[end] = Math.max(prefixSizes[end], prefixSizes[localIndices[0]] + constraint.minimum);
304
+ }
305
+ }
306
+ return trackIndices.map((_, index)=>prefixSizes[index + 1] - prefixSizes[index]);
307
+ };
308
+ const getConstrainedGroupReductions = (tracks, trackIndices, constraints, requestedReduction)=>{
309
+ const reductions = trackIndices.map(()=>0);
310
+ const initialSizes = trackIndices.map((trackIndex)=>numericValue(tracks[trackIndex]));
311
+ if (constraints.some((constraint)=>constraint.trackIndices.reduce((total, trackIndex)=>total + numericValue(tracks[trackIndex]), 0) < constraint.minimum - 0.001)) return reductions;
312
+ const minimumSizes = getMinimumConstrainedSizes(trackIndices, constraints);
313
+ const maximumReduction = Math.max(0, initialSizes.reduce((total, size)=>total + size, 0) - minimumSizes.reduce((total, size)=>total + size, 0));
314
+ const allConstraints = constraints.concat(trackIndices.map((trackIndex)=>({
315
+ minimum: 0,
316
+ trackIndices: [
317
+ trackIndex
318
+ ]
319
+ })));
320
+ let activeTrackIndices = [
321
+ ...trackIndices
322
+ ];
323
+ let remainingReduction = Math.min(requestedReduction, maximumReduction);
324
+ while(remainingReduction > 0.001 && activeTrackIndices.length > 0){
325
+ const activeSize = activeTrackIndices.reduce((total, trackIndex)=>total + numericValue(tracks[trackIndex]) - reductions[trackIndices.indexOf(trackIndex)], 0);
326
+ const proposedReductions = activeTrackIndices.map((trackIndex)=>{
327
+ const currentSize = numericValue(tracks[trackIndex]) - reductions[trackIndices.indexOf(trackIndex)];
328
+ return currentSize / activeSize * remainingReduction;
329
+ });
330
+ let appliedRatio = 1;
331
+ for (const constraint of allConstraints){
332
+ const constrainedActiveIndices = activeTrackIndices.filter((trackIndex)=>constraint.trackIndices.includes(trackIndex));
333
+ const proposedReduction = constrainedActiveIndices.reduce((total, trackIndex)=>total + proposedReductions[activeTrackIndices.indexOf(trackIndex)], 0);
334
+ if (0 === proposedReduction) continue;
335
+ const currentSize = constraint.trackIndices.reduce((total, trackIndex)=>total + numericValue(tracks[trackIndex]) - reductions[trackIndices.indexOf(trackIndex)], 0);
336
+ appliedRatio = Math.min(appliedRatio, Math.max(0, (currentSize - constraint.minimum) / proposedReduction));
337
+ }
338
+ let appliedReduction = 0;
339
+ activeTrackIndices.forEach((trackIndex, index)=>{
340
+ const reduction = proposedReductions[index] * appliedRatio;
341
+ reductions[trackIndices.indexOf(trackIndex)] += reduction;
342
+ appliedReduction += reduction;
343
+ });
344
+ remainingReduction -= appliedReduction;
345
+ if (1 === appliedRatio) break;
346
+ activeTrackIndices = activeTrackIndices.filter((trackIndex)=>allConstraints.every((constraint)=>{
347
+ if (!constraint.trackIndices.includes(trackIndex)) return true;
348
+ const currentSize = constraint.trackIndices.reduce((total, constrainedTrackIndex)=>total + numericValue(tracks[constrainedTrackIndex]) - reductions[trackIndices.indexOf(constrainedTrackIndex)], 0);
349
+ return currentSize - constraint.minimum > 0.001;
350
+ }));
351
+ }
352
+ if (remainingReduction > 0.001) {
353
+ const currentSizes = initialSizes.map((size, index)=>size - reductions[index]);
354
+ const availableReduction = currentSizes.reduce((total, size)=>total + size, 0) - minimumSizes.reduce((total, size)=>total + size, 0);
355
+ const interpolation = availableReduction > 0 ? Math.min(1, remainingReduction / availableReduction) : 0;
356
+ currentSizes.forEach((size, index)=>{
357
+ const finalSize = size + (minimumSizes[index] - size) * interpolation;
358
+ reductions[index] = initialSizes[index] - finalSize;
359
+ });
360
+ }
361
+ return reductions;
362
+ };
363
+ const getProportionalResizeAllowance = (tracks, trackIndices, constraints, trackType)=>{
364
+ if (trackIndices.some((index)=>!hasNumericValue(tracks[index]))) return measurementRequired(trackType);
365
+ const requestedReduction = trackIndices.reduce((total, index)=>total + numericValue(tracks[index]), 0);
366
+ return ok(getConstrainedGroupReductions(tracks, trackIndices, constraints, requestedReduction).reduce((total, reduction)=>total + reduction, 0));
367
+ };
368
+ const resizeTracksProportionally = (tracks, { afterConstraints = [], afterTrackIndices, beforeConstraints = [], beforeTrackIndices, delta, initialSizes }, trackType)=>{
369
+ const resizedIndices = [
370
+ ...beforeTrackIndices,
371
+ ...afterTrackIndices
372
+ ];
373
+ if (resizedIndices.some((index)=>void 0 === tracks[index])) return failure("TRACK_NOT_FOUND", "[GridTracks] proportional resize track not found", trackType);
374
+ const next = [
375
+ ...tracks
376
+ ];
377
+ if (initialSizes) resizedIndices.forEach((trackIndex)=>{
378
+ next[trackIndex] = sizedTrack(pixelSize(initialSizes[trackIndex]));
379
+ });
380
+ if (resizedIndices.some((index)=>!hasNumericValue(next[index]))) return measurementRequired(trackType);
381
+ const resizeGroup = (indices, constraints, totalChange)=>{
382
+ const groupTracks = indices.map((index)=>next[index]);
383
+ if (totalChange >= 0) {
384
+ const groupSize = groupTracks.reduce((total, track)=>total + numericValue(track), 0);
385
+ let appliedChange = 0;
386
+ for (const [index, trackIndex] of indices.entries()){
387
+ const change = index === indices.length - 1 ? totalChange - appliedChange : 0 === groupSize ? totalChange / indices.length : totalChange * (numericValue(next[trackIndex]) / groupSize);
388
+ const incremented = incrementTrack(next[trackIndex], change, trackType);
389
+ if (!incremented.ok) return incremented.error;
390
+ next[trackIndex] = incremented.value;
391
+ appliedChange += change;
392
+ }
393
+ return;
394
+ }
395
+ const reductions = getConstrainedGroupReductions(next, indices, constraints, -totalChange);
396
+ for (const [index, trackIndex] of indices.entries()){
397
+ const incremented = incrementTrack(next[trackIndex], -reductions[index], trackType);
398
+ if (!incremented.ok) return incremented.error;
399
+ next[trackIndex] = incremented.value;
400
+ }
401
+ };
402
+ const beforeError = resizeGroup(beforeTrackIndices, beforeConstraints, -delta);
403
+ if (beforeError) return {
404
+ error: beforeError,
405
+ ok: false
406
+ };
407
+ const afterError = resizeGroup(afterTrackIndices, afterConstraints, delta);
408
+ if (afterError) return {
409
+ error: afterError,
410
+ ok: false
411
+ };
412
+ return ok(transition(next, identitySources(tracks.length)));
413
+ };
414
+ const span = (start, end)=>({
415
+ end,
416
+ start
417
+ });
418
+ const cloneItem = (item, { column, row })=>({
419
+ ...item,
420
+ column: column ? span(column.start, column.end) : item.column,
421
+ row: row ? span(row.start, row.end) : item.row
422
+ });
423
+ const findGeometryItem = (geometry, itemId)=>geometry.items.find(({ id })=>id === itemId);
424
+ const requireItem = (geometry, itemId)=>{
425
+ const item = findGeometryItem(geometry, itemId);
426
+ return item ? ok(item) : failure("ITEM_NOT_FOUND", `[GridModel] GridItem #${itemId} not found`);
427
+ };
428
+ const applyGeometryUpdates = (geometry, updates)=>{
429
+ if (0 === updates.length) return geometry;
430
+ const updatesById = new Map();
431
+ const addUpdate = (update)=>{
432
+ const existing = updatesById.get(update.id);
433
+ updatesById.set(update.id, existing ? {
434
+ ...existing,
435
+ ...update
436
+ } : update);
437
+ };
438
+ for (const update of updates){
439
+ addUpdate(update);
440
+ const item = findGeometryItem(geometry, update.id);
441
+ if (item?.type === "stacked-content") for (const { id } of stackedChildItems(geometry, update.id))addUpdate({
442
+ ...update,
443
+ id
444
+ });
445
+ }
446
+ return {
447
+ ...geometry,
448
+ items: geometry.items.map((item)=>{
449
+ const update = updatesById.get(item.id);
450
+ return update ? cloneItem(item, update) : item;
451
+ })
452
+ };
453
+ };
454
+ const applyGeometryAxisUpdates = (geometry, updates, axis)=>applyGeometryUpdates(geometry, updates.flatMap((update)=>update[axis] ? [
455
+ {
456
+ [axis]: update[axis],
457
+ id: update.id
458
+ }
459
+ ] : []));
460
+ const withTracks = (geometry, trackType, tracks)=>"column" === trackType ? {
461
+ ...geometry,
462
+ columns: trackSizes(tracks)
463
+ } : {
464
+ ...geometry,
465
+ rows: trackSizes(tracks)
466
+ };
467
+ const isFixedHeightItem = ({ resizeable })=>false === resizeable || void 0 === resizeable || "h" === resizeable;
468
+ const isFixedWidthItem = ({ resizeable })=>false === resizeable || void 0 === resizeable || "v" === resizeable;
469
+ const canSplitGridItem = (item, splitDirection)=>"east" === splitDirection || "west" === splitDirection ? !isFixedWidthItem(item) : !isFixedHeightItem(item);
470
+ const gridResizeDirectionForSplit = (splitDirection)=>"north" === splitDirection || "south" === splitDirection ? "vertical" : "horizontal";
471
+ const byColumnStart = (item1, item2)=>item1.column.start - item2.column.start;
472
+ const byRowStart = (item1, item2)=>item1.row.start - item2.row.start;
473
+ const positionComparator = (p1, p2)=>{
474
+ if (p1.start < p2.start) return -1;
475
+ if (p1.start > p2.start) return 1;
476
+ if (p1.end < p2.end) return -1;
477
+ if (p1.end > p2.end) return 1;
478
+ return 0;
479
+ };
480
+ const byColumnPosition = ({ column: pos1 }, { column: pos2 })=>positionComparator(pos1, pos2);
481
+ const byRowPosition = ({ row: pos1 }, { row: pos2 })=>positionComparator(pos1, pos2);
482
+ const itemsFillColumn = (items, pos)=>{
483
+ const sortedItems = [
484
+ ...items
485
+ ].sort(byColumnPosition);
486
+ const firstItem = sortedItems.at(0);
487
+ const lastItem = sortedItems.at(-1);
488
+ if (firstItem && lastItem) {
489
+ const { start } = firstItem.column;
490
+ const { end } = lastItem.column;
491
+ if (start === pos.start && end === pos.end) {
492
+ for(let i = 1; i < sortedItems.length; i++)if (sortedItems[i - 1].column.end !== sortedItems[i].column.start) return false;
493
+ return true;
494
+ }
495
+ }
496
+ return false;
497
+ };
498
+ const itemsFillRow = (items, pos)=>{
499
+ const sortedItems = [
500
+ ...items
501
+ ].sort(byRowPosition);
502
+ const firstItem = sortedItems.at(0);
503
+ const lastItem = sortedItems.at(-1);
504
+ if (firstItem && lastItem) {
505
+ const { start } = firstItem.row;
506
+ const { end } = lastItem.row;
507
+ if (start === pos.start && end === pos.end) {
508
+ for(let i = 1; i < sortedItems.length; i++)if (sortedItems[i - 1].row.end !== sortedItems[i].row.start) return false;
509
+ return true;
510
+ }
511
+ }
512
+ return false;
513
+ };
514
+ const findByColumnStart = (items, col)=>{
515
+ const matches = items.filter(({ column: { start } })=>start === col);
516
+ return 0 === matches.length ? void 0 : matches;
517
+ };
518
+ const findByColumnEnd = (items, col)=>{
519
+ const matches = items.filter(({ column: { end } })=>end === col);
520
+ return 0 === matches.length ? void 0 : matches;
521
+ };
522
+ const findByRowStart = (items, row)=>{
523
+ const matches = items.filter(({ row: { start } })=>start === row);
524
+ return 0 === matches.length ? void 0 : matches;
525
+ };
526
+ const findByRowEnd = (items, row)=>{
527
+ const matches = items.filter(({ row: { end } })=>end === row);
528
+ return 0 === matches.length ? void 0 : matches;
529
+ };
530
+ const stackedChildItems = (geometry, stackId)=>geometry.items.filter((item)=>item.stackId === stackId);
531
+ const splitGridChildPosition = ({ column, row }, splitDirection, splitTrackIndex)=>{
532
+ const { start: colStart, end: colEnd } = column;
533
+ const { start: rowStart, end: rowEnd } = row;
534
+ switch(splitDirection){
535
+ case "north":
536
+ return [
537
+ {
538
+ column: span(colStart, colEnd),
539
+ row: span(rowStart, splitTrackIndex)
540
+ },
541
+ {
542
+ column: span(colStart, colEnd),
543
+ row: span(splitTrackIndex, rowEnd)
544
+ }
545
+ ];
546
+ case "east":
547
+ return [
548
+ {
549
+ column: span(splitTrackIndex, colEnd),
550
+ row: span(rowStart, rowEnd)
551
+ },
552
+ {
553
+ column: span(colStart, splitTrackIndex),
554
+ row: span(rowStart, rowEnd)
555
+ }
556
+ ];
557
+ case "south":
558
+ return [
559
+ {
560
+ column: span(colStart, colEnd),
561
+ row: span(splitTrackIndex, rowEnd)
562
+ },
563
+ {
564
+ column: span(colStart, colEnd),
565
+ row: span(rowStart, splitTrackIndex)
566
+ }
567
+ ];
568
+ case "west":
569
+ return [
570
+ {
571
+ column: span(colStart, splitTrackIndex),
572
+ row: span(rowStart, rowEnd)
573
+ },
574
+ {
575
+ column: span(splitTrackIndex, colEnd),
576
+ row: span(rowStart, rowEnd)
577
+ }
578
+ ];
579
+ }
580
+ };
581
+ const trackTransitionFor = (trackType, trackTransition)=>void 0 === trackTransition ? {} : "column" === trackType ? {
582
+ columns: trackTransition
583
+ } : {
584
+ rows: trackTransition
585
+ };
586
+ const shiftItemsForNewTrack = (items, trackType, trackIndex)=>{
587
+ const gridPosition = trackIndex + 1;
588
+ const updates = [];
589
+ for (const item of items){
590
+ const { start, end } = item[trackType];
591
+ if (start > gridPosition) updates.push({
592
+ [trackType]: span(start + 1, end + 1),
593
+ id: item.id
594
+ });
595
+ else if (end > gridPosition) updates.push({
596
+ [trackType]: span(start, end + 1),
597
+ id: item.id
598
+ });
599
+ }
600
+ return updates;
601
+ };
602
+ const splitTargetUpdates = (geometry, droppedItemId, targetItemId, splitDirection, splitIndex, order)=>{
603
+ const targetItem = findGeometryItem(geometry, targetItemId);
604
+ if (void 0 === targetItem) return [];
605
+ const [droppedItemPosition, targetItemPosition] = splitGridChildPosition(targetItem, splitDirection, splitIndex);
606
+ const droppedUpdate = {
607
+ ...droppedItemPosition,
608
+ id: droppedItemId
609
+ };
610
+ const targetUpdate = {
611
+ ...targetItemPosition,
612
+ id: targetItemId
613
+ };
614
+ const updates = "target-first" === order ? [
615
+ targetUpdate,
616
+ droppedUpdate
617
+ ] : [
618
+ droppedUpdate,
619
+ targetUpdate
620
+ ];
621
+ if ("stacked-content" === targetItem.type) updates.push(...stackedChildItems(geometry, targetItemId).map(({ id })=>({
622
+ ...targetItemPosition,
623
+ id
624
+ })));
625
+ return updates;
626
+ };
627
+ const splitGridItem = (geometry, { droppedItemId, resizeDirection, splitDirection, targetItemId }, measurements = NO_MEASUREMENTS)=>{
628
+ const target = requireItem(geometry, targetItemId);
629
+ if (!target.ok) return target;
630
+ const droppedItem = requireItem(geometry, droppedItemId);
631
+ if (!droppedItem.ok) return droppedItem;
632
+ const targetGridItem = target.value;
633
+ if (!canSplitGridItem(targetGridItem, splitDirection)) return failure("NON_RESIZABLE", `Grid item #${targetItemId} cannot be split ${splitDirection}`);
634
+ const trackType = (resizeDirection ?? gridResizeDirectionForSplit(splitDirection)) === "vertical" ? "row" : "column";
635
+ const tracks = readTracks(geometryTracks(geometry, trackType), measurements[trackType]);
636
+ const resizeTrack = targetGridItem[trackType];
637
+ const splitAtNewTrack = (trackTransition, newTrackIndex, splitIndexFor)=>{
638
+ const silentUpdates = shiftItemsForNewTrack(geometry.items, trackType, newTrackIndex);
639
+ const accommodated = withTracks(applyGeometryUpdates(geometry, silentUpdates), trackType, trackTransition.tracks);
640
+ const updatedTarget = findGeometryItem(accommodated, targetItemId);
641
+ if (void 0 === updatedTarget) return failure("ITEM_NOT_FOUND", `[GridModel] GridItem #${targetItemId} not found`);
642
+ const updates = splitTargetUpdates(accommodated, droppedItemId, targetItemId, splitDirection, splitIndexFor(updatedTarget), "target-first");
643
+ return ok({
644
+ ...trackTransitionFor(trackType, trackTransition),
645
+ geometry: applyGeometryUpdates(accommodated, updates),
646
+ silentUpdates,
647
+ updates
648
+ });
649
+ };
650
+ if (resizeTrack.end - resizeTrack.start === 1) {
651
+ const newTrackIndex = resizeTrack.start - 1;
652
+ const trackTransition = splitTrack(tracks, newTrackIndex, trackType);
653
+ if (!trackTransition.ok) return trackTransition;
654
+ return splitAtNewTrack(trackTransition.value, newTrackIndex, (item)=>"row" === trackType ? item.row.end - 1 : item.column.end - 1);
655
+ }
656
+ const bisectingGridTrack = findBisectingTrack(tracks, resizeTrack.start, resizeTrack.end, trackType);
657
+ if (!bisectingGridTrack.ok) return bisectingGridTrack;
658
+ if (-1 !== bisectingGridTrack.value) {
659
+ const updates = splitTargetUpdates(geometry, droppedItemId, targetItemId, splitDirection, bisectingGridTrack.value, "dropped-first");
660
+ return ok({
661
+ geometry: applyGeometryUpdates(geometry, updates),
662
+ silentUpdates: [],
663
+ updates
664
+ });
665
+ }
666
+ const bisection = bisectTracks(tracks, resizeTrack.start, resizeTrack.end, trackType);
667
+ if (!bisection.ok) return bisection;
668
+ return splitAtNewTrack(bisection.value.transition, bisection.value.newTrackIndex, ()=>bisection.value.newTrackIndex + 2);
669
+ };
670
+ const replaceGridItem = (geometry, { droppedItemId, targetItemId })=>{
671
+ const dropped = requireItem(geometry, droppedItemId);
672
+ if (!dropped.ok) return dropped;
673
+ const target = requireItem(geometry, targetItemId);
674
+ if (!target.ok) return target;
675
+ const { column, row } = target.value;
676
+ const removals = [
677
+ {
678
+ id: targetItemId,
679
+ reason: "close"
680
+ }
681
+ ];
682
+ const silentUpdates = [
683
+ {
684
+ column,
685
+ id: droppedItemId,
686
+ row
687
+ }
688
+ ];
689
+ if ("stacked-content" === dropped.value.type) silentUpdates.push(...stackedChildItems(geometry, droppedItemId).map(({ id })=>({
690
+ column,
691
+ id,
692
+ row
693
+ })));
694
+ const next = applyGeometryUpdates({
695
+ ...geometry,
696
+ items: geometry.items.filter(({ id })=>id !== targetItemId)
697
+ }, silentUpdates);
698
+ return ok({
699
+ geometry: next,
700
+ removals,
701
+ silentUpdates,
702
+ updates: []
703
+ });
704
+ };
705
+ const findUnusedGridLines = (geometry)=>{
706
+ const colCount = geometry.columns.length;
707
+ const rowCount = geometry.rows.length;
708
+ const unusedStartPositions = [];
709
+ const unusedColLines = [];
710
+ const unusedRowLines = [];
711
+ const items = geometry.items.filter((item)=>!item.dragging);
712
+ for(let i = 1; i <= colCount; i++)if (!findByColumnStart(items, i)) unusedStartPositions.push(i);
713
+ for(let i = 2; i <= colCount + 1; i++)if (!findByColumnEnd(items, i) && unusedStartPositions.includes(i)) unusedColLines.push(i);
714
+ unusedStartPositions.length = 0;
715
+ for(let i = 1; i <= rowCount; i++)if (!findByRowStart(items, i)) unusedStartPositions.push(i);
716
+ for(let i = 2; i <= rowCount + 1; i++)if (!findByRowEnd(items, i) && unusedStartPositions.includes(i)) unusedRowLines.push(i);
717
+ return [
718
+ unusedColLines,
719
+ unusedRowLines
720
+ ];
721
+ };
722
+ const shiftItemsForInsertedTrack = (items, trackType, index, position)=>{
723
+ const gridPosition = index + 1;
724
+ const updates = [];
725
+ for (const item of items){
726
+ const { start, end } = item[trackType];
727
+ if ("before" === position) {
728
+ if (start >= gridPosition) updates.push({
729
+ [trackType]: span(start + 1, end + 1),
730
+ id: item.id
731
+ });
732
+ else if (end >= gridPosition) updates.push({
733
+ [trackType]: span(start, end + 1),
734
+ id: item.id
735
+ });
736
+ } else if (start > gridPosition) updates.push({
737
+ [trackType]: span(start + 1, end + 1),
738
+ id: item.id
739
+ });
740
+ else if (end > gridPosition) updates.push({
741
+ [trackType]: span(start, end + 1),
742
+ id: item.id
743
+ });
744
+ }
745
+ return updates;
746
+ };
747
+ const shiftItemsForRemovedTrack = (items, trackType, trackIndex)=>{
748
+ const gridPosition = trackIndex + 1;
749
+ const updates = [];
750
+ for (const item of items){
751
+ const { start, end } = item[trackType];
752
+ const nextStart = start > gridPosition ? start - 1 : start;
753
+ const nextEnd = end > gridPosition ? end - 1 : end;
754
+ if (nextStart !== start || nextEnd !== end) updates.push({
755
+ [trackType]: span(nextStart, nextEnd),
756
+ id: item.id
757
+ });
758
+ }
759
+ return updates;
760
+ };
761
+ const mergeUpdates = (target, additions)=>{
762
+ for (const update of additions){
763
+ const index = target.findIndex(({ id })=>id === update.id);
764
+ if (-1 === index) target.push(update);
765
+ else target[index] = {
766
+ ...target[index],
767
+ ...update
768
+ };
769
+ }
770
+ };
771
+ const updateContrasToOccupySpace = (geometry, { column, id, row })=>{
772
+ const { items } = geometry;
773
+ const adjacentItemsWithSameRowStart = findByRowStart(items, row.start)?.filter(({ column: { start, end }, stackId })=>void 0 === stackId && (end === column.start || start === column.end));
774
+ if (adjacentItemsWithSameRowStart) {
775
+ const itemsInSameRow = adjacentItemsWithSameRowStart.filter((item)=>item.row.end === row.end && item.id !== id);
776
+ if (1 === itemsInSameRow.length) {
777
+ const [itemInSameRow] = itemsInSameRow;
778
+ const { id: contraId, column: { start, end } } = itemInSameRow;
779
+ if (end === column.start || start === column.end) {
780
+ const nextColumn = end === column.start ? span(start, column.end) : span(column.start, end);
781
+ const updates = [
782
+ {
783
+ column: nextColumn,
784
+ id: contraId
785
+ }
786
+ ];
787
+ if ("stacked-content" === itemInSameRow.type) updates.push(...stackedChildItems(geometry, itemInSameRow.id).map(({ id: stackedId })=>({
788
+ column: nextColumn,
789
+ id: stackedId
790
+ })));
791
+ return [
792
+ updates,
793
+ []
794
+ ];
795
+ }
796
+ } else if (2 === itemsInSameRow.length) {
797
+ const adjacentBefore = itemsInSameRow.filter((item)=>item.column.end === column.start);
798
+ if (1 === adjacentBefore.length) {
799
+ const { id: contraId, column: { start } } = adjacentBefore[0];
800
+ return [
801
+ [
802
+ {
803
+ column: span(start, column.end),
804
+ id: contraId
805
+ }
806
+ ],
807
+ []
808
+ ];
809
+ }
810
+ const adjacentAfter = itemsInSameRow.filter((item)=>item.column.start === column.end);
811
+ if (1 === adjacentAfter.length) {
812
+ const { id: contraId, column: { end } } = adjacentAfter[0];
813
+ return [
814
+ [
815
+ {
816
+ column: span(column.start, end),
817
+ id: contraId
818
+ }
819
+ ],
820
+ []
821
+ ];
822
+ }
823
+ } else if (row.end - row.start > 1) {
824
+ const itemsEndingWhereTargetStarts = findByColumnEnd(items, column.start)?.filter((item)=>void 0 === item.stackId && item.row.start >= row.start && item.row.end <= row.end);
825
+ if (itemsEndingWhereTargetStarts && itemsFillRow(itemsEndingWhereTargetStarts, row)) return [
826
+ itemsEndingWhereTargetStarts.map(({ id: contraId, column: { start } })=>({
827
+ column: span(start, column.end),
828
+ id: contraId
829
+ })),
830
+ []
831
+ ];
832
+ const itemsStartingWhereTargetEnds = findByColumnStart(items, column.end)?.filter((item)=>void 0 === item.stackId && item.row.start >= row.start && item.row.end <= row.end);
833
+ if (itemsStartingWhereTargetEnds && itemsFillRow(itemsStartingWhereTargetEnds, row)) return [
834
+ itemsStartingWhereTargetEnds.map(({ id: contraId, column: { end } })=>({
835
+ column: span(column.start, end),
836
+ id: contraId
837
+ })),
838
+ []
839
+ ];
840
+ }
841
+ }
842
+ const adjacentItemsWithSameColumnStart = findByColumnStart(items, column.start)?.filter(({ row: { start, end }, stackId })=>void 0 === stackId && (end === row.start || start === row.end));
843
+ if (adjacentItemsWithSameColumnStart) {
844
+ const itemsInSameColumn = adjacentItemsWithSameColumnStart.filter((item)=>item.column.end === column.end && item.id !== id);
845
+ if (1 === itemsInSameColumn.length) {
846
+ const [itemInSameColumn] = itemsInSameColumn;
847
+ const { id: contraId, row: { start, end } } = itemInSameColumn;
848
+ if (end === row.start || start === row.end) {
849
+ const nextRow = end === row.start ? span(start, row.end) : span(row.start, end);
850
+ const updates = [
851
+ {
852
+ id: contraId,
853
+ row: nextRow
854
+ }
855
+ ];
856
+ if ("stacked-content" === itemInSameColumn.type) updates.push(...stackedChildItems(geometry, itemInSameColumn.id).map(({ id: stackedId })=>({
857
+ id: stackedId,
858
+ row: nextRow
859
+ })));
860
+ return [
861
+ [],
862
+ updates
863
+ ];
864
+ }
865
+ } else if (2 === itemsInSameColumn.length) {
866
+ const adjacentBefore = itemsInSameColumn.filter((item)=>item.row.end === row.start);
867
+ if (1 === adjacentBefore.length) {
868
+ const { id: contraId, row: { start } } = adjacentBefore[0];
869
+ return [
870
+ [
871
+ {
872
+ id: contraId,
873
+ row: span(start, row.end)
874
+ }
875
+ ],
876
+ []
877
+ ];
878
+ }
879
+ const adjacentAfter = itemsInSameColumn.filter((item)=>item.row.start === row.end);
880
+ if (1 === adjacentAfter.length) {
881
+ const { id: contraId, row: { end } } = adjacentAfter[0];
882
+ return [
883
+ [
884
+ {
885
+ id: contraId,
886
+ row: span(row.start, end)
887
+ }
888
+ ],
889
+ []
890
+ ];
891
+ }
892
+ } else if (column.end - column.start > 1) {
893
+ const itemsEndingWhereTargetStarts = findByRowEnd(items, row.start)?.filter((item)=>item.column.start >= column.start && item.column.end <= column.end);
894
+ if (itemsEndingWhereTargetStarts && itemsFillColumn(itemsEndingWhereTargetStarts, column)) return [
895
+ [],
896
+ itemsEndingWhereTargetStarts.map(({ id: contraId, row: { start } })=>({
897
+ id: contraId,
898
+ row: span(start, row.end)
899
+ }))
900
+ ];
901
+ const itemsStartingWhereTargetEnds = findByRowStart(items, row.end)?.filter((item)=>item.column.start >= column.start && item.column.end <= column.end);
902
+ if (itemsStartingWhereTargetEnds && itemsFillColumn(itemsStartingWhereTargetEnds, column)) return [
903
+ [],
904
+ itemsStartingWhereTargetEnds.map(({ id: contraId, row: { end } })=>({
905
+ id: contraId,
906
+ row: span(row.start, end)
907
+ }))
908
+ ];
909
+ }
910
+ }
911
+ return [
912
+ [],
913
+ []
914
+ ];
915
+ };
916
+ const removeGridItem = (geometry, { itemId, reason }, { createPlaceholderId, measurements = NO_MEASUREMENTS })=>{
917
+ const target = requireItem(geometry, itemId);
918
+ if (!target.ok) return target;
919
+ const gridItem = target.value;
920
+ const removals = [
921
+ {
922
+ id: itemId,
923
+ reason
924
+ }
925
+ ];
926
+ let current = "drag" === reason ? {
927
+ ...geometry,
928
+ items: geometry.items.map((item)=>item.id === itemId ? {
929
+ ...item,
930
+ dragging: true
931
+ } : item)
932
+ } : {
933
+ ...geometry,
934
+ items: geometry.items.filter(({ id })=>id !== itemId)
935
+ };
936
+ if (gridItem.stackId) return ok({
937
+ geometry: current,
938
+ notify: false,
939
+ removals,
940
+ silentUpdates: [],
941
+ stackMember: true,
942
+ updates: []
943
+ });
944
+ const [colItemUpdates, rowItemUpdates] = updateContrasToOccupySpace(current, gridItem);
945
+ current = applyGeometryAxisUpdates(current, colItemUpdates, "column");
946
+ current = applyGeometryAxisUpdates(current, rowItemUpdates, "row");
947
+ const [unusedColLines, unusedRowLines] = findUnusedGridLines(current);
948
+ let columnTracks = readTracks(current.columns, measurements.column);
949
+ let rowTracks = readTracks(current.rows, measurements.row);
950
+ let columns;
951
+ let rows;
952
+ for (const line of [
953
+ ...unusedColLines
954
+ ].sort((a, b)=>b - a)){
955
+ const shifts = shiftItemsForRemovedTrack(current.items, "column", line - 1);
956
+ mergeUpdates(colItemUpdates, shifts);
957
+ current = applyGeometryUpdates(current, shifts);
958
+ const removed = removeTrack(columnTracks, line - 1, "bwd", "column");
959
+ if (!removed.ok) return removed;
960
+ columnTracks = removed.value.tracks;
961
+ columns = composeTransitions(columns, removed.value);
962
+ current = withTracks(current, "column", columnTracks);
963
+ }
964
+ for (const line of [
965
+ ...unusedRowLines
966
+ ].sort((a, b)=>b - a)){
967
+ const shifts = shiftItemsForRemovedTrack(current.items, "row", line - 1);
968
+ mergeUpdates(rowItemUpdates, shifts);
969
+ current = applyGeometryUpdates(current, shifts);
970
+ const removed = removeTrack(rowTracks, line - 1, "bwd", "row");
971
+ if (!removed.ok) return removed;
972
+ rowTracks = removed.value.tracks;
973
+ rows = composeTransitions(rows, removed.value);
974
+ current = withTracks(current, "row", rowTracks);
975
+ }
976
+ const notify = colItemUpdates.length > 0 || rowItemUpdates.length > 0 || unusedColLines.length > 0 || unusedRowLines.length > 0;
977
+ const transitionResult = {
978
+ ...columns ? {
979
+ columns
980
+ } : {},
981
+ ...rows ? {
982
+ rows
983
+ } : {},
984
+ geometry: current,
985
+ notify,
986
+ removals,
987
+ silentUpdates: [],
988
+ stackMember: false,
989
+ updates: [
990
+ ...colItemUpdates,
991
+ ...rowItemUpdates
992
+ ]
993
+ };
994
+ if ("placeholder" === reason) return ok(transitionResult);
995
+ const placeholders = regeneratePlaceholders(current, createPlaceholderId);
996
+ return ok({
997
+ ...transitionResult,
998
+ geometry: placeholders.geometry,
999
+ placeholders: {
1000
+ added: placeholders.added,
1001
+ removedIds: placeholders.removedIds
1002
+ }
1003
+ });
1004
+ };
1005
+ const rowCellsAllEmpty = (fromIndex, toIndex, row)=>{
1006
+ if (!row) return false;
1007
+ for(let i = fromIndex; i < toIndex; i++)if (0 !== row[i]) return false;
1008
+ return true;
1009
+ };
1010
+ const getGridMatrix = (items, rowCount, colCount)=>{
1011
+ const grid = Array.from({
1012
+ length: rowCount
1013
+ }, ()=>new Array(colCount).fill(0));
1014
+ for (const { column: { start: colStart, end: colEnd }, row: { start: rowStart, end: rowEnd } } of items)for(let row = rowStart - 1; row < rowEnd - 1; row++)for(let col = colStart - 1; col < colEnd - 1; col++)if (grid[row]?.[col] !== void 0) grid[row][col] += 1;
1015
+ return grid;
1016
+ };
1017
+ const regeneratePlaceholders = (geometry, createPlaceholderId)=>{
1018
+ const removedIds = geometry.items.filter(({ type })=>"placeholder" === type).map(({ id })=>id);
1019
+ const items = geometry.items.filter(({ type })=>"placeholder" !== type);
1020
+ const grid = getGridMatrix(items.filter(({ dragging })=>!dragging), geometry.rows.length, geometry.columns.length);
1021
+ const added = [];
1022
+ for(let i = 0; i < grid.length; i++){
1023
+ const cols = grid[i];
1024
+ for(let j = 0; j < cols.length; j++)if (0 === cols[j]) {
1025
+ cols[j] = 1;
1026
+ let nextRow = i + 1;
1027
+ let nextCol = j + 1;
1028
+ while(0 === cols[nextCol]){
1029
+ cols[nextCol] = 1;
1030
+ nextCol += 1;
1031
+ }
1032
+ while(rowCellsAllEmpty(j, nextCol, grid[nextRow])){
1033
+ for(let col = j; col < nextCol; col++)grid[nextRow][col] = 1;
1034
+ nextRow += 1;
1035
+ }
1036
+ added.push({
1037
+ column: span(j + 1, nextCol + 1),
1038
+ id: createPlaceholderId(),
1039
+ resizeable: "hv",
1040
+ row: span(i + 1, nextRow + 1),
1041
+ type: "placeholder"
1042
+ });
1043
+ }
1044
+ }
1045
+ return {
1046
+ added,
1047
+ geometry: {
1048
+ ...geometry,
1049
+ items: [
1050
+ ...items,
1051
+ ...added
1052
+ ]
1053
+ },
1054
+ removedIds
1055
+ };
1056
+ };
1057
+ const getMatchingColspan = (targetGridItem, siblings, contras)=>{
1058
+ const startCol = targetGridItem.column.start;
1059
+ let siblingIndex = 0;
1060
+ let contraIndex = 0;
1061
+ const contrasOut = [];
1062
+ const siblingsOut = [
1063
+ targetGridItem
1064
+ ];
1065
+ const targetAndSiblings = [
1066
+ targetGridItem,
1067
+ ...siblings
1068
+ ];
1069
+ while(siblingIndex < targetAndSiblings.length && contraIndex < contras.length){
1070
+ const sibling = targetAndSiblings[siblingIndex];
1071
+ const contra = contras[contraIndex];
1072
+ const end = Math.max(contra.column.end, sibling.column.end);
1073
+ if (contra.column.end === end && sibling.column.end === end) {
1074
+ contrasOut.push(contras[contraIndex]);
1075
+ return {
1076
+ contras: contrasOut,
1077
+ position: span(startCol, end),
1078
+ siblings: siblingsOut
1079
+ };
1080
+ }
1081
+ if (contra.column.end < end) {
1082
+ contrasOut.push(contras[contraIndex]);
1083
+ contraIndex += 1;
1084
+ } else {
1085
+ siblingsOut.push(siblings[siblingIndex]);
1086
+ siblingIndex += 1;
1087
+ }
1088
+ }
1089
+ };
1090
+ const getMatchingRowspan = (gridItem, siblings, contras)=>{
1091
+ const startRow = gridItem.row.start;
1092
+ let siblingIndex = 0;
1093
+ let contraIndex = 0;
1094
+ const contrasOut = [];
1095
+ const siblingsOut = [
1096
+ gridItem
1097
+ ];
1098
+ const targetAndSiblings = [
1099
+ gridItem,
1100
+ ...siblings
1101
+ ];
1102
+ while(siblingIndex < targetAndSiblings.length && contraIndex < contras.length){
1103
+ const sibling = targetAndSiblings[siblingIndex];
1104
+ const contra = contras[contraIndex];
1105
+ const end = Math.max(contra.row.end, sibling.row.end);
1106
+ if (contra.row.end === end && sibling.row.end === end) {
1107
+ contrasOut.push(contras[contraIndex]);
1108
+ return {
1109
+ contras: contrasOut,
1110
+ position: span(startRow, end),
1111
+ siblings: siblingsOut
1112
+ };
1113
+ }
1114
+ if (contra.row.end < end) {
1115
+ contrasOut.push(contras[contraIndex]);
1116
+ contraIndex += 1;
1117
+ } else {
1118
+ siblingsOut.push(siblings[siblingIndex]);
1119
+ siblingIndex += 1;
1120
+ }
1121
+ }
1122
+ };
1123
+ const getContrasAbove = (items, { column, row })=>{
1124
+ const allContrasAbove = findByRowEnd(items, row.start);
1125
+ if (allContrasAbove) {
1126
+ const indexOfAlignedContra = allContrasAbove.findIndex((item)=>item.column.start === column.start);
1127
+ if (-1 !== indexOfAlignedContra) return [
1128
+ ...allContrasAbove
1129
+ ].sort(byColumnStart).slice(indexOfAlignedContra);
1130
+ }
1131
+ return [];
1132
+ };
1133
+ const getContrasBelow = (items, { column, row })=>{
1134
+ const allContrasBelow = findByRowStart(items, row.end);
1135
+ if (allContrasBelow) {
1136
+ const indexOfAlignedContra = allContrasBelow.findIndex((item)=>item.column.start === column.start);
1137
+ if (-1 !== indexOfAlignedContra) return [
1138
+ ...allContrasBelow
1139
+ ].sort(byColumnStart).slice(indexOfAlignedContra);
1140
+ }
1141
+ return [];
1142
+ };
1143
+ const getSiblingsRight = (items, { column, row })=>findByRowStart(items, row.start)?.filter((item)=>item.column.start > column.start) ?? [];
1144
+ const getContrasLeft = (items, { column, row })=>{
1145
+ const allContrasLeft = findByColumnEnd(items, column.start);
1146
+ if (allContrasLeft) {
1147
+ const indexOfAlignedContra = allContrasLeft.findIndex((item)=>item.row.start === row.start);
1148
+ if (-1 !== indexOfAlignedContra) return [
1149
+ ...allContrasLeft
1150
+ ].sort(byRowStart).slice(indexOfAlignedContra);
1151
+ }
1152
+ return [];
1153
+ };
1154
+ const getSiblingsBelow = (items, { column, row })=>findByColumnStart(items, column.start)?.filter((item)=>item.row.start > row.start) ?? [];
1155
+ const findColumnContrasAndSiblings = (items, childItem)=>{
1156
+ const contrasLeft = getContrasLeft(items, childItem);
1157
+ if (contrasLeft.length > 0) return getMatchingRowspan(childItem, getSiblingsBelow(items, childItem), contrasLeft);
1158
+ };
1159
+ const findRowContrasAndSiblings = (items, childItem)=>{
1160
+ const contrasAbove = getContrasAbove(items, childItem);
1161
+ if (contrasAbove.length > 0) if (!isFixedHeightItem(childItem)) return getMatchingColspan(childItem, getSiblingsRight(items, childItem), contrasAbove);
1162
+ else {
1163
+ const contrasBelow = getContrasBelow(items, childItem);
1164
+ if (contrasBelow.length > 0) {
1165
+ const [contra] = contrasBelow;
1166
+ return getMatchingColspan(contra, getSiblingsRight(items, contra), contrasAbove);
1167
+ }
1168
+ }
1169
+ };
1170
+ const computeSplitters = (geometry)=>{
1171
+ const { items } = geometry;
1172
+ const layoutItems = items.filter(({ stackId })=>void 0 === stackId);
1173
+ const splitters = [];
1174
+ const horizontalSplitterItemIds = [];
1175
+ const verticalSplitterItemIds = [];
1176
+ for (const childItem of layoutItems){
1177
+ const { column, id, row } = childItem;
1178
+ if (!isFixedWidthItem(childItem)) {
1179
+ const columnContrasAndSiblings = findColumnContrasAndSiblings(layoutItems, childItem);
1180
+ if (columnContrasAndSiblings) {
1181
+ const resizeTrackIndex = column.start - 1;
1182
+ const contraTrackIndex = column.start - 2;
1183
+ columnContrasAndSiblings.siblings.forEach((sibling)=>{
1184
+ verticalSplitterItemIds.push(sibling.id);
1185
+ });
1186
+ splitters.push({
1187
+ align: "start",
1188
+ ariaOrientation: "vertical",
1189
+ column,
1190
+ controls: id,
1191
+ id: `${id}-splitter-h`,
1192
+ orientation: "horizontal",
1193
+ resizedChildItems: {
1194
+ after: columnContrasAndSiblings.siblings.map((c)=>c.id),
1195
+ before: columnContrasAndSiblings.contras.map((c)=>c.id)
1196
+ },
1197
+ resizedGridTracks: [
1198
+ contraTrackIndex,
1199
+ resizeTrackIndex
1200
+ ],
1201
+ row: columnContrasAndSiblings.position
1202
+ });
1203
+ }
1204
+ }
1205
+ if (!isFixedHeightItem(childItem)) {
1206
+ const rowContrasAndSiblings = findRowContrasAndSiblings(layoutItems, childItem);
1207
+ if (rowContrasAndSiblings) {
1208
+ const contraTrackIndex = row.start - 2;
1209
+ let resizeTrackIndex = row.start - 1;
1210
+ if (rowContrasAndSiblings.siblings[0].id !== childItem.id) resizeTrackIndex = rowContrasAndSiblings.siblings[0].row.start - 1;
1211
+ rowContrasAndSiblings.siblings.forEach((sibling)=>{
1212
+ horizontalSplitterItemIds.push(sibling.id);
1213
+ });
1214
+ splitters.push({
1215
+ align: "start",
1216
+ ariaOrientation: "horizontal",
1217
+ column: rowContrasAndSiblings.position,
1218
+ controls: id,
1219
+ id: `${id}-splitter-v`,
1220
+ orientation: "vertical",
1221
+ resizedChildItems: {
1222
+ after: rowContrasAndSiblings.siblings.map((c)=>c.id),
1223
+ before: rowContrasAndSiblings.contras.map((c)=>c.id)
1224
+ },
1225
+ resizedGridTracks: [
1226
+ contraTrackIndex,
1227
+ resizeTrackIndex
1228
+ ],
1229
+ row
1230
+ });
1231
+ }
1232
+ }
1233
+ }
1234
+ return {
1235
+ horizontalSplitterItemIds,
1236
+ splitters,
1237
+ verticalSplitterItemIds
1238
+ };
1239
+ };
1240
+ const doesResizeRequireNewTrack = (splitters, splitter)=>{
1241
+ const potentialCandidates = splitters.filter(({ id, orientation })=>orientation === splitter.orientation && id !== splitter.id);
1242
+ if (potentialCandidates.length > 0) {
1243
+ const track = "horizontal" === splitter.orientation ? "column" : "row";
1244
+ const splitterStart = splitter[track].start;
1245
+ return potentialCandidates.some(({ [track]: { start } })=>start === splitterStart);
1246
+ }
1247
+ return false;
1248
+ };
1249
+ const insertTrackForResize = (geometry, { contraItemIds, index, position, resizeItemIds, size, trackType }, measurements = NO_MEASUREMENTS)=>{
1250
+ const tracks = readTracks(geometryTracks(geometry, trackType), measurements[trackType]);
1251
+ const inserted = insertTrack(tracks, {
1252
+ index,
1253
+ position
1254
+ }, size, trackType);
1255
+ if (!inserted.ok) return inserted;
1256
+ const insertUpdates = shiftItemsForInsertedTrack(geometry.items, trackType, index, position);
1257
+ let current = applyGeometryUpdates(geometry, insertUpdates);
1258
+ if (inserted.value) current = withTracks(current, trackType, inserted.value.tracks);
1259
+ const adjustment = "before" === position ? -1 : 1;
1260
+ const anchorUpdates = [];
1261
+ for (const id of resizeItemIds){
1262
+ const item = findGeometryItem(current, id);
1263
+ if (void 0 === item) return failure("ITEM_NOT_FOUND", `[GridModel] GridItem #${id} not found`);
1264
+ const { start, end } = item[trackType];
1265
+ anchorUpdates.push({
1266
+ [trackType]: span(start + adjustment, end),
1267
+ id
1268
+ });
1269
+ }
1270
+ for (const id of contraItemIds){
1271
+ const item = findGeometryItem(current, id);
1272
+ if (void 0 === item) return failure("ITEM_NOT_FOUND", `[GridModel] GridItem #${id} not found`);
1273
+ const { start, end } = item[trackType];
1274
+ anchorUpdates.push({
1275
+ [trackType]: span(start, end + adjustment),
1276
+ id
1277
+ });
1278
+ }
1279
+ return ok({
1280
+ ...trackTransitionFor(trackType, inserted.value),
1281
+ anchorUpdates,
1282
+ geometry: applyGeometryUpdates(current, anchorUpdates),
1283
+ insertUpdates
1284
+ });
1285
+ };
1286
+ const removeGridTrack = (geometry, { assignDirection, trackIndex, trackType, updateItems = true }, measurements = NO_MEASUREMENTS)=>{
1287
+ const tracks = readTracks(geometryTracks(geometry, trackType), measurements[trackType]);
1288
+ const removed = removeTrack(tracks, trackIndex, assignDirection, trackType);
1289
+ if (!removed.ok) return removed;
1290
+ const updates = updateItems ? shiftItemsForRemovedTrack(geometry.items, trackType, trackIndex) : [];
1291
+ return ok({
1292
+ ...trackTransitionFor(trackType, removed.value),
1293
+ geometry: withTracks(applyGeometryUpdates(geometry, updates), trackType, removed.value.tracks),
1294
+ silentUpdates: [],
1295
+ updates
1296
+ });
1297
+ };
1298
+ export { NO_MEASUREMENTS, NO_TRACK_METRICS, applyGeometryUpdates, bisectTracks, canSplitGridItem, computeSplitters, doesResizeRequireNewTrack, findBisectingTrack, findGeometryItem, findUnusedGridLines, geometryTracks, getGridMatrix, getProportionalResizeAllowance, gridResizeDirectionForSplit, insertTrack, insertTrackForResize, isFixedHeightItem, isFixedWidthItem, isFractionTrackSize, isPixelTrackSize, itemsFillColumn, itemsFillRow, readTracks, regeneratePlaceholders, removeGridItem, removeGridTrack, removeTrack, replaceGridItem, resizeTrackTo, resizeTracksAdjacent, resizeTracksProportionally, shiftItemsForInsertedTrack, shiftItemsForNewTrack, shiftItemsForRemovedTrack, splitGridChildPosition, splitGridItem, splitTrack, trackMetrics, trackSizes };