@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.
- package/LICENSE +201 -0
- package/README.md +141 -0
- package/package.json +40 -0
- package/src/GridCommand.js +395 -0
- package/src/GridComponentSettings.js +163 -0
- package/src/GridController.js +359 -0
- package/src/GridDragCoordinator.js +293 -0
- package/src/GridGeometry.js +1298 -0
- package/src/GridLayout.css.js +119 -0
- package/src/GridLayout.js +120 -0
- package/src/GridLayoutContext.js +58 -0
- package/src/GridLayoutDocument.js +307 -0
- package/src/GridLayoutDocumentIds.js +110 -0
- package/src/GridLayoutItem.css.js +46 -0
- package/src/GridLayoutItem.js +147 -0
- package/src/GridLayoutLegacyCompatibility.js +66 -0
- package/src/GridLayoutModel.js +138 -0
- package/src/GridLayoutProvider.js +263 -0
- package/src/GridLayoutStackedItem.css.js +26 -0
- package/src/GridLayoutStackedtem.js +140 -0
- package/src/GridModel.js +1289 -0
- package/src/GridPlaceholder.css.js +9 -0
- package/src/GridPlaceholder.js +24 -0
- package/src/GridSnapshot.js +11 -0
- package/src/GridSplitter.css.js +62 -0
- package/src/GridSplitter.js +26 -0
- package/src/GridStack.js +277 -0
- package/src/Icon.css.js +8 -0
- package/src/Icon.js +26 -0
- package/src/IconButton.css.js +9 -0
- package/src/IconButton.js +28 -0
- package/src/TabDialog.css.js +10 -0
- package/src/TabDialog.js +77 -0
- package/src/TabMenu.css.js +8 -0
- package/src/TabMenu.js +78 -0
- package/src/componentToJson.js +41 -0
- package/src/drag-drop-next/DragContextNext.js +173 -0
- package/src/drag-drop-next/DragDropProviderNext.css.js +72 -0
- package/src/drag-drop-next/DragDropProviderNext.js +91 -0
- package/src/drag-drop-next/SpaceMan.js +256 -0
- package/src/drag-drop-next/TemplateDragSession.js +36 -0
- package/src/drag-drop-next/tabstrip-drag-drop.js +251 -0
- package/src/grid-dom-utils.js +57 -0
- package/src/grid-layout-utils.js +39 -0
- package/src/grid-snapshot-adapters.js +435 -0
- package/src/index.js +24 -0
- package/src/json-value.js +56 -0
- package/src/layoutFromJson.js +15 -0
- package/src/layoutToJson.js +5 -0
- package/src/propUtils.js +15 -0
- package/src/react-element-utils.js +15 -0
- package/src/typeOf.js +15 -0
- package/src/useAsDropTarget.js +229 -0
- package/src/useDraggable.js +46 -0
- package/src/useEditTabName.js +70 -0
- package/src/useGridChildProps.js +47 -0
- package/src/useGridControllerSnapshot.js +21 -0
- package/src/useGridLayout.js +727 -0
- package/src/useGridSplitterResizing.js +262 -0
- package/src/useNotDropTarget.js +2 -0
- package/types/AddTabDialog.d.ts +5 -0
- package/types/GridCommand.d.ts +146 -0
- package/types/GridComponentSettings.d.ts +71 -0
- package/types/GridController.d.ts +50 -0
- package/types/GridDragCoordinator.d.ts +80 -0
- package/types/GridGeometry.d.ts +331 -0
- package/types/GridLayout.d.ts +20 -0
- package/types/GridLayoutContext.d.ts +141 -0
- package/types/GridLayoutDocument.d.ts +61 -0
- package/types/GridLayoutDocumentIds.d.ts +15 -0
- package/types/GridLayoutItem.d.ts +28 -0
- package/types/GridLayoutLegacyCompatibility.d.ts +57 -0
- package/types/GridLayoutModel.d.ts +72 -0
- package/types/GridLayoutProvider.d.ts +59 -0
- package/types/GridLayoutStackedtem.d.ts +10 -0
- package/types/GridModel.d.ts +504 -0
- package/types/GridPlaceholder.d.ts +5 -0
- package/types/GridSnapshot.d.ts +49 -0
- package/types/GridSplitter.d.ts +10 -0
- package/types/GridStack.d.ts +160 -0
- package/types/Icon.d.ts +6 -0
- package/types/IconButton.d.ts +6 -0
- package/types/TabDialog.d.ts +9 -0
- package/types/TabMenu.d.ts +7 -0
- package/types/componentToJson.d.ts +38 -0
- package/types/drag-drop-next/DragContextNext.d.ts +92 -0
- package/types/drag-drop-next/DragDropProviderNext.d.ts +23 -0
- package/types/drag-drop-next/SpaceMan.d.ts +35 -0
- package/types/drag-drop-next/TemplateDragSession.d.ts +21 -0
- package/types/drag-drop-next/tabstrip-drag-drop.d.ts +3 -0
- package/types/grid-dom-utils.d.ts +17 -0
- package/types/grid-layout-utils.d.ts +18 -0
- package/types/grid-matrix.d.ts +5 -0
- package/types/grid-snapshot-adapters.d.ts +22 -0
- package/types/index.d.ts +24 -0
- package/types/json-value.d.ts +23 -0
- package/types/layoutFromJson.d.ts +4 -0
- package/types/layoutToJson.d.ts +3 -0
- package/types/propUtils.d.ts +5 -0
- package/types/react-element-utils.d.ts +3 -0
- package/types/src/AddTabDialog.d.ts +5 -0
- package/types/src/GridLayout.d.ts +14 -0
- package/types/src/GridLayoutContext.d.ts +88 -0
- package/types/src/GridLayoutItem.d.ts +28 -0
- package/types/src/GridLayoutModel.d.ts +59 -0
- package/types/src/GridLayoutProvider.d.ts +37 -0
- package/types/src/GridLayoutStackedtem.d.ts +10 -0
- package/types/src/GridModel.d.ts +350 -0
- package/types/src/GridPlaceholder.d.ts +5 -0
- package/types/src/GridSplitter.d.ts +9 -0
- package/types/src/Icon.d.ts +6 -0
- package/types/src/IconButton.d.ts +6 -0
- package/types/src/TabMenu.d.ts +6 -0
- package/types/src/componentToJson.d.ts +32 -0
- package/types/src/drag-drop-next/DragContextNext.d.ts +73 -0
- package/types/src/drag-drop-next/DragDropProviderNext.d.ts +20 -0
- package/types/src/drag-drop-next/SpaceMan.d.ts +35 -0
- package/types/src/drag-drop-next/tabstrip-drag-drop.d.ts +3 -0
- package/types/src/grid-dom-utils.d.ts +17 -0
- package/types/src/grid-layout-utils.d.ts +80 -0
- package/types/src/grid-matrix.d.ts +5 -0
- package/types/src/index.d.ts +10 -0
- package/types/src/layoutFromJson.d.ts +3 -0
- package/types/src/layoutToJson.d.ts +2 -0
- package/types/src/propUtils.d.ts +5 -0
- package/types/src/react-element-utils.d.ts +3 -0
- package/types/src/typeOf.d.ts +6 -0
- package/types/src/useAsDropTarget.d.ts +7 -0
- package/types/src/useDraggable.d.ts +23 -0
- package/types/src/useGridChildProps.d.ts +13 -0
- package/types/src/useGridLayout.d.ts +43 -0
- package/types/src/useGridSplitterResizing.d.ts +12 -0
- package/types/src/useNotDropTarget.d.ts +1 -0
- package/types/typeOf.d.ts +6 -0
- package/types/useAsDropTarget.d.ts +7 -0
- package/types/useDraggable.d.ts +24 -0
- package/types/useEditTabName.d.ts +11 -0
- package/types/useGridChildProps.d.ts +13 -0
- package/types/useGridControllerSnapshot.d.ts +7 -0
- package/types/useGridLayout.d.ts +53 -0
- package/types/useGridSplitterResizing.d.ts +14 -0
- 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 };
|