@dashfoo/core 1.0.0 → 1.1.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/README.md +30 -23
- package/dist/index.d.ts +308 -196
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +279 -269
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -2,11 +2,8 @@ import { z } from "zod";
|
|
|
2
2
|
import { assign, emit, setup } from "xstate";
|
|
3
3
|
|
|
4
4
|
//#region src/lib/clamp-selected.ts
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
* boundary (`tabsetNodeSchema`) and the tree walker that `normalize` uses need
|
|
8
|
-
* it, and importing it from either one would close a module cycle.
|
|
9
|
-
*/
|
|
5
|
+
/** In `lib/` because both the parse boundary and `normalize` need it, and
|
|
6
|
+
* importing it from either would close a module cycle. */
|
|
10
7
|
const clampSelected = (length, selected) => {
|
|
11
8
|
if (length === 0) return 0;
|
|
12
9
|
const base = Number.isFinite(selected) ? Math.trunc(selected) : 0;
|
|
@@ -17,14 +14,7 @@ const clampSelected = (length, selected) => {
|
|
|
17
14
|
|
|
18
15
|
//#endregion
|
|
19
16
|
//#region src/model/schema.ts
|
|
20
|
-
/**
|
|
21
|
-
* Weights are relative shares, and every reader that rendered one already
|
|
22
|
-
* treated a missing weight as a single share (`row-view` summed `weight ?? 1`),
|
|
23
|
-
* so filling it in at the boundary changes nothing on screen. The one place that
|
|
24
|
-
* read a missing weight as a percentage was `placeBesideTarget`, which is why
|
|
25
|
-
* docking beside an unweighted target halved it to `50` and left its unweighted
|
|
26
|
-
* siblings rendering as a sliver of the row.
|
|
27
|
-
*/
|
|
17
|
+
/** Weights are relative shares of a row, not percentages. */
|
|
28
18
|
const DEFAULT_WEIGHT = 1;
|
|
29
19
|
const jsonValueSchema = z.lazy(() => z.union([
|
|
30
20
|
z.string(),
|
|
@@ -69,11 +59,8 @@ const tabNodeSchema = z.object({
|
|
|
69
59
|
type: z.literal("tab")
|
|
70
60
|
});
|
|
71
61
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* `.pick`), and picking `weight` with its default attached would inject that
|
|
75
|
-
* default: `.partial()` over a defaulted field still fills it in, which would
|
|
76
|
-
* rewrite a tabset's weight on every unrelated attribute update.
|
|
62
|
+
* Kept pre-transform so `node-attrs` can `.pick()` from it: a pipe has no
|
|
63
|
+
* `.pick`, and `.partial()` over a defaulted field still fills the default in.
|
|
77
64
|
*/
|
|
78
65
|
const tabsetNodeObjectSchema = z.object({
|
|
79
66
|
children: z.array(tabNodeSchema),
|
|
@@ -91,8 +78,11 @@ const tabsetNodeSchema = tabsetNodeObjectSchema.transform((tabset) => ({
|
|
|
91
78
|
...tabset,
|
|
92
79
|
selected: clampSelected(tabset.children.length, tabset.selected)
|
|
93
80
|
}));
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
/** The getter's explicit return type is required: without it, TS7023. */
|
|
82
|
+
const rowNodeSchema = z.object({
|
|
83
|
+
get children() {
|
|
84
|
+
return z.array(z.union([rowNodeSchema, tabsetNodeSchema]));
|
|
85
|
+
},
|
|
96
86
|
id: z.string(),
|
|
97
87
|
max: dimensionSchema.optional(),
|
|
98
88
|
min: dimensionSchema.optional(),
|
|
@@ -100,7 +90,7 @@ const rowNodeSchema = z.lazy(() => z.object({
|
|
|
100
90
|
snap: snapSchema.optional(),
|
|
101
91
|
type: z.literal("row"),
|
|
102
92
|
weight: z.number().default(1)
|
|
103
|
-
})
|
|
93
|
+
});
|
|
104
94
|
const geometrySchema = z.object({
|
|
105
95
|
height: z.number(),
|
|
106
96
|
left: z.number(),
|
|
@@ -146,11 +136,7 @@ const mutableTabAttrsSchema = tabNodeSchema.pick({
|
|
|
146
136
|
enableRename: true,
|
|
147
137
|
name: true
|
|
148
138
|
}).partial();
|
|
149
|
-
/**
|
|
150
|
-
* `weight` is re-declared instead of picked: `.partial()` over the schema's
|
|
151
|
-
* defaulted `weight` still injects the default, so `{ name: "x" }` would arrive
|
|
152
|
-
* at the reducer carrying a weight and silently resize the tabset.
|
|
153
|
-
*/
|
|
139
|
+
/** `weight` is re-declared, not picked: `.partial()` over a defaulted field still injects the default. */
|
|
154
140
|
const mutableTabsetAttrsSchema = tabsetNodeObjectSchema.pick({
|
|
155
141
|
enableClose: true,
|
|
156
142
|
enableMaximize: true,
|
|
@@ -166,13 +152,8 @@ const mutableRowAttrsSchema = z.object({
|
|
|
166
152
|
weight: z.number()
|
|
167
153
|
}).partial();
|
|
168
154
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* its first member and strips the keys the others own, which erased every
|
|
172
|
-
* tabset and row attribute at the parse boundary. The four keys that two kinds
|
|
173
|
-
* share (`enableClose`, `max`, `min`, `weight`) declare the same type on both
|
|
174
|
-
* sides, so merging widens nothing. Which keys are legal for a given node is
|
|
175
|
-
* enforced by the reducer against that node's own schema.
|
|
155
|
+
* Merged, not a union: a union of all-optional members matches its first member
|
|
156
|
+
* and strips the rest. The reducer enforces which keys a given node accepts.
|
|
176
157
|
*/
|
|
177
158
|
const mutableNodeAttrsSchema = mutableTabAttrsSchema.extend(mutableTabsetAttrsSchema.shape).extend(mutableRowAttrsSchema.shape);
|
|
178
159
|
|
|
@@ -202,7 +183,6 @@ const actionSchema = z.discriminatedUnion("type", [
|
|
|
202
183
|
type: z.literal("moveNode")
|
|
203
184
|
}),
|
|
204
185
|
z.object({
|
|
205
|
-
index: z.number().int().optional(),
|
|
206
186
|
location: dockLocationSchema,
|
|
207
187
|
sourceId: z.string(),
|
|
208
188
|
targetId: z.string(),
|
|
@@ -293,16 +273,9 @@ const createTabId = () => createNodeId("tab");
|
|
|
293
273
|
const collectRoots = (model) => [model.layout, ...model.floats.map((float) => float.layout)];
|
|
294
274
|
const findFloat = (model, floatId) => model.floats.find((float) => float.id === floatId);
|
|
295
275
|
/**
|
|
296
|
-
* Depth-first pre-order
|
|
297
|
-
* order
|
|
298
|
-
*
|
|
299
|
-
* traversal serves both find-first and collect-all.
|
|
300
|
-
*
|
|
301
|
-
* Every query below used to carry its own copy of this recursion, and they had
|
|
302
|
-
* drifted in child order: some scanned a row's own tabsets before descending,
|
|
303
|
-
* others interleaved. With unique ids all orders agree, so they now share this
|
|
304
|
-
* one; a model with duplicate ids (which the builder and the parser both warn
|
|
305
|
-
* about) can see a different duplicate win than it did before.
|
|
276
|
+
* Depth-first pre-order; a defined `visit` result stops the walk. With unique
|
|
277
|
+
* ids the order is immaterial, but a duplicate-id model (which the builder and
|
|
278
|
+
* the parser both warn about) resolves to whichever copy this reaches first.
|
|
306
279
|
*/
|
|
307
280
|
const walk = (row, visit, parent, index = -1) => {
|
|
308
281
|
const atRoot = visit({
|
|
@@ -333,7 +306,20 @@ const walk = (row, visit, parent, index = -1) => {
|
|
|
333
306
|
}
|
|
334
307
|
}
|
|
335
308
|
};
|
|
336
|
-
const
|
|
309
|
+
const isRow = (node) => node.type === "row";
|
|
310
|
+
const isTab = (node) => node.type === "tab";
|
|
311
|
+
const isTabset = (node) => node.type === "tabset";
|
|
312
|
+
/** Predicate-first, so an id shared across node kinds resolves the same way here as anywhere else. */
|
|
313
|
+
const locate = (model, id, is) => {
|
|
314
|
+
for (const root of collectRoots(model)) {
|
|
315
|
+
const found = walk(root, ({ index, node, parent }) => node.id === id && is(node) ? {
|
|
316
|
+
index,
|
|
317
|
+
node,
|
|
318
|
+
parent
|
|
319
|
+
} : void 0);
|
|
320
|
+
if (found) return found;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
337
323
|
const collectTabsetsInRow = (row, acc) => {
|
|
338
324
|
walk(row, ({ node }) => {
|
|
339
325
|
if (node.type === "tabset") acc.push(node);
|
|
@@ -345,41 +331,28 @@ const collectTabsets = (model) => {
|
|
|
345
331
|
return acc;
|
|
346
332
|
};
|
|
347
333
|
const getFirstTabset = (model) => collectTabsets(model)[0];
|
|
348
|
-
const findTabset = (model, tabsetId) =>
|
|
349
|
-
const findTabInContainer = (container, tabId) => {
|
|
350
|
-
const index = container.children.findIndex((tab) => tab.id === tabId);
|
|
351
|
-
if (index === -1) return;
|
|
352
|
-
const tab = container.children.at(index);
|
|
353
|
-
if (!tab) return;
|
|
354
|
-
return {
|
|
355
|
-
container,
|
|
356
|
-
index,
|
|
357
|
-
tab
|
|
358
|
-
};
|
|
359
|
-
};
|
|
334
|
+
const findTabset = (model, tabsetId) => locate(model, tabsetId, isTabset)?.node;
|
|
360
335
|
const findTab = (model, tabId) => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const found = locate(row, id)?.node;
|
|
368
|
-
return found?.type === "row" ? found : void 0;
|
|
336
|
+
const found = locate(model, tabId, isTab);
|
|
337
|
+
return found?.parent?.type === "tabset" ? {
|
|
338
|
+
container: found.parent,
|
|
339
|
+
index: found.index,
|
|
340
|
+
tab: found.node
|
|
341
|
+
} : void 0;
|
|
369
342
|
};
|
|
370
|
-
const
|
|
343
|
+
const findRow = (model, id) => locate(model, id, isRow)?.node;
|
|
371
344
|
const findAttributedNode = (model, id) => {
|
|
372
345
|
for (const root of collectRoots(model)) {
|
|
373
|
-
const found =
|
|
346
|
+
const found = walk(root, ({ node }) => node.id === id ? node : void 0);
|
|
374
347
|
if (found) return found;
|
|
375
348
|
}
|
|
376
349
|
};
|
|
377
350
|
const findRootContaining = (model, nodeId) => {
|
|
378
|
-
for (const root of collectRoots(model)) if (
|
|
351
|
+
for (const root of collectRoots(model)) if (walk(root, ({ node }) => node.id === nodeId ? true : void 0)) return root;
|
|
379
352
|
};
|
|
380
|
-
const findTabsetParent = (
|
|
381
|
-
const found = locate(
|
|
382
|
-
return found?.
|
|
353
|
+
const findTabsetParent = (model, tabsetId) => {
|
|
354
|
+
const found = locate(model, tabsetId, isTabset);
|
|
355
|
+
return found?.parent?.type === "row" ? {
|
|
383
356
|
index: found.index,
|
|
384
357
|
parent: found.parent
|
|
385
358
|
} : void 0;
|
|
@@ -417,29 +390,31 @@ const tab = (component, name, options = {}) => ({
|
|
|
417
390
|
});
|
|
418
391
|
const tabset = (children, options = {}) => {
|
|
419
392
|
const { max, min, ...rest } = options;
|
|
420
|
-
|
|
393
|
+
const built = {
|
|
421
394
|
children,
|
|
422
395
|
type: "tabset",
|
|
423
396
|
...rest,
|
|
424
|
-
...max === void 0 ? {} : { max: toDimension(max) },
|
|
425
|
-
...min === void 0 ? {} : { min: toDimension(min) },
|
|
426
397
|
id: options.id ?? createNodeId("tabset"),
|
|
427
398
|
selected: clampSelected(children.length, options.selected ?? 0),
|
|
428
399
|
weight: options.weight ?? 1
|
|
429
400
|
};
|
|
401
|
+
if (max !== void 0) built.max = toDimension(max);
|
|
402
|
+
if (min !== void 0) built.min = toDimension(min);
|
|
403
|
+
return built;
|
|
430
404
|
};
|
|
431
405
|
const row = (children, options = {}) => {
|
|
432
406
|
const { max, min, ...rest } = options;
|
|
433
|
-
|
|
407
|
+
const built = {
|
|
434
408
|
children,
|
|
435
409
|
type: "row",
|
|
436
410
|
...rest,
|
|
437
|
-
...max === void 0 ? {} : { max: toDimension(max) },
|
|
438
|
-
...min === void 0 ? {} : { min: toDimension(min) },
|
|
439
411
|
id: options.id ?? createNodeId("row"),
|
|
440
412
|
orientation: options.orientation ?? "row",
|
|
441
413
|
weight: options.weight ?? 1
|
|
442
414
|
};
|
|
415
|
+
if (max !== void 0) built.max = toDimension(max);
|
|
416
|
+
if (min !== void 0) built.min = toDimension(min);
|
|
417
|
+
return built;
|
|
443
418
|
};
|
|
444
419
|
const floatNode = (layout, geometry, options = {}) => ({
|
|
445
420
|
geometry,
|
|
@@ -487,13 +462,6 @@ const closestEdge = (d) => {
|
|
|
487
462
|
if (min === d.top) return "top";
|
|
488
463
|
return "bottom";
|
|
489
464
|
};
|
|
490
|
-
const dockLocationFor = (target) => {
|
|
491
|
-
switch (target.kind) {
|
|
492
|
-
case "split": return `split-${target.edge}`;
|
|
493
|
-
case "tab": return "center";
|
|
494
|
-
default: return assertNever(target);
|
|
495
|
-
}
|
|
496
|
-
};
|
|
497
465
|
const splitEdge = (location) => {
|
|
498
466
|
switch (location) {
|
|
499
467
|
case "center": return;
|
|
@@ -506,13 +474,10 @@ const splitEdge = (location) => {
|
|
|
506
474
|
};
|
|
507
475
|
const resolveDockTarget = (pointer, rect, opts) => {
|
|
508
476
|
const band = opts?.bandFraction ?? DEFAULT_BAND_FRACTION;
|
|
509
|
-
if (rect.width <= 0 || rect.height <= 0) return
|
|
477
|
+
if (rect.width <= 0 || rect.height <= 0) return "center";
|
|
510
478
|
const distances = edgeDistances(pointer, rect);
|
|
511
|
-
if (Math.min(distances.left, distances.right, distances.top, distances.bottom) > band) return
|
|
512
|
-
return {
|
|
513
|
-
edge: closestEdge(distances),
|
|
514
|
-
kind: "split"
|
|
515
|
-
};
|
|
479
|
+
if (Math.min(distances.left, distances.right, distances.top, distances.bottom) > band) return "center";
|
|
480
|
+
return `split-${closestEdge(distances)}`;
|
|
516
481
|
};
|
|
517
482
|
const dockZonePolygons = (rect, opts) => {
|
|
518
483
|
const { height: h, width: w, x, y } = rect;
|
|
@@ -661,21 +626,20 @@ const resolveSnapTargets = (config, panelCount) => {
|
|
|
661
626
|
}
|
|
662
627
|
return [...targets].toSorted((a, b) => a - b);
|
|
663
628
|
};
|
|
664
|
-
const
|
|
629
|
+
const resolveSnapGrid = (config, panelCount) => ({
|
|
630
|
+
targets: config === null ? [] : resolveSnapTargets(config, panelCount),
|
|
631
|
+
threshold: config?.threshold ?? 4
|
|
632
|
+
});
|
|
633
|
+
const snapEnabled = (grid) => grid.targets.length > 0;
|
|
665
634
|
const nearestTarget = (position, targets) => targets.reduce((best, target) => {
|
|
666
635
|
if (best === void 0 || Math.abs(target - position) < Math.abs(best - position)) return target;
|
|
667
636
|
return best;
|
|
668
637
|
}, void 0);
|
|
669
|
-
const snapSizes = (sizes, boundaryIndex,
|
|
670
|
-
const
|
|
638
|
+
const snapSizes = (sizes, boundaryIndex, grid) => {
|
|
639
|
+
const { targets, threshold } = grid;
|
|
671
640
|
const left = sizes[boundaryIndex];
|
|
672
641
|
const right = sizes[boundaryIndex + 1];
|
|
673
|
-
if (boundaryIndex < 0 || left === void 0 || right === void 0) return {
|
|
674
|
-
sizes,
|
|
675
|
-
snapped: false
|
|
676
|
-
};
|
|
677
|
-
const targets = resolveSnapTargets(config, sizes.length);
|
|
678
|
-
if (targets.length === 0) return {
|
|
642
|
+
if (boundaryIndex < 0 || left === void 0 || right === void 0 || targets.length === 0) return {
|
|
679
643
|
sizes,
|
|
680
644
|
snapped: false
|
|
681
645
|
};
|
|
@@ -699,50 +663,51 @@ const snapSizes = (sizes, boundaryIndex, config) => {
|
|
|
699
663
|
snapped: true
|
|
700
664
|
};
|
|
701
665
|
};
|
|
702
|
-
const decideSnap = (sizes, boundaryIndex,
|
|
703
|
-
if (boundaryIndex === null ||
|
|
704
|
-
const { sizes: snappedSizes, snapped } = snapSizes(sizes, boundaryIndex,
|
|
666
|
+
const decideSnap = (sizes, boundaryIndex, grid) => {
|
|
667
|
+
if (boundaryIndex === null || !snapEnabled(grid)) return { kind: "inactive" };
|
|
668
|
+
const { sizes: snappedSizes, snapped } = snapSizes(sizes, boundaryIndex, grid);
|
|
705
669
|
return snapped ? {
|
|
706
670
|
kind: "engage",
|
|
707
671
|
sizes: snappedSizes
|
|
708
672
|
} : { kind: "clear" };
|
|
709
673
|
};
|
|
710
|
-
const settleSnap = (sizes, boundaryIndex,
|
|
711
|
-
const decision = decideSnap(sizes, boundaryIndex,
|
|
674
|
+
const settleSnap = (sizes, boundaryIndex, grid) => {
|
|
675
|
+
const decision = decideSnap(sizes, boundaryIndex, grid);
|
|
712
676
|
return decision.kind === "engage" ? decision.sizes : sizes;
|
|
713
677
|
};
|
|
714
678
|
|
|
715
679
|
//#endregion
|
|
716
680
|
//#region src/model/invariants.ts
|
|
717
681
|
/**
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
* load and on every persist, so the loss is permanent.
|
|
722
|
-
*
|
|
723
|
-
* `snap` only exists on rows, so it is carried only to a row survivor.
|
|
682
|
+
* The survivor takes over the collapsed row's slot, so it has to take the slot's
|
|
683
|
+
* sizing too: dropping it widens a pinned sidebar, permanently, since this runs
|
|
684
|
+
* on every persist. `snap` is row-only, so it travels only to a row survivor.
|
|
724
685
|
*/
|
|
725
686
|
const inheritSlot = (row, survivor) => {
|
|
726
687
|
const sized = {
|
|
727
688
|
...survivor,
|
|
728
|
-
weight: row.weight
|
|
729
|
-
...row.min === void 0 ? {} : { min: row.min },
|
|
730
|
-
...row.max === void 0 ? {} : { max: row.max }
|
|
689
|
+
weight: row.weight
|
|
731
690
|
};
|
|
691
|
+
if (row.min !== void 0) sized.min = row.min;
|
|
692
|
+
if (row.max !== void 0) sized.max = row.max;
|
|
732
693
|
if (sized.type === "row" && row.snap !== void 0) return {
|
|
733
694
|
...sized,
|
|
734
695
|
snap: row.snap
|
|
735
696
|
};
|
|
736
697
|
return sized;
|
|
737
698
|
};
|
|
738
|
-
|
|
699
|
+
/** Records the id of every tabset it keeps, in document order. */
|
|
700
|
+
const normalizeRowChildren = (children, keptTabsetIds) => {
|
|
739
701
|
const out = [];
|
|
740
702
|
for (const child of children) {
|
|
741
703
|
if (child.type === "tabset") {
|
|
742
|
-
if (child.children.length > 0)
|
|
704
|
+
if (child.children.length > 0) {
|
|
705
|
+
out.push(child);
|
|
706
|
+
keptTabsetIds.push(child.id);
|
|
707
|
+
}
|
|
743
708
|
continue;
|
|
744
709
|
}
|
|
745
|
-
const grandchildren = normalizeRowChildren(child.children);
|
|
710
|
+
const grandchildren = normalizeRowChildren(child.children, keptTabsetIds);
|
|
746
711
|
if (grandchildren.length === 0) continue;
|
|
747
712
|
if (grandchildren.length === 1) {
|
|
748
713
|
const only = grandchildren[0];
|
|
@@ -756,9 +721,8 @@ const normalizeRowChildren = (children) => {
|
|
|
756
721
|
}
|
|
757
722
|
return out;
|
|
758
723
|
};
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
let children = normalizeRowChildren(root.children);
|
|
724
|
+
const normalizeLayout = (root, keptTabsetIds) => {
|
|
725
|
+
let children = normalizeRowChildren(root.children, keptTabsetIds);
|
|
762
726
|
let orientation = root.orientation;
|
|
763
727
|
let inner = children[0];
|
|
764
728
|
while (children.length === 1 && inner?.type === "row") {
|
|
@@ -773,53 +737,67 @@ const normalizeLayout = (root) => {
|
|
|
773
737
|
};
|
|
774
738
|
};
|
|
775
739
|
const normalize = (model) => {
|
|
776
|
-
const
|
|
740
|
+
const mainTabsetIds = [];
|
|
741
|
+
const layout = normalizeLayout(model.layout, mainTabsetIds);
|
|
777
742
|
const floats = [];
|
|
743
|
+
const floatTabsetIds = [];
|
|
778
744
|
for (const float of model.floats) {
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
745
|
+
const ownTabsetIds = [];
|
|
746
|
+
const floatLayout = normalizeLayout(float.layout, ownTabsetIds);
|
|
747
|
+
if (floatLayout.children.length > 0) {
|
|
748
|
+
floats.push({
|
|
749
|
+
...float,
|
|
750
|
+
layout: floatLayout
|
|
751
|
+
});
|
|
752
|
+
floatTabsetIds.push(...ownTabsetIds);
|
|
753
|
+
}
|
|
784
754
|
}
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const tabsets = collectTabsets(withRoots);
|
|
791
|
-
const tabsetIds = new Set(tabsets.map((tabset) => tabset.id));
|
|
792
|
-
const firstTabsetId = tabsets[0]?.id;
|
|
793
|
-
const activeTabsetId = model.activeTabsetId !== void 0 && tabsetIds.has(model.activeTabsetId) ? model.activeTabsetId : firstTabsetId;
|
|
794
|
-
const maximizedTabsetId = model.maximizedTabsetId !== void 0 && tabsetIds.has(model.maximizedTabsetId) ? model.maximizedTabsetId : void 0;
|
|
755
|
+
const mainIds = new Set(mainTabsetIds);
|
|
756
|
+
const liveIds = /* @__PURE__ */ new Set([...mainTabsetIds, ...floatTabsetIds]);
|
|
757
|
+
const firstTabsetId = mainTabsetIds[0] ?? floatTabsetIds[0];
|
|
758
|
+
const activeTabsetId = model.activeTabsetId !== void 0 && liveIds.has(model.activeTabsetId) ? model.activeTabsetId : firstTabsetId;
|
|
759
|
+
const maximizedTabsetId = model.maximizedTabsetId !== void 0 && mainIds.has(model.maximizedTabsetId) ? model.maximizedTabsetId : void 0;
|
|
795
760
|
return {
|
|
796
|
-
...
|
|
761
|
+
...model,
|
|
797
762
|
activeTabsetId,
|
|
763
|
+
floats,
|
|
764
|
+
layout,
|
|
798
765
|
maximizedTabsetId
|
|
799
766
|
};
|
|
800
767
|
};
|
|
801
768
|
|
|
802
769
|
//#endregion
|
|
803
770
|
//#region src/state/surgery.ts
|
|
804
|
-
const removeTabsetReturning = (
|
|
805
|
-
const found = findTabsetParent(
|
|
771
|
+
const removeTabsetReturning = (model, tabsetId) => {
|
|
772
|
+
const found = findTabsetParent(model, tabsetId);
|
|
806
773
|
if (!found) return;
|
|
807
774
|
const [removed] = found.parent.children.splice(found.index, 1);
|
|
808
775
|
return removed?.type === "tabset" ? removed : void 0;
|
|
809
776
|
};
|
|
810
|
-
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
777
|
+
/** Total, so a sixth `DockLocation` is a compile error here rather than a wrong default. */
|
|
778
|
+
const SPLIT_PLACEMENT = {
|
|
779
|
+
"split-bottom": {
|
|
780
|
+
before: false,
|
|
781
|
+
orientation: "column"
|
|
782
|
+
},
|
|
783
|
+
"split-left": {
|
|
784
|
+
before: true,
|
|
785
|
+
orientation: "row"
|
|
786
|
+
},
|
|
787
|
+
"split-right": {
|
|
788
|
+
before: false,
|
|
789
|
+
orientation: "row"
|
|
790
|
+
},
|
|
791
|
+
"split-top": {
|
|
792
|
+
before: true,
|
|
793
|
+
orientation: "column"
|
|
794
|
+
}
|
|
817
795
|
};
|
|
818
796
|
const placeBesideTarget = (draft, placed, targetId, location) => {
|
|
819
797
|
const targetTabset = findTabset(draft, targetId);
|
|
820
|
-
const found = findTabsetParent(
|
|
821
|
-
if (!targetTabset || !found) return;
|
|
822
|
-
const { before, orientation } =
|
|
798
|
+
const found = findTabsetParent(draft, targetId);
|
|
799
|
+
if (!targetTabset || !found) return false;
|
|
800
|
+
const { before, orientation } = SPLIT_PLACEMENT[location];
|
|
823
801
|
if (found.parent.orientation === orientation) {
|
|
824
802
|
const half = targetTabset.weight / 2;
|
|
825
803
|
targetTabset.weight = half;
|
|
@@ -838,16 +816,17 @@ const placeBesideTarget = (draft, placed, targetId, location) => {
|
|
|
838
816
|
found.parent.children.splice(found.index, 1, newRow);
|
|
839
817
|
}
|
|
840
818
|
draft.activeTabsetId = placed.id;
|
|
819
|
+
return true;
|
|
841
820
|
};
|
|
842
821
|
const insertTab = (draft, tab, target) => {
|
|
843
822
|
const { id: targetId, index, location } = target;
|
|
844
823
|
const targetTabset = findTabset(draft, targetId);
|
|
845
|
-
if (!targetTabset) return;
|
|
824
|
+
if (!targetTabset) return false;
|
|
846
825
|
if (location === "center") {
|
|
847
826
|
const at = Math.min(Math.max(index ?? targetTabset.children.length, 0), targetTabset.children.length);
|
|
848
827
|
targetTabset.children.splice(at, 0, tab);
|
|
849
828
|
targetTabset.selected = at;
|
|
850
|
-
return;
|
|
829
|
+
return true;
|
|
851
830
|
}
|
|
852
831
|
const newTabset = {
|
|
853
832
|
children: [tab],
|
|
@@ -856,13 +835,9 @@ const insertTab = (draft, tab, target) => {
|
|
|
856
835
|
type: "tabset",
|
|
857
836
|
weight: 1
|
|
858
837
|
};
|
|
859
|
-
placeBesideTarget(draft, newTabset, targetId, location);
|
|
838
|
+
return placeBesideTarget(draft, newTabset, targetId, location);
|
|
860
839
|
};
|
|
861
|
-
/**
|
|
862
|
-
* Removing a tab can leave `selected` past the end of the strip, so every
|
|
863
|
-
* detach re-clamps its container rather than leaving the whole tree to be
|
|
864
|
-
* re-walked afterwards.
|
|
865
|
-
*/
|
|
840
|
+
/** Re-clamps here rather than leaving the whole tree to be re-walked afterwards. */
|
|
866
841
|
const detachTab = (container, index) => {
|
|
867
842
|
const removed = container.children.splice(index, 1).at(0);
|
|
868
843
|
container.selected = clampSelected(container.children.length, container.selected);
|
|
@@ -924,10 +899,10 @@ const pushFloat = (draft, layout, geometry, floatId) => {
|
|
|
924
899
|
if (first) draft.activeTabsetId = first.id;
|
|
925
900
|
};
|
|
926
901
|
const floatTabsetById = (draft, tabsetId, geometry, floatId) => {
|
|
927
|
-
const
|
|
928
|
-
if (!
|
|
929
|
-
|
|
930
|
-
|
|
902
|
+
const detached = removeTabsetReturning(draft, tabsetId);
|
|
903
|
+
if (!detached) return false;
|
|
904
|
+
pushFloat(draft, wrapTabsetInLayout(detached), geometry, floatId);
|
|
905
|
+
return true;
|
|
931
906
|
};
|
|
932
907
|
const resolveMainTarget = (draft, targetId) => {
|
|
933
908
|
const mainTabsets = [];
|
|
@@ -943,36 +918,35 @@ const resolveMainTarget = (draft, targetId) => {
|
|
|
943
918
|
return mainTabsets[0];
|
|
944
919
|
};
|
|
945
920
|
const dockFloat = (draft, floatId, targetId, location) => {
|
|
946
|
-
const
|
|
947
|
-
const
|
|
948
|
-
if (index === -1) return;
|
|
949
|
-
const float = floats.splice(index, 1).at(0);
|
|
950
|
-
if (!float) return;
|
|
921
|
+
const index = draft.floats.findIndex((float) => float.id === floatId);
|
|
922
|
+
const float = draft.floats.at(index);
|
|
923
|
+
if (index === -1 || !float) return false;
|
|
951
924
|
const tabsets = [];
|
|
952
925
|
collectTabsetsInRow(float.layout, tabsets);
|
|
953
926
|
const tabs = tabsets.flatMap((tabset) => tabset.children);
|
|
954
|
-
if (tabs.length === 0) return;
|
|
927
|
+
if (tabs.length === 0) return false;
|
|
955
928
|
const leadTabset = tabsets.at(0);
|
|
956
929
|
const selectedOffset = leadTabset ? clampSelected(leadTabset.children.length, leadTabset.selected) : 0;
|
|
957
930
|
const target = resolveMainTarget(draft, targetId);
|
|
931
|
+
draft.floats.splice(index, 1);
|
|
958
932
|
if (!target) {
|
|
959
933
|
draft.layout = float.layout;
|
|
960
934
|
draft.activeTabsetId = leadTabset?.id;
|
|
961
|
-
return;
|
|
935
|
+
return true;
|
|
962
936
|
}
|
|
963
937
|
if (location === "center") {
|
|
964
938
|
mergeTabsInto(target, tabs, selectedOffset);
|
|
965
939
|
draft.activeTabsetId = target.id;
|
|
966
|
-
return;
|
|
940
|
+
return true;
|
|
967
941
|
}
|
|
968
|
-
const
|
|
969
|
-
placeBesideTarget(draft, tabsets.length === 1 && leadTabset ? leadTabset : {
|
|
942
|
+
const placed = tabsets.length === 1 && leadTabset ? leadTabset : {
|
|
970
943
|
children: tabs,
|
|
971
944
|
id: createNodeId("tabset"),
|
|
972
945
|
selected: selectedOffset,
|
|
973
946
|
type: "tabset",
|
|
974
947
|
weight: 1
|
|
975
|
-
}
|
|
948
|
+
};
|
|
949
|
+
return placeBesideTarget(draft, placed, target.id, location ?? "split-right");
|
|
976
950
|
};
|
|
977
951
|
|
|
978
952
|
//#endregion
|
|
@@ -991,127 +965,138 @@ const parseNodeAttrs = (node, attrs) => {
|
|
|
991
965
|
default: return assertNever(node);
|
|
992
966
|
}
|
|
993
967
|
};
|
|
968
|
+
const sameGeometry = (a, b) => a.height === b.height && a.left === b.left && a.top === b.top && a.width === b.width;
|
|
969
|
+
const alreadyApplied = (target, attrs) => {
|
|
970
|
+
const current = new Map(Object.entries(target));
|
|
971
|
+
return Object.entries(attrs).every(([key, value]) => Object.is(current.get(key), value));
|
|
972
|
+
};
|
|
973
|
+
/** Reports whether it changed anything, so `history.dispatch` can skip a no-op. */
|
|
994
974
|
const applyAction = (draft, action) => {
|
|
995
975
|
switch (action.type) {
|
|
996
|
-
case "addNode":
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
});
|
|
1002
|
-
return;
|
|
976
|
+
case "addNode": return insertTab(draft, action.tab, {
|
|
977
|
+
id: action.targetId,
|
|
978
|
+
index: action.index,
|
|
979
|
+
location: action.location
|
|
980
|
+
});
|
|
1003
981
|
case "adjustSplit": {
|
|
1004
|
-
const row = findRow(
|
|
1005
|
-
if (!row) return;
|
|
982
|
+
const row = findRow(draft, action.rowId);
|
|
983
|
+
if (!row) return false;
|
|
984
|
+
let changed = false;
|
|
1006
985
|
for (let index = 0; index < action.weights.length; index++) {
|
|
1007
986
|
const child = row.children.at(index);
|
|
1008
987
|
const weight = action.weights[index];
|
|
1009
|
-
if (!child || weight === void 0) continue;
|
|
988
|
+
if (!child || weight === void 0 || child.weight === weight) continue;
|
|
1010
989
|
child.weight = weight;
|
|
990
|
+
changed = true;
|
|
1011
991
|
}
|
|
1012
|
-
return;
|
|
992
|
+
return changed;
|
|
1013
993
|
}
|
|
1014
994
|
case "deleteTab": {
|
|
1015
995
|
const location = findTab(draft, action.tabId);
|
|
1016
|
-
if (location)
|
|
1017
|
-
|
|
996
|
+
if (!location) return false;
|
|
997
|
+
detachTab(location.container, location.index);
|
|
998
|
+
return true;
|
|
1018
999
|
}
|
|
1019
|
-
case "deleteTabset":
|
|
1020
|
-
|
|
1021
|
-
if (root) removeTabset(root, action.tabsetId);
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
case "dockFloat":
|
|
1025
|
-
dockFloat(draft, action.floatId, action.targetId, action.location);
|
|
1026
|
-
return;
|
|
1000
|
+
case "deleteTabset": return removeTabsetReturning(draft, action.tabsetId) !== void 0;
|
|
1001
|
+
case "dockFloat": return dockFloat(draft, action.floatId, action.targetId, action.location);
|
|
1027
1002
|
case "floatTab": {
|
|
1028
1003
|
const location = findTab(draft, action.tabId);
|
|
1029
|
-
if (!location) return;
|
|
1004
|
+
if (!location) return false;
|
|
1030
1005
|
const removed = detachTab(location.container, location.index);
|
|
1031
|
-
if (removed)
|
|
1032
|
-
|
|
1006
|
+
if (!removed) return false;
|
|
1007
|
+
pushFloat(draft, wrapTabInLayout(removed), action.geometry, action.floatId);
|
|
1008
|
+
return true;
|
|
1033
1009
|
}
|
|
1034
|
-
case "floatTabset":
|
|
1035
|
-
floatTabsetById(draft, action.tabsetId, action.geometry, action.floatId);
|
|
1036
|
-
return;
|
|
1010
|
+
case "floatTabset": return floatTabsetById(draft, action.tabsetId, action.geometry, action.floatId);
|
|
1037
1011
|
case "moveFloat": {
|
|
1038
1012
|
const float = findFloat(draft, action.floatId);
|
|
1039
|
-
if (float
|
|
1040
|
-
|
|
1013
|
+
if (!float || sameGeometry(float.geometry, action.geometry)) return false;
|
|
1014
|
+
float.geometry = action.geometry;
|
|
1015
|
+
return true;
|
|
1041
1016
|
}
|
|
1042
1017
|
case "moveNode": {
|
|
1043
1018
|
const source = findTab(draft, action.sourceId);
|
|
1044
|
-
if (!source) return;
|
|
1019
|
+
if (!source || !findTabset(draft, action.targetId)) return false;
|
|
1045
1020
|
const removed = detachTab(source.container, source.index);
|
|
1046
|
-
if (!removed) return;
|
|
1047
|
-
insertTab(draft, removed, {
|
|
1021
|
+
if (!removed) return false;
|
|
1022
|
+
return insertTab(draft, removed, {
|
|
1048
1023
|
id: action.targetId,
|
|
1049
1024
|
index: action.index,
|
|
1050
1025
|
location: action.location
|
|
1051
1026
|
});
|
|
1052
|
-
return;
|
|
1053
1027
|
}
|
|
1054
1028
|
case "moveTabset": {
|
|
1055
|
-
if (action.sourceId === action.targetId) return;
|
|
1029
|
+
if (action.sourceId === action.targetId) return false;
|
|
1056
1030
|
const source = findTabset(draft, action.sourceId);
|
|
1057
|
-
|
|
1031
|
+
const target = findTabset(draft, action.targetId);
|
|
1032
|
+
if (!source || !target) return false;
|
|
1058
1033
|
if (action.location === "center") {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const sourceRoot = findRootContaining(draft, action.sourceId);
|
|
1063
|
-
if (sourceRoot) removeTabset(sourceRoot, action.sourceId);
|
|
1064
|
-
}
|
|
1065
|
-
return;
|
|
1034
|
+
mergeTabsInto(target, source.children, clampSelected(source.children.length, source.selected));
|
|
1035
|
+
removeTabsetReturning(draft, action.sourceId);
|
|
1036
|
+
return true;
|
|
1066
1037
|
}
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
if (detached) placeBesideTarget(draft, detached, action.targetId, action.location);
|
|
1070
|
-
return;
|
|
1038
|
+
const detached = removeTabsetReturning(draft, action.sourceId);
|
|
1039
|
+
return detached ? placeBesideTarget(draft, detached, action.targetId, action.location) : false;
|
|
1071
1040
|
}
|
|
1072
1041
|
case "renameFloat": {
|
|
1073
1042
|
const float = findFloat(draft, action.floatId);
|
|
1074
|
-
if (float)
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1043
|
+
if (!float) return false;
|
|
1044
|
+
const others = draft.floats.filter((other) => other.id !== action.floatId);
|
|
1045
|
+
const name = uniqueFloatName(action.name, others);
|
|
1046
|
+
if (float.name === name) return false;
|
|
1047
|
+
float.name = name;
|
|
1048
|
+
return true;
|
|
1079
1049
|
}
|
|
1080
1050
|
case "renameTab": {
|
|
1081
1051
|
const location = findTab(draft, action.tabId);
|
|
1082
|
-
if (location
|
|
1083
|
-
|
|
1052
|
+
if (!location || location.tab.name === action.name) return false;
|
|
1053
|
+
location.tab.name = action.name;
|
|
1054
|
+
return true;
|
|
1084
1055
|
}
|
|
1085
1056
|
case "selectTab": {
|
|
1086
1057
|
const tabset = findTabset(draft, action.tabsetId);
|
|
1087
|
-
if (tabset)
|
|
1088
|
-
|
|
1058
|
+
if (!tabset) return false;
|
|
1059
|
+
const selected = clampSelected(tabset.children.length, action.index);
|
|
1060
|
+
if (tabset.selected === selected) return false;
|
|
1061
|
+
tabset.selected = selected;
|
|
1062
|
+
return true;
|
|
1089
1063
|
}
|
|
1090
1064
|
case "setActiveTabset":
|
|
1091
|
-
if (findTabset(draft, action.tabsetId))
|
|
1092
|
-
|
|
1065
|
+
if (draft.activeTabsetId === action.tabsetId || !findTabset(draft, action.tabsetId)) return false;
|
|
1066
|
+
draft.activeTabsetId = action.tabsetId;
|
|
1067
|
+
return true;
|
|
1093
1068
|
case "setFloatMinimized": {
|
|
1094
1069
|
const float = findFloat(draft, action.floatId);
|
|
1095
|
-
if (float
|
|
1096
|
-
|
|
1070
|
+
if (!float || (float.minimized ?? false) === action.minimized) return false;
|
|
1071
|
+
float.minimized = action.minimized;
|
|
1072
|
+
return true;
|
|
1073
|
+
}
|
|
1074
|
+
case "setMaximizedTabset": {
|
|
1075
|
+
const next = action.tabsetId ?? void 0;
|
|
1076
|
+
if (draft.maximizedTabsetId === next) return false;
|
|
1077
|
+
draft.maximizedTabsetId = next;
|
|
1078
|
+
return true;
|
|
1097
1079
|
}
|
|
1098
|
-
case "setMaximizedTabset":
|
|
1099
|
-
draft.maximizedTabsetId = action.tabsetId ?? void 0;
|
|
1100
|
-
return;
|
|
1101
1080
|
case "updateGlobalAttributes":
|
|
1081
|
+
if (alreadyApplied(draft.global, action.attrs)) return false;
|
|
1102
1082
|
Object.assign(draft.global, action.attrs);
|
|
1103
|
-
return;
|
|
1083
|
+
return true;
|
|
1104
1084
|
case "updateNodeAttributes": {
|
|
1105
1085
|
const node = findAttributedNode(draft, action.nodeId);
|
|
1106
|
-
if (node)
|
|
1107
|
-
|
|
1086
|
+
if (!node) return false;
|
|
1087
|
+
const parsed = parseNodeAttrs(node, action.attrs);
|
|
1088
|
+
if (alreadyApplied(node, parsed)) return false;
|
|
1089
|
+
Object.assign(node, parsed);
|
|
1090
|
+
return true;
|
|
1108
1091
|
}
|
|
1109
|
-
default: assertNever(action);
|
|
1092
|
+
default: return assertNever(action);
|
|
1110
1093
|
}
|
|
1111
1094
|
};
|
|
1095
|
+
/** Identity-equal output means "no edit". A rejected action skips `normalize`, so
|
|
1096
|
+
* pass a model that has already been through it. */
|
|
1112
1097
|
const reducer = (model, action) => {
|
|
1113
1098
|
const draft = structuredClone(model);
|
|
1114
|
-
applyAction(draft, action);
|
|
1099
|
+
if (!applyAction(draft, action)) return model;
|
|
1115
1100
|
return normalize(draft);
|
|
1116
1101
|
};
|
|
1117
1102
|
|
|
@@ -1125,8 +1110,14 @@ const createHistory = (present) => ({
|
|
|
1125
1110
|
});
|
|
1126
1111
|
const canUndo = (history) => history.past.length > 0;
|
|
1127
1112
|
const canRedo = (history) => history.future.length > 0;
|
|
1113
|
+
/**
|
|
1114
|
+
* An action that changed nothing is not an edit: recording it would spend an
|
|
1115
|
+
* undo slot and clear a redo stack the user just built. Not coalescing (ADR
|
|
1116
|
+
* 0002): two distinct edits are still two entries.
|
|
1117
|
+
*/
|
|
1128
1118
|
const dispatch = (history, action) => {
|
|
1129
1119
|
const present = reducer(history.present, action);
|
|
1120
|
+
if (Object.is(present, history.present)) return history;
|
|
1130
1121
|
return {
|
|
1131
1122
|
future: [],
|
|
1132
1123
|
past: [...history.past, history.present].slice(-100),
|
|
@@ -1172,25 +1163,50 @@ const dashfooMachine = setup({ types: {
|
|
|
1172
1163
|
|
|
1173
1164
|
//#endregion
|
|
1174
1165
|
//#region src/machines/drag-dock-machine.ts
|
|
1175
|
-
|
|
1176
|
-
|
|
1166
|
+
/** One definition of both "is this drop valid" and "what does it do", so the guard and the emit agree. */
|
|
1167
|
+
const dropAction = (drag) => {
|
|
1168
|
+
if (drag.kind !== "dragging" || drag.drop === null) return null;
|
|
1169
|
+
const { intent, scope } = drag.drop;
|
|
1170
|
+
const { subject } = drag;
|
|
1171
|
+
if (subject.kind === "external") return {
|
|
1172
|
+
action: {
|
|
1173
|
+
index: intent.index,
|
|
1174
|
+
location: intent.location,
|
|
1175
|
+
tab: subject.tab,
|
|
1176
|
+
targetId: intent.targetId,
|
|
1177
|
+
type: "addNode"
|
|
1178
|
+
},
|
|
1179
|
+
scope
|
|
1180
|
+
};
|
|
1181
|
+
if (subject.kind === "tabset") return {
|
|
1182
|
+
action: {
|
|
1183
|
+
location: intent.location,
|
|
1184
|
+
sourceId: subject.id,
|
|
1185
|
+
targetId: intent.targetId,
|
|
1186
|
+
type: "moveTabset"
|
|
1187
|
+
},
|
|
1188
|
+
scope
|
|
1189
|
+
};
|
|
1177
1190
|
return {
|
|
1178
|
-
|
|
1179
|
-
|
|
1191
|
+
action: {
|
|
1192
|
+
index: intent.index,
|
|
1193
|
+
location: intent.location,
|
|
1194
|
+
sourceId: subject.id,
|
|
1195
|
+
targetId: intent.targetId,
|
|
1196
|
+
type: "moveNode"
|
|
1197
|
+
},
|
|
1198
|
+
scope
|
|
1180
1199
|
};
|
|
1181
1200
|
};
|
|
1182
1201
|
const dragDockMachine = setup({
|
|
1183
|
-
guards: { hasValidDrop: ({ context }) => context.
|
|
1202
|
+
guards: { hasValidDrop: ({ context }) => dropAction(context.drag) !== null },
|
|
1184
1203
|
types: {
|
|
1185
1204
|
context: {},
|
|
1186
1205
|
emitted: {},
|
|
1187
1206
|
events: {}
|
|
1188
1207
|
}
|
|
1189
1208
|
}).createMachine({
|
|
1190
|
-
context: {
|
|
1191
|
-
intent: null,
|
|
1192
|
-
subject: null
|
|
1193
|
-
},
|
|
1209
|
+
context: { drag: { kind: "idle" } },
|
|
1194
1210
|
id: "dragDock",
|
|
1195
1211
|
initial: "idle",
|
|
1196
1212
|
states: {
|
|
@@ -1198,36 +1214,30 @@ const dragDockMachine = setup({
|
|
|
1198
1214
|
CANCEL: { target: "idle" },
|
|
1199
1215
|
DROP: [{
|
|
1200
1216
|
actions: emit(({ context }) => {
|
|
1201
|
-
const
|
|
1217
|
+
const resolved = dropAction(context.drag);
|
|
1218
|
+
if (resolved === null) throw new Error("drop requires a subject and a resolved drop target");
|
|
1202
1219
|
return {
|
|
1203
|
-
action:
|
|
1204
|
-
|
|
1205
|
-
location: intent.location,
|
|
1206
|
-
tab: subject.tab,
|
|
1207
|
-
targetId: intent.targetId,
|
|
1208
|
-
type: "addNode"
|
|
1209
|
-
} : {
|
|
1210
|
-
index: intent.index,
|
|
1211
|
-
location: intent.location,
|
|
1212
|
-
sourceId: subject.id,
|
|
1213
|
-
targetId: intent.targetId,
|
|
1214
|
-
type: subject.kind === "tabset" ? "moveTabset" : "moveNode"
|
|
1215
|
-
},
|
|
1220
|
+
action: resolved.action,
|
|
1221
|
+
scope: resolved.scope,
|
|
1216
1222
|
type: "COMMIT"
|
|
1217
1223
|
};
|
|
1218
1224
|
}),
|
|
1219
1225
|
guard: "hasValidDrop",
|
|
1220
1226
|
target: "idle"
|
|
1221
1227
|
}, { target: "idle" }],
|
|
1222
|
-
OVER: { actions: assign({
|
|
1228
|
+
OVER: { actions: assign({ drag: ({ context, event }) => context.drag.kind === "dragging" ? {
|
|
1229
|
+
...context.drag,
|
|
1230
|
+
drop: event.drop
|
|
1231
|
+
} : context.drag }) }
|
|
1223
1232
|
} },
|
|
1224
1233
|
idle: {
|
|
1225
|
-
entry: assign({
|
|
1226
|
-
intent: null,
|
|
1227
|
-
subject: null
|
|
1228
|
-
}),
|
|
1234
|
+
entry: assign({ drag: { kind: "idle" } }),
|
|
1229
1235
|
on: { START: {
|
|
1230
|
-
actions: assign({
|
|
1236
|
+
actions: assign({ drag: ({ event }) => ({
|
|
1237
|
+
drop: null,
|
|
1238
|
+
kind: "dragging",
|
|
1239
|
+
subject: event.subject
|
|
1240
|
+
}) }),
|
|
1231
1241
|
target: "dragging"
|
|
1232
1242
|
} }
|
|
1233
1243
|
}
|
|
@@ -1243,7 +1253,7 @@ const parseModel = (value) => {
|
|
|
1243
1253
|
return model;
|
|
1244
1254
|
};
|
|
1245
1255
|
const toJSON = (model) => JSON.stringify(model);
|
|
1246
|
-
const fromJSON = (json) => parseModel(JSON.parse(json));
|
|
1256
|
+
const fromJSON = (json) => parseModel(jsonValueSchema.parse(JSON.parse(json)));
|
|
1247
1257
|
|
|
1248
1258
|
//#endregion
|
|
1249
1259
|
//#region src/model/stack.ts
|
|
@@ -1269,5 +1279,5 @@ const stackModel = (model, orientation = "column") => {
|
|
|
1269
1279
|
};
|
|
1270
1280
|
|
|
1271
1281
|
//#endregion
|
|
1272
|
-
export { DEFAULT_SNAP_THRESHOLD, DEFAULT_WEIGHT, HISTORY_LIMIT, actionSchema, canRedo, canUndo, collectRoots, collectTabsets, collectTabsetsInRow, createHistory, createNodeId, createTabId, dashfooMachine, dashfooSchema, decideSnap, dimensionSchema, dispatch,
|
|
1282
|
+
export { DEFAULT_SNAP_THRESHOLD, DEFAULT_WEIGHT, HISTORY_LIMIT, actionSchema, canRedo, canUndo, collectRoots, collectTabsets, collectTabsetsInRow, createHistory, createNodeId, createTabId, dashfooMachine, dashfooSchema, decideSnap, dimensionSchema, dispatch, dockLocationSchema, dockZonePolygons, dragDockMachine, dropAction, edgeSchema, findAttributedNode, findDuplicateIds, findFloat, findRootContaining, findRow, findTab, findTabset, findTabsetParent, floatNode, floatNodeSchema, fromJSON, geometrySchema, getFirstTabset, globalAttributesSchema, jsonValueSchema, model, mutableNodeAttrsSchema, normalize, orientationSchema, parseModel, redo, reducer, resolveDockTarget, resolveSnapGrid, resolveSnapTargets, row, rowNodeSchema, settleSnap, snapEnabled, snapSchema, snapSizes, splitEdge, stackModel, tab, tabNodeSchema, tabset, tabsetNodeObjectSchema, tabsetNodeSchema, toJSON, undo, unitSchema, zoneRect };
|
|
1273
1283
|
//# sourceMappingURL=index.js.map
|