@dashfoo/core 1.0.0 → 1.1.0
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 +19 -12
- package/dist/index.d.ts +197 -85
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +267 -259
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -487,13 +460,6 @@ const closestEdge = (d) => {
|
|
|
487
460
|
if (min === d.top) return "top";
|
|
488
461
|
return "bottom";
|
|
489
462
|
};
|
|
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
463
|
const splitEdge = (location) => {
|
|
498
464
|
switch (location) {
|
|
499
465
|
case "center": return;
|
|
@@ -506,13 +472,10 @@ const splitEdge = (location) => {
|
|
|
506
472
|
};
|
|
507
473
|
const resolveDockTarget = (pointer, rect, opts) => {
|
|
508
474
|
const band = opts?.bandFraction ?? DEFAULT_BAND_FRACTION;
|
|
509
|
-
if (rect.width <= 0 || rect.height <= 0) return
|
|
475
|
+
if (rect.width <= 0 || rect.height <= 0) return "center";
|
|
510
476
|
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
|
-
};
|
|
477
|
+
if (Math.min(distances.left, distances.right, distances.top, distances.bottom) > band) return "center";
|
|
478
|
+
return `split-${closestEdge(distances)}`;
|
|
516
479
|
};
|
|
517
480
|
const dockZonePolygons = (rect, opts) => {
|
|
518
481
|
const { height: h, width: w, x, y } = rect;
|
|
@@ -661,21 +624,20 @@ const resolveSnapTargets = (config, panelCount) => {
|
|
|
661
624
|
}
|
|
662
625
|
return [...targets].toSorted((a, b) => a - b);
|
|
663
626
|
};
|
|
664
|
-
const
|
|
627
|
+
const resolveSnapGrid = (config, panelCount) => ({
|
|
628
|
+
targets: config === null ? [] : resolveSnapTargets(config, panelCount),
|
|
629
|
+
threshold: config?.threshold ?? 4
|
|
630
|
+
});
|
|
631
|
+
const snapEnabled = (grid) => grid.targets.length > 0;
|
|
665
632
|
const nearestTarget = (position, targets) => targets.reduce((best, target) => {
|
|
666
633
|
if (best === void 0 || Math.abs(target - position) < Math.abs(best - position)) return target;
|
|
667
634
|
return best;
|
|
668
635
|
}, void 0);
|
|
669
|
-
const snapSizes = (sizes, boundaryIndex,
|
|
670
|
-
const
|
|
636
|
+
const snapSizes = (sizes, boundaryIndex, grid) => {
|
|
637
|
+
const { targets, threshold } = grid;
|
|
671
638
|
const left = sizes[boundaryIndex];
|
|
672
639
|
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 {
|
|
640
|
+
if (boundaryIndex < 0 || left === void 0 || right === void 0 || targets.length === 0) return {
|
|
679
641
|
sizes,
|
|
680
642
|
snapped: false
|
|
681
643
|
};
|
|
@@ -699,28 +661,25 @@ const snapSizes = (sizes, boundaryIndex, config) => {
|
|
|
699
661
|
snapped: true
|
|
700
662
|
};
|
|
701
663
|
};
|
|
702
|
-
const decideSnap = (sizes, boundaryIndex,
|
|
703
|
-
if (boundaryIndex === null ||
|
|
704
|
-
const { sizes: snappedSizes, snapped } = snapSizes(sizes, boundaryIndex,
|
|
664
|
+
const decideSnap = (sizes, boundaryIndex, grid) => {
|
|
665
|
+
if (boundaryIndex === null || !snapEnabled(grid)) return { kind: "inactive" };
|
|
666
|
+
const { sizes: snappedSizes, snapped } = snapSizes(sizes, boundaryIndex, grid);
|
|
705
667
|
return snapped ? {
|
|
706
668
|
kind: "engage",
|
|
707
669
|
sizes: snappedSizes
|
|
708
670
|
} : { kind: "clear" };
|
|
709
671
|
};
|
|
710
|
-
const settleSnap = (sizes, boundaryIndex,
|
|
711
|
-
const decision = decideSnap(sizes, boundaryIndex,
|
|
672
|
+
const settleSnap = (sizes, boundaryIndex, grid) => {
|
|
673
|
+
const decision = decideSnap(sizes, boundaryIndex, grid);
|
|
712
674
|
return decision.kind === "engage" ? decision.sizes : sizes;
|
|
713
675
|
};
|
|
714
676
|
|
|
715
677
|
//#endregion
|
|
716
678
|
//#region src/model/invariants.ts
|
|
717
679
|
/**
|
|
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.
|
|
680
|
+
* The survivor takes over the collapsed row's slot, so it has to take the slot's
|
|
681
|
+
* sizing too: dropping it widens a pinned sidebar, permanently, since this runs
|
|
682
|
+
* on every persist. `snap` is row-only, so it travels only to a row survivor.
|
|
724
683
|
*/
|
|
725
684
|
const inheritSlot = (row, survivor) => {
|
|
726
685
|
const sized = {
|
|
@@ -735,14 +694,18 @@ const inheritSlot = (row, survivor) => {
|
|
|
735
694
|
};
|
|
736
695
|
return sized;
|
|
737
696
|
};
|
|
738
|
-
|
|
697
|
+
/** Records the id of every tabset it keeps, in document order. */
|
|
698
|
+
const normalizeRowChildren = (children, keptTabsetIds) => {
|
|
739
699
|
const out = [];
|
|
740
700
|
for (const child of children) {
|
|
741
701
|
if (child.type === "tabset") {
|
|
742
|
-
if (child.children.length > 0)
|
|
702
|
+
if (child.children.length > 0) {
|
|
703
|
+
out.push(child);
|
|
704
|
+
keptTabsetIds.push(child.id);
|
|
705
|
+
}
|
|
743
706
|
continue;
|
|
744
707
|
}
|
|
745
|
-
const grandchildren = normalizeRowChildren(child.children);
|
|
708
|
+
const grandchildren = normalizeRowChildren(child.children, keptTabsetIds);
|
|
746
709
|
if (grandchildren.length === 0) continue;
|
|
747
710
|
if (grandchildren.length === 1) {
|
|
748
711
|
const only = grandchildren[0];
|
|
@@ -756,9 +719,8 @@ const normalizeRowChildren = (children) => {
|
|
|
756
719
|
}
|
|
757
720
|
return out;
|
|
758
721
|
};
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
let children = normalizeRowChildren(root.children);
|
|
722
|
+
const normalizeLayout = (root, keptTabsetIds) => {
|
|
723
|
+
let children = normalizeRowChildren(root.children, keptTabsetIds);
|
|
762
724
|
let orientation = root.orientation;
|
|
763
725
|
let inner = children[0];
|
|
764
726
|
while (children.length === 1 && inner?.type === "row") {
|
|
@@ -773,53 +735,67 @@ const normalizeLayout = (root) => {
|
|
|
773
735
|
};
|
|
774
736
|
};
|
|
775
737
|
const normalize = (model) => {
|
|
776
|
-
const
|
|
738
|
+
const mainTabsetIds = [];
|
|
739
|
+
const layout = normalizeLayout(model.layout, mainTabsetIds);
|
|
777
740
|
const floats = [];
|
|
741
|
+
const floatTabsetIds = [];
|
|
778
742
|
for (const float of model.floats) {
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
743
|
+
const ownTabsetIds = [];
|
|
744
|
+
const floatLayout = normalizeLayout(float.layout, ownTabsetIds);
|
|
745
|
+
if (floatLayout.children.length > 0) {
|
|
746
|
+
floats.push({
|
|
747
|
+
...float,
|
|
748
|
+
layout: floatLayout
|
|
749
|
+
});
|
|
750
|
+
floatTabsetIds.push(...ownTabsetIds);
|
|
751
|
+
}
|
|
784
752
|
}
|
|
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;
|
|
753
|
+
const mainIds = new Set(mainTabsetIds);
|
|
754
|
+
const liveIds = /* @__PURE__ */ new Set([...mainTabsetIds, ...floatTabsetIds]);
|
|
755
|
+
const firstTabsetId = mainTabsetIds[0] ?? floatTabsetIds[0];
|
|
756
|
+
const activeTabsetId = model.activeTabsetId !== void 0 && liveIds.has(model.activeTabsetId) ? model.activeTabsetId : firstTabsetId;
|
|
757
|
+
const maximizedTabsetId = model.maximizedTabsetId !== void 0 && mainIds.has(model.maximizedTabsetId) ? model.maximizedTabsetId : void 0;
|
|
795
758
|
return {
|
|
796
|
-
...
|
|
759
|
+
...model,
|
|
797
760
|
activeTabsetId,
|
|
761
|
+
floats,
|
|
762
|
+
layout,
|
|
798
763
|
maximizedTabsetId
|
|
799
764
|
};
|
|
800
765
|
};
|
|
801
766
|
|
|
802
767
|
//#endregion
|
|
803
768
|
//#region src/state/surgery.ts
|
|
804
|
-
const removeTabsetReturning = (
|
|
805
|
-
const found = findTabsetParent(
|
|
769
|
+
const removeTabsetReturning = (model, tabsetId) => {
|
|
770
|
+
const found = findTabsetParent(model, tabsetId);
|
|
806
771
|
if (!found) return;
|
|
807
772
|
const [removed] = found.parent.children.splice(found.index, 1);
|
|
808
773
|
return removed?.type === "tabset" ? removed : void 0;
|
|
809
774
|
};
|
|
810
|
-
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
775
|
+
/** Total, so a sixth `DockLocation` is a compile error here rather than a wrong default. */
|
|
776
|
+
const SPLIT_PLACEMENT = {
|
|
777
|
+
"split-bottom": {
|
|
778
|
+
before: false,
|
|
779
|
+
orientation: "column"
|
|
780
|
+
},
|
|
781
|
+
"split-left": {
|
|
782
|
+
before: true,
|
|
783
|
+
orientation: "row"
|
|
784
|
+
},
|
|
785
|
+
"split-right": {
|
|
786
|
+
before: false,
|
|
787
|
+
orientation: "row"
|
|
788
|
+
},
|
|
789
|
+
"split-top": {
|
|
790
|
+
before: true,
|
|
791
|
+
orientation: "column"
|
|
792
|
+
}
|
|
817
793
|
};
|
|
818
794
|
const placeBesideTarget = (draft, placed, targetId, location) => {
|
|
819
795
|
const targetTabset = findTabset(draft, targetId);
|
|
820
|
-
const found = findTabsetParent(
|
|
821
|
-
if (!targetTabset || !found) return;
|
|
822
|
-
const { before, orientation } =
|
|
796
|
+
const found = findTabsetParent(draft, targetId);
|
|
797
|
+
if (!targetTabset || !found) return false;
|
|
798
|
+
const { before, orientation } = SPLIT_PLACEMENT[location];
|
|
823
799
|
if (found.parent.orientation === orientation) {
|
|
824
800
|
const half = targetTabset.weight / 2;
|
|
825
801
|
targetTabset.weight = half;
|
|
@@ -838,16 +814,17 @@ const placeBesideTarget = (draft, placed, targetId, location) => {
|
|
|
838
814
|
found.parent.children.splice(found.index, 1, newRow);
|
|
839
815
|
}
|
|
840
816
|
draft.activeTabsetId = placed.id;
|
|
817
|
+
return true;
|
|
841
818
|
};
|
|
842
819
|
const insertTab = (draft, tab, target) => {
|
|
843
820
|
const { id: targetId, index, location } = target;
|
|
844
821
|
const targetTabset = findTabset(draft, targetId);
|
|
845
|
-
if (!targetTabset) return;
|
|
822
|
+
if (!targetTabset) return false;
|
|
846
823
|
if (location === "center") {
|
|
847
824
|
const at = Math.min(Math.max(index ?? targetTabset.children.length, 0), targetTabset.children.length);
|
|
848
825
|
targetTabset.children.splice(at, 0, tab);
|
|
849
826
|
targetTabset.selected = at;
|
|
850
|
-
return;
|
|
827
|
+
return true;
|
|
851
828
|
}
|
|
852
829
|
const newTabset = {
|
|
853
830
|
children: [tab],
|
|
@@ -856,13 +833,9 @@ const insertTab = (draft, tab, target) => {
|
|
|
856
833
|
type: "tabset",
|
|
857
834
|
weight: 1
|
|
858
835
|
};
|
|
859
|
-
placeBesideTarget(draft, newTabset, targetId, location);
|
|
836
|
+
return placeBesideTarget(draft, newTabset, targetId, location);
|
|
860
837
|
};
|
|
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
|
-
*/
|
|
838
|
+
/** Re-clamps here rather than leaving the whole tree to be re-walked afterwards. */
|
|
866
839
|
const detachTab = (container, index) => {
|
|
867
840
|
const removed = container.children.splice(index, 1).at(0);
|
|
868
841
|
container.selected = clampSelected(container.children.length, container.selected);
|
|
@@ -924,10 +897,10 @@ const pushFloat = (draft, layout, geometry, floatId) => {
|
|
|
924
897
|
if (first) draft.activeTabsetId = first.id;
|
|
925
898
|
};
|
|
926
899
|
const floatTabsetById = (draft, tabsetId, geometry, floatId) => {
|
|
927
|
-
const
|
|
928
|
-
if (!
|
|
929
|
-
|
|
930
|
-
|
|
900
|
+
const detached = removeTabsetReturning(draft, tabsetId);
|
|
901
|
+
if (!detached) return false;
|
|
902
|
+
pushFloat(draft, wrapTabsetInLayout(detached), geometry, floatId);
|
|
903
|
+
return true;
|
|
931
904
|
};
|
|
932
905
|
const resolveMainTarget = (draft, targetId) => {
|
|
933
906
|
const mainTabsets = [];
|
|
@@ -943,36 +916,35 @@ const resolveMainTarget = (draft, targetId) => {
|
|
|
943
916
|
return mainTabsets[0];
|
|
944
917
|
};
|
|
945
918
|
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;
|
|
919
|
+
const index = draft.floats.findIndex((float) => float.id === floatId);
|
|
920
|
+
const float = draft.floats.at(index);
|
|
921
|
+
if (index === -1 || !float) return false;
|
|
951
922
|
const tabsets = [];
|
|
952
923
|
collectTabsetsInRow(float.layout, tabsets);
|
|
953
924
|
const tabs = tabsets.flatMap((tabset) => tabset.children);
|
|
954
|
-
if (tabs.length === 0) return;
|
|
925
|
+
if (tabs.length === 0) return false;
|
|
955
926
|
const leadTabset = tabsets.at(0);
|
|
956
927
|
const selectedOffset = leadTabset ? clampSelected(leadTabset.children.length, leadTabset.selected) : 0;
|
|
957
928
|
const target = resolveMainTarget(draft, targetId);
|
|
929
|
+
draft.floats.splice(index, 1);
|
|
958
930
|
if (!target) {
|
|
959
931
|
draft.layout = float.layout;
|
|
960
932
|
draft.activeTabsetId = leadTabset?.id;
|
|
961
|
-
return;
|
|
933
|
+
return true;
|
|
962
934
|
}
|
|
963
935
|
if (location === "center") {
|
|
964
936
|
mergeTabsInto(target, tabs, selectedOffset);
|
|
965
937
|
draft.activeTabsetId = target.id;
|
|
966
|
-
return;
|
|
938
|
+
return true;
|
|
967
939
|
}
|
|
968
|
-
const
|
|
969
|
-
placeBesideTarget(draft, tabsets.length === 1 && leadTabset ? leadTabset : {
|
|
940
|
+
const placed = tabsets.length === 1 && leadTabset ? leadTabset : {
|
|
970
941
|
children: tabs,
|
|
971
942
|
id: createNodeId("tabset"),
|
|
972
943
|
selected: selectedOffset,
|
|
973
944
|
type: "tabset",
|
|
974
945
|
weight: 1
|
|
975
|
-
}
|
|
946
|
+
};
|
|
947
|
+
return placeBesideTarget(draft, placed, target.id, location ?? "split-right");
|
|
976
948
|
};
|
|
977
949
|
|
|
978
950
|
//#endregion
|
|
@@ -991,127 +963,138 @@ const parseNodeAttrs = (node, attrs) => {
|
|
|
991
963
|
default: return assertNever(node);
|
|
992
964
|
}
|
|
993
965
|
};
|
|
966
|
+
const sameGeometry = (a, b) => a.height === b.height && a.left === b.left && a.top === b.top && a.width === b.width;
|
|
967
|
+
const alreadyApplied = (target, attrs) => {
|
|
968
|
+
const current = new Map(Object.entries(target));
|
|
969
|
+
return Object.entries(attrs).every(([key, value]) => Object.is(current.get(key), value));
|
|
970
|
+
};
|
|
971
|
+
/** Reports whether it changed anything, so `history.dispatch` can skip a no-op. */
|
|
994
972
|
const applyAction = (draft, action) => {
|
|
995
973
|
switch (action.type) {
|
|
996
|
-
case "addNode":
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
});
|
|
1002
|
-
return;
|
|
974
|
+
case "addNode": return insertTab(draft, action.tab, {
|
|
975
|
+
id: action.targetId,
|
|
976
|
+
index: action.index,
|
|
977
|
+
location: action.location
|
|
978
|
+
});
|
|
1003
979
|
case "adjustSplit": {
|
|
1004
|
-
const row = findRow(
|
|
1005
|
-
if (!row) return;
|
|
980
|
+
const row = findRow(draft, action.rowId);
|
|
981
|
+
if (!row) return false;
|
|
982
|
+
let changed = false;
|
|
1006
983
|
for (let index = 0; index < action.weights.length; index++) {
|
|
1007
984
|
const child = row.children.at(index);
|
|
1008
985
|
const weight = action.weights[index];
|
|
1009
|
-
if (!child || weight === void 0) continue;
|
|
986
|
+
if (!child || weight === void 0 || child.weight === weight) continue;
|
|
1010
987
|
child.weight = weight;
|
|
988
|
+
changed = true;
|
|
1011
989
|
}
|
|
1012
|
-
return;
|
|
990
|
+
return changed;
|
|
1013
991
|
}
|
|
1014
992
|
case "deleteTab": {
|
|
1015
993
|
const location = findTab(draft, action.tabId);
|
|
1016
|
-
if (location)
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
case "deleteTabset": {
|
|
1020
|
-
const root = findRootContaining(draft, action.tabsetId);
|
|
1021
|
-
if (root) removeTabset(root, action.tabsetId);
|
|
1022
|
-
return;
|
|
994
|
+
if (!location) return false;
|
|
995
|
+
detachTab(location.container, location.index);
|
|
996
|
+
return true;
|
|
1023
997
|
}
|
|
1024
|
-
case "
|
|
1025
|
-
|
|
1026
|
-
return;
|
|
998
|
+
case "deleteTabset": return removeTabsetReturning(draft, action.tabsetId) !== void 0;
|
|
999
|
+
case "dockFloat": return dockFloat(draft, action.floatId, action.targetId, action.location);
|
|
1027
1000
|
case "floatTab": {
|
|
1028
1001
|
const location = findTab(draft, action.tabId);
|
|
1029
|
-
if (!location) return;
|
|
1002
|
+
if (!location) return false;
|
|
1030
1003
|
const removed = detachTab(location.container, location.index);
|
|
1031
|
-
if (removed)
|
|
1032
|
-
|
|
1004
|
+
if (!removed) return false;
|
|
1005
|
+
pushFloat(draft, wrapTabInLayout(removed), action.geometry, action.floatId);
|
|
1006
|
+
return true;
|
|
1033
1007
|
}
|
|
1034
|
-
case "floatTabset":
|
|
1035
|
-
floatTabsetById(draft, action.tabsetId, action.geometry, action.floatId);
|
|
1036
|
-
return;
|
|
1008
|
+
case "floatTabset": return floatTabsetById(draft, action.tabsetId, action.geometry, action.floatId);
|
|
1037
1009
|
case "moveFloat": {
|
|
1038
1010
|
const float = findFloat(draft, action.floatId);
|
|
1039
|
-
if (float
|
|
1040
|
-
|
|
1011
|
+
if (!float || sameGeometry(float.geometry, action.geometry)) return false;
|
|
1012
|
+
float.geometry = action.geometry;
|
|
1013
|
+
return true;
|
|
1041
1014
|
}
|
|
1042
1015
|
case "moveNode": {
|
|
1043
1016
|
const source = findTab(draft, action.sourceId);
|
|
1044
|
-
if (!source) return;
|
|
1017
|
+
if (!source || !findTabset(draft, action.targetId)) return false;
|
|
1045
1018
|
const removed = detachTab(source.container, source.index);
|
|
1046
|
-
if (!removed) return;
|
|
1047
|
-
insertTab(draft, removed, {
|
|
1019
|
+
if (!removed) return false;
|
|
1020
|
+
return insertTab(draft, removed, {
|
|
1048
1021
|
id: action.targetId,
|
|
1049
1022
|
index: action.index,
|
|
1050
1023
|
location: action.location
|
|
1051
1024
|
});
|
|
1052
|
-
return;
|
|
1053
1025
|
}
|
|
1054
1026
|
case "moveTabset": {
|
|
1055
|
-
if (action.sourceId === action.targetId) return;
|
|
1027
|
+
if (action.sourceId === action.targetId) return false;
|
|
1056
1028
|
const source = findTabset(draft, action.sourceId);
|
|
1057
|
-
|
|
1029
|
+
const target = findTabset(draft, action.targetId);
|
|
1030
|
+
if (!source || !target) return false;
|
|
1058
1031
|
if (action.location === "center") {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const sourceRoot = findRootContaining(draft, action.sourceId);
|
|
1063
|
-
if (sourceRoot) removeTabset(sourceRoot, action.sourceId);
|
|
1064
|
-
}
|
|
1065
|
-
return;
|
|
1032
|
+
mergeTabsInto(target, source.children, clampSelected(source.children.length, source.selected));
|
|
1033
|
+
removeTabsetReturning(draft, action.sourceId);
|
|
1034
|
+
return true;
|
|
1066
1035
|
}
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
if (detached) placeBesideTarget(draft, detached, action.targetId, action.location);
|
|
1070
|
-
return;
|
|
1036
|
+
const detached = removeTabsetReturning(draft, action.sourceId);
|
|
1037
|
+
return detached ? placeBesideTarget(draft, detached, action.targetId, action.location) : false;
|
|
1071
1038
|
}
|
|
1072
1039
|
case "renameFloat": {
|
|
1073
1040
|
const float = findFloat(draft, action.floatId);
|
|
1074
|
-
if (float)
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1041
|
+
if (!float) return false;
|
|
1042
|
+
const others = draft.floats.filter((other) => other.id !== action.floatId);
|
|
1043
|
+
const name = uniqueFloatName(action.name, others);
|
|
1044
|
+
if (float.name === name) return false;
|
|
1045
|
+
float.name = name;
|
|
1046
|
+
return true;
|
|
1079
1047
|
}
|
|
1080
1048
|
case "renameTab": {
|
|
1081
1049
|
const location = findTab(draft, action.tabId);
|
|
1082
|
-
if (location
|
|
1083
|
-
|
|
1050
|
+
if (!location || location.tab.name === action.name) return false;
|
|
1051
|
+
location.tab.name = action.name;
|
|
1052
|
+
return true;
|
|
1084
1053
|
}
|
|
1085
1054
|
case "selectTab": {
|
|
1086
1055
|
const tabset = findTabset(draft, action.tabsetId);
|
|
1087
|
-
if (tabset)
|
|
1088
|
-
|
|
1056
|
+
if (!tabset) return false;
|
|
1057
|
+
const selected = clampSelected(tabset.children.length, action.index);
|
|
1058
|
+
if (tabset.selected === selected) return false;
|
|
1059
|
+
tabset.selected = selected;
|
|
1060
|
+
return true;
|
|
1089
1061
|
}
|
|
1090
1062
|
case "setActiveTabset":
|
|
1091
|
-
if (findTabset(draft, action.tabsetId))
|
|
1092
|
-
|
|
1063
|
+
if (draft.activeTabsetId === action.tabsetId || !findTabset(draft, action.tabsetId)) return false;
|
|
1064
|
+
draft.activeTabsetId = action.tabsetId;
|
|
1065
|
+
return true;
|
|
1093
1066
|
case "setFloatMinimized": {
|
|
1094
1067
|
const float = findFloat(draft, action.floatId);
|
|
1095
|
-
if (float
|
|
1096
|
-
|
|
1068
|
+
if (!float || (float.minimized ?? false) === action.minimized) return false;
|
|
1069
|
+
float.minimized = action.minimized;
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
case "setMaximizedTabset": {
|
|
1073
|
+
const next = action.tabsetId ?? void 0;
|
|
1074
|
+
if (draft.maximizedTabsetId === next) return false;
|
|
1075
|
+
draft.maximizedTabsetId = next;
|
|
1076
|
+
return true;
|
|
1097
1077
|
}
|
|
1098
|
-
case "setMaximizedTabset":
|
|
1099
|
-
draft.maximizedTabsetId = action.tabsetId ?? void 0;
|
|
1100
|
-
return;
|
|
1101
1078
|
case "updateGlobalAttributes":
|
|
1079
|
+
if (alreadyApplied(draft.global, action.attrs)) return false;
|
|
1102
1080
|
Object.assign(draft.global, action.attrs);
|
|
1103
|
-
return;
|
|
1081
|
+
return true;
|
|
1104
1082
|
case "updateNodeAttributes": {
|
|
1105
1083
|
const node = findAttributedNode(draft, action.nodeId);
|
|
1106
|
-
if (node)
|
|
1107
|
-
|
|
1084
|
+
if (!node) return false;
|
|
1085
|
+
const parsed = parseNodeAttrs(node, action.attrs);
|
|
1086
|
+
if (alreadyApplied(node, parsed)) return false;
|
|
1087
|
+
Object.assign(node, parsed);
|
|
1088
|
+
return true;
|
|
1108
1089
|
}
|
|
1109
|
-
default: assertNever(action);
|
|
1090
|
+
default: return assertNever(action);
|
|
1110
1091
|
}
|
|
1111
1092
|
};
|
|
1093
|
+
/** Identity-equal output means "no edit". A rejected action skips `normalize`, so
|
|
1094
|
+
* pass a model that has already been through it. */
|
|
1112
1095
|
const reducer = (model, action) => {
|
|
1113
1096
|
const draft = structuredClone(model);
|
|
1114
|
-
applyAction(draft, action);
|
|
1097
|
+
if (!applyAction(draft, action)) return model;
|
|
1115
1098
|
return normalize(draft);
|
|
1116
1099
|
};
|
|
1117
1100
|
|
|
@@ -1125,8 +1108,14 @@ const createHistory = (present) => ({
|
|
|
1125
1108
|
});
|
|
1126
1109
|
const canUndo = (history) => history.past.length > 0;
|
|
1127
1110
|
const canRedo = (history) => history.future.length > 0;
|
|
1111
|
+
/**
|
|
1112
|
+
* An action that changed nothing is not an edit: recording it would spend an
|
|
1113
|
+
* undo slot and clear a redo stack the user just built. Not coalescing (ADR
|
|
1114
|
+
* 0002): two distinct edits are still two entries.
|
|
1115
|
+
*/
|
|
1128
1116
|
const dispatch = (history, action) => {
|
|
1129
1117
|
const present = reducer(history.present, action);
|
|
1118
|
+
if (Object.is(present, history.present)) return history;
|
|
1130
1119
|
return {
|
|
1131
1120
|
future: [],
|
|
1132
1121
|
past: [...history.past, history.present].slice(-100),
|
|
@@ -1172,25 +1161,50 @@ const dashfooMachine = setup({ types: {
|
|
|
1172
1161
|
|
|
1173
1162
|
//#endregion
|
|
1174
1163
|
//#region src/machines/drag-dock-machine.ts
|
|
1175
|
-
|
|
1176
|
-
|
|
1164
|
+
/** One definition of both "is this drop valid" and "what does it do", so the guard and the emit agree. */
|
|
1165
|
+
const dropAction = (drag) => {
|
|
1166
|
+
if (drag.kind !== "dragging" || drag.drop === null) return null;
|
|
1167
|
+
const { intent, scope } = drag.drop;
|
|
1168
|
+
const { subject } = drag;
|
|
1169
|
+
if (subject.kind === "external") return {
|
|
1170
|
+
action: {
|
|
1171
|
+
index: intent.index,
|
|
1172
|
+
location: intent.location,
|
|
1173
|
+
tab: subject.tab,
|
|
1174
|
+
targetId: intent.targetId,
|
|
1175
|
+
type: "addNode"
|
|
1176
|
+
},
|
|
1177
|
+
scope
|
|
1178
|
+
};
|
|
1179
|
+
if (subject.kind === "tabset") return {
|
|
1180
|
+
action: {
|
|
1181
|
+
location: intent.location,
|
|
1182
|
+
sourceId: subject.id,
|
|
1183
|
+
targetId: intent.targetId,
|
|
1184
|
+
type: "moveTabset"
|
|
1185
|
+
},
|
|
1186
|
+
scope
|
|
1187
|
+
};
|
|
1177
1188
|
return {
|
|
1178
|
-
|
|
1179
|
-
|
|
1189
|
+
action: {
|
|
1190
|
+
index: intent.index,
|
|
1191
|
+
location: intent.location,
|
|
1192
|
+
sourceId: subject.id,
|
|
1193
|
+
targetId: intent.targetId,
|
|
1194
|
+
type: "moveNode"
|
|
1195
|
+
},
|
|
1196
|
+
scope
|
|
1180
1197
|
};
|
|
1181
1198
|
};
|
|
1182
1199
|
const dragDockMachine = setup({
|
|
1183
|
-
guards: { hasValidDrop: ({ context }) => context.
|
|
1200
|
+
guards: { hasValidDrop: ({ context }) => dropAction(context.drag) !== null },
|
|
1184
1201
|
types: {
|
|
1185
1202
|
context: {},
|
|
1186
1203
|
emitted: {},
|
|
1187
1204
|
events: {}
|
|
1188
1205
|
}
|
|
1189
1206
|
}).createMachine({
|
|
1190
|
-
context: {
|
|
1191
|
-
intent: null,
|
|
1192
|
-
subject: null
|
|
1193
|
-
},
|
|
1207
|
+
context: { drag: { kind: "idle" } },
|
|
1194
1208
|
id: "dragDock",
|
|
1195
1209
|
initial: "idle",
|
|
1196
1210
|
states: {
|
|
@@ -1198,36 +1212,30 @@ const dragDockMachine = setup({
|
|
|
1198
1212
|
CANCEL: { target: "idle" },
|
|
1199
1213
|
DROP: [{
|
|
1200
1214
|
actions: emit(({ context }) => {
|
|
1201
|
-
const
|
|
1215
|
+
const resolved = dropAction(context.drag);
|
|
1216
|
+
if (resolved === null) throw new Error("drop requires a subject and a resolved drop target");
|
|
1202
1217
|
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
|
-
},
|
|
1218
|
+
action: resolved.action,
|
|
1219
|
+
scope: resolved.scope,
|
|
1216
1220
|
type: "COMMIT"
|
|
1217
1221
|
};
|
|
1218
1222
|
}),
|
|
1219
1223
|
guard: "hasValidDrop",
|
|
1220
1224
|
target: "idle"
|
|
1221
1225
|
}, { target: "idle" }],
|
|
1222
|
-
OVER: { actions: assign({
|
|
1226
|
+
OVER: { actions: assign({ drag: ({ context, event }) => context.drag.kind === "dragging" ? {
|
|
1227
|
+
...context.drag,
|
|
1228
|
+
drop: event.drop
|
|
1229
|
+
} : context.drag }) }
|
|
1223
1230
|
} },
|
|
1224
1231
|
idle: {
|
|
1225
|
-
entry: assign({
|
|
1226
|
-
intent: null,
|
|
1227
|
-
subject: null
|
|
1228
|
-
}),
|
|
1232
|
+
entry: assign({ drag: { kind: "idle" } }),
|
|
1229
1233
|
on: { START: {
|
|
1230
|
-
actions: assign({
|
|
1234
|
+
actions: assign({ drag: ({ event }) => ({
|
|
1235
|
+
drop: null,
|
|
1236
|
+
kind: "dragging",
|
|
1237
|
+
subject: event.subject
|
|
1238
|
+
}) }),
|
|
1231
1239
|
target: "dragging"
|
|
1232
1240
|
} }
|
|
1233
1241
|
}
|
|
@@ -1269,5 +1277,5 @@ const stackModel = (model, orientation = "column") => {
|
|
|
1269
1277
|
};
|
|
1270
1278
|
|
|
1271
1279
|
//#endregion
|
|
1272
|
-
export { DEFAULT_SNAP_THRESHOLD, DEFAULT_WEIGHT, HISTORY_LIMIT, actionSchema, canRedo, canUndo, collectRoots, collectTabsets, collectTabsetsInRow, createHistory, createNodeId, createTabId, dashfooMachine, dashfooSchema, decideSnap, dimensionSchema, dispatch,
|
|
1280
|
+
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
1281
|
//# sourceMappingURL=index.js.map
|