@dashfoo/core 0.3.0 → 0.5.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 CHANGED
@@ -29,19 +29,24 @@ type Dashfoo = {
29
29
  layout: RowNode; // the root is always a row
30
30
  activeTabsetId?: string;
31
31
  maximizedTabsetId?: string;
32
+ floats?: FloatNode[]; // floating panels, each its own layout subtree
32
33
  };
33
34
  ```
34
35
 
35
- The tree has three node kinds, each discriminated by `type`:
36
+ The tree has three node kinds, each discriminated by `type`, plus an optional
37
+ floating-panel node:
36
38
 
37
- | Node | `type` | Holds |
38
- | ------------ | ---------- | ----------------------------------------------------------------------- |
39
- | `RowNode` | `"row"` | `children` (rows or tabsets), `orientation` (`row`/`column`), `weight?` |
40
- | `TabsetNode` | `"tabset"` | `children` (tabs), `selected` index, optional `min`/`max`/`weight` |
41
- | `TabNode` | `"tab"` | `component`, `name`, `id`, optional `config` + `enable*` flags |
39
+ | Node | `type` | Holds |
40
+ | ------------ | ---------- | ---------------------------------------------------------------------------------------- |
41
+ | `RowNode` | `"row"` | `children` (rows or tabsets), `orientation` (`row`/`column`), `weight?` |
42
+ | `TabsetNode` | `"tabset"` | `children` (tabs), `selected` index, optional `min`/`max`/`weight` |
43
+ | `TabNode` | `"tab"` | `component`, `name`, `id`, optional `config` + `enable*` flags |
44
+ | `FloatNode` | `"float"` | a floating panel: its own `layout` (`RowNode`), `geometry`, optional `name`, `minimized` |
42
45
 
43
46
  Rows nest (a row's child can be another row), which is how arbitrary tiled splits
44
- are represented. `min`/`max` are `Dimension` values (`{ unit, value }`) where
47
+ are represented. `floats` carry floating panels (see "Floating panels" below): each
48
+ owns a full `RowNode` layout, so traversals and actions treat a float as a second
49
+ root alongside `layout`. `min`/`max` are `Dimension` values (`{ unit, value }`) where
45
50
  `unit` is one of `px`, `%`, `em`, `rem`, `vh`, `vw`. `global.tabSetMinSize` is
46
51
  the default tabset minimum size in pixels for renderers that honor it; the React
47
52
  adapter falls back to `320px` when it is omitted.
@@ -101,6 +106,12 @@ untrusted payloads against `actionSchema` before dispatch.
101
106
  | `deleteTab` | Remove a tab |
102
107
  | `deleteTabset` | Remove a whole tabset |
103
108
  | `adjustSplit` | Set the `weights` of a row's children (splitter drag) |
109
+ | `floatTab` | Float a tab out into a new floating panel |
110
+ | `floatTabset` | Float a whole tabset (with its tabs) into a new floating panel |
111
+ | `dockFloat` | Dock a float back as its own panel (or `center`-merge its tabs) |
112
+ | `moveFloat` | Update a floating panel's stored rect (drag/resize) |
113
+ | `setFloatMinimized` | Collapse a float to a chip, or restore it |
114
+ | `renameFloat` | Rename a floating panel's window title |
104
115
  | `updateNodeAttributes` | Patch mutable attrs on a tab / tabset / row |
105
116
  | `updateGlobalAttributes` | Patch the `global` block |
106
117
 
@@ -156,6 +167,7 @@ tree canonical so downstream code never has to defend against degenerate shapes:
156
167
  - clamps every `selected` index into range
157
168
  - forces `activeTabsetId` / `maximizedTabsetId` to point at a tabset that exists
158
169
  (falling back to the first tabset, or clearing)
170
+ - heals each floating panel's own layout and drops a float once it empties
159
171
 
160
172
  `normalize` is exported on its own if you build a model by hand and want it
161
173
  canonicalized without dispatching an action.
@@ -165,18 +177,46 @@ canonicalized without dispatching an action.
165
177
  Read-only lookups over a model, all exported:
166
178
 
167
179
  ```ts
168
- collectTabsets(model): Array<TabsetNode>; // depth-first, layout only
180
+ collectRoots(model): Array<RowNode>; // main layout + each float's layout
181
+ collectTabsets(model): Array<TabsetNode>; // depth-first, across all roots
169
182
  getFirstTabset(model): TabsetNode | undefined;
170
183
  findTabset(model, tabsetId): TabsetNode | undefined;
171
184
  findTab(model, tabId): TabLocation | undefined; // searches tabsets
172
- findRow(row, rowId): RowNode | undefined; // pass model.layout as the root
185
+ findFloat(model, floatId): FloatNode | undefined;
186
+ findRow(row, rowId): RowNode | undefined; // pass a root (model.layout or a float's layout)
187
+ findRootContaining(model, nodeId): RowNode | undefined; // which root holds a node
173
188
  findAttributedNode(model, id): AttributedNode | undefined; // row, tabset, or tab
174
189
  findTabsetParent(row, tabsetId): { index: number; parent: RowNode } | undefined;
175
190
  findDuplicateIds(model): Array<string>; // ids used more than once
176
191
  ```
177
192
 
178
193
  `findTab` returns `{ container, index, tab }` so a caller knows where the tab
179
- lives (a tabset in the layout).
194
+ lives (a tabset in some root). Lookups span every root, so a floated tab is as
195
+ reachable as a docked one; `findRootContaining` tells you which root (main layout
196
+ or a float) a node lives in.
197
+
198
+ ## Floating panels
199
+
200
+ A panel can float out into a movable, resizable overlay (the React layer renders
201
+ it in-app — see `@dashfoo/react`). Floats are first-class model nodes
202
+ (`Dashfoo.floats`), each owning a full `RowNode` layout, so they serialize and
203
+ self-heal like the main tree:
204
+
205
+ ```ts
206
+ import { reducer } from "@dashfoo/core";
207
+
208
+ // Float the whole tabset "left" (the React layer supplies the in-app geometry;
209
+ // omit it for a default rect).
210
+ const floated = reducer(model, { type: "floatTabset", tabsetId: "left" });
211
+ floated.floats; // [{ type: "float", id, layout: RowNode, geometry }]
212
+
213
+ // Dock it back into the active main tabset (center) and drop the float.
214
+ const docked = reducer(floated, { type: "dockFloat", floatId: floated.floats![0].id });
215
+ ```
216
+
217
+ `normalize` runs over each float's layout too and drops a float once its last
218
+ panel leaves. The `floatNode(layout, geometry, opts?)` builder constructs one,
219
+ and `model(layout, { floats })` seeds them.
180
220
 
181
221
  ## Geometry
182
222
 
@@ -204,6 +244,29 @@ matching inner corner. The two functions share the band default, so a map
204
244
  painted from the polygons always agrees with the live hit-test — use it to
205
245
  visualize drop zones or to build custom drop indicators.
206
246
 
247
+ ## Snapping
248
+
249
+ Pure math for magnetic split-resize snapping, over ordered percentage arrays so
250
+ it stays framework-free; the React adapter maps rrp's id-keyed layout to/from
251
+ these arrays and supplies the dragged boundary index.
252
+
253
+ ```ts
254
+ resolveSnapTargets(config: SnapConfig, panelCount: number): number[]; // grid positions inside 0..100
255
+ snapSizes(sizes: number[], boundaryIndex: number, config: SnapConfig): { sizes: number[]; snapped: boolean };
256
+ snapEnabled(config: SnapConfig | null): boolean;
257
+ ```
258
+
259
+ `resolveSnapTargets` builds the grid for a row from its config and panel count: the
260
+ union of the `step` grid (multiples of a fixed percent) and the `divisions` grid
261
+ (even splits — multiples of `100/d`, where `d` is the number or, for `"panels"`,
262
+ the panel count). `snapSizes` snaps the boundary between panel `boundaryIndex` and
263
+ the next onto the nearest target within `threshold` (default `4`), moving only that
264
+ pair so siblings keep their size. It is a no-op when the grid is empty, the index
265
+ is out of range, or the correction would drive a panel negative. `snapEnabled`
266
+ reports whether a config produces any grid. `SnapConfig` is the
267
+ `{ step?, divisions?, threshold? }` shape carried by `global.snap` and
268
+ `RowNode.snap`.
269
+
207
270
  ## History (undo / redo)
208
271
 
209
272
  A small `past` / `present` / `future` structure that wraps the reducer. `present`
@@ -285,14 +348,14 @@ layout, carrying the `TabNode` to insert), which the React layer forwards to
285
348
  ## Public exports
286
349
 
287
350
  `schema` — `dashfooSchema`, `rowNodeSchema`, `tabsetNodeSchema`, `tabNodeSchema`,
288
- `dimensionSchema`, `edgeSchema`, `unitSchema`,
289
- `orientationSchema`, `globalAttributesSchema`,
351
+ `floatNodeSchema`, `geometrySchema`, `dimensionSchema`, `snapSchema`, `edgeSchema`,
352
+ `unitSchema`, `orientationSchema`, `globalAttributesSchema`,
290
353
  `jsonValueSchema`; types `Dashfoo`, `RowNode`, `TabsetNode`, `TabNode`,
291
- `Dimension`, `Edge`, `Unit`, `Orientation`,
354
+ `FloatNode`, `Geometry`, `Dimension`, `SnapConfig`, `Edge`, `Unit`, `Orientation`,
292
355
  `GlobalAttributes`, `Node`, `Json`.
293
356
 
294
- `builders` — `model`, `row`, `tabset`, `tab`; option types `ModelOptions`,
295
- `RowOptions`, `TabsetOptions`, `TabOptions`.
357
+ `builders` — `model`, `row`, `tabset`, `tab`, `floatNode`; option types
358
+ `ModelOptions`, `RowOptions`, `TabsetOptions`, `TabOptions`, `FloatOptions`.
296
359
 
297
360
  `ids` — `createNodeId`, `createTabId`.
298
361
 
@@ -301,13 +364,17 @@ layout, carrying the `TabNode` to insert), which the React layer forwards to
301
364
 
302
365
  `reducer` — `reducer`. `invariants` — `normalize`.
303
366
 
304
- `tree` — `collectTabsets`, `getFirstTabset`, `findTabset`, `findTab`, `findRow`,
305
- `findAttributedNode`, `findTabsetParent`, `findDuplicateIds`;
367
+ `tree` — `collectRoots`, `collectTabsets`, `collectTabsetsInRow`, `getFirstTabset`,
368
+ `findTabset`, `findTab`, `findFloat`, `findRow`, `findRootContaining`, `findAttributedNode`,
369
+ `findTabsetParent`, `findDuplicateIds`;
306
370
  types `AttributedNode`, `TabContainer`, `TabLocation`.
307
371
 
308
372
  `geometry` — `resolveDockTarget`, `dockZonePolygons`, `zoneRect`; types
309
373
  `DockTarget`, `DockZone`, `BandOptions`, `Point`, `Rect`.
310
374
 
375
+ `snap` — `resolveSnapTargets`, `snapSizes`, `snapEnabled`, `DEFAULT_SNAP_THRESHOLD`;
376
+ type `SnapResult`.
377
+
311
378
  `history` — `createHistory`, `dispatch`, `undo`, `redo`, `canUndo`, `canRedo`;
312
379
  type `History`.
313
380
 
package/dist/index.d.ts CHANGED
@@ -109,6 +109,11 @@ declare const mutableNodeAttrsSchema: z.ZodUnion<readonly [z.ZodObject<{
109
109
  row: "row";
110
110
  column: "column";
111
111
  }>>;
112
+ snap: z.ZodOptional<z.ZodOptional<z.ZodObject<{
113
+ divisions: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"panels">]>>;
114
+ step: z.ZodOptional<z.ZodNumber>;
115
+ threshold: z.ZodOptional<z.ZodNumber>;
116
+ }, z.core.$strip>>>;
112
117
  weight: z.ZodOptional<z.ZodNumber>;
113
118
  }, z.core.$strip>]>;
114
119
  declare const dockLocationSchema: z.ZodEnum<{
@@ -344,6 +349,11 @@ declare const actionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
344
349
  row: "row";
345
350
  column: "column";
346
351
  }>>;
352
+ snap: z.ZodOptional<z.ZodOptional<z.ZodObject<{
353
+ divisions: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"panels">]>>;
354
+ step: z.ZodOptional<z.ZodNumber>;
355
+ threshold: z.ZodOptional<z.ZodNumber>;
356
+ }, z.core.$strip>>>;
347
357
  weight: z.ZodOptional<z.ZodNumber>;
348
358
  }, z.core.$strip>]>;
349
359
  nodeId: z.ZodString;
@@ -352,6 +362,11 @@ declare const actionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
352
362
  attrs: z.ZodObject<{
353
363
  enableSplitDock: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
354
364
  enableSplitResize: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
365
+ snap: z.ZodOptional<z.ZodOptional<z.ZodObject<{
366
+ divisions: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"panels">]>>;
367
+ step: z.ZodOptional<z.ZodNumber>;
368
+ threshold: z.ZodOptional<z.ZodNumber>;
369
+ }, z.core.$strip>>>;
355
370
  splitterSize: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
356
371
  tabEnableClose: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
357
372
  tabEnableDrag: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
@@ -365,6 +380,54 @@ declare const actionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
365
380
  tabSetMinSize: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
366
381
  }, z.core.$strip>;
367
382
  type: z.ZodLiteral<"updateGlobalAttributes">;
383
+ }, z.core.$strip>, z.ZodObject<{
384
+ floatId: z.ZodOptional<z.ZodString>;
385
+ geometry: z.ZodOptional<z.ZodObject<{
386
+ height: z.ZodNumber;
387
+ left: z.ZodNumber;
388
+ top: z.ZodNumber;
389
+ width: z.ZodNumber;
390
+ }, z.core.$strip>>;
391
+ tabId: z.ZodString;
392
+ type: z.ZodLiteral<"floatTab">;
393
+ }, z.core.$strip>, z.ZodObject<{
394
+ floatId: z.ZodOptional<z.ZodString>;
395
+ geometry: z.ZodOptional<z.ZodObject<{
396
+ height: z.ZodNumber;
397
+ left: z.ZodNumber;
398
+ top: z.ZodNumber;
399
+ width: z.ZodNumber;
400
+ }, z.core.$strip>>;
401
+ tabsetId: z.ZodString;
402
+ type: z.ZodLiteral<"floatTabset">;
403
+ }, z.core.$strip>, z.ZodObject<{
404
+ floatId: z.ZodString;
405
+ location: z.ZodOptional<z.ZodEnum<{
406
+ center: "center";
407
+ "split-bottom": "split-bottom";
408
+ "split-left": "split-left";
409
+ "split-right": "split-right";
410
+ "split-top": "split-top";
411
+ }>>;
412
+ targetId: z.ZodOptional<z.ZodString>;
413
+ type: z.ZodLiteral<"dockFloat">;
414
+ }, z.core.$strip>, z.ZodObject<{
415
+ floatId: z.ZodString;
416
+ geometry: z.ZodObject<{
417
+ height: z.ZodNumber;
418
+ left: z.ZodNumber;
419
+ top: z.ZodNumber;
420
+ width: z.ZodNumber;
421
+ }, z.core.$strip>;
422
+ type: z.ZodLiteral<"moveFloat">;
423
+ }, z.core.$strip>, z.ZodObject<{
424
+ floatId: z.ZodString;
425
+ minimized: z.ZodBoolean;
426
+ type: z.ZodLiteral<"setFloatMinimized">;
427
+ }, z.core.$strip>, z.ZodObject<{
428
+ floatId: z.ZodString;
429
+ name: z.ZodString;
430
+ type: z.ZodLiteral<"renameFloat">;
368
431
  }, z.core.$strip>], "type">;
369
432
  type DockLocation = z.infer<typeof dockLocationSchema>;
370
433
  type MutableNodeAttrs = z.infer<typeof mutableNodeAttrsSchema>;
@@ -409,6 +472,11 @@ declare const dimensionSchema: z.ZodObject<{
409
472
  }>;
410
473
  value: z.ZodNumber;
411
474
  }, z.core.$strip>;
475
+ declare const snapSchema: z.ZodObject<{
476
+ divisions: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"panels">]>>;
477
+ step: z.ZodOptional<z.ZodNumber>;
478
+ threshold: z.ZodOptional<z.ZodNumber>;
479
+ }, z.core.$strip>;
412
480
  declare const tabNodeSchema: z.ZodObject<{
413
481
  component: z.ZodString;
414
482
  config: z.ZodOptional<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>;
@@ -466,13 +534,38 @@ type RowNode = {
466
534
  max?: z.infer<typeof dimensionSchema>;
467
535
  min?: z.infer<typeof dimensionSchema>;
468
536
  orientation: z.infer<typeof orientationSchema>;
537
+ snap?: z.infer<typeof snapSchema>;
469
538
  type: "row";
470
539
  weight?: number;
471
540
  };
472
541
  declare const rowNodeSchema: z.ZodType<RowNode>;
542
+ declare const geometrySchema: z.ZodObject<{
543
+ height: z.ZodNumber;
544
+ left: z.ZodNumber;
545
+ top: z.ZodNumber;
546
+ width: z.ZodNumber;
547
+ }, z.core.$strip>;
548
+ declare const floatNodeSchema: z.ZodObject<{
549
+ geometry: z.ZodObject<{
550
+ height: z.ZodNumber;
551
+ left: z.ZodNumber;
552
+ top: z.ZodNumber;
553
+ width: z.ZodNumber;
554
+ }, z.core.$strip>;
555
+ id: z.ZodString;
556
+ layout: z.ZodType<RowNode, unknown, z.core.$ZodTypeInternals<RowNode, unknown>>;
557
+ minimized: z.ZodOptional<z.ZodBoolean>;
558
+ name: z.ZodOptional<z.ZodString>;
559
+ type: z.ZodLiteral<"float">;
560
+ }, z.core.$strip>;
473
561
  declare const globalAttributesSchema: z.ZodObject<{
474
562
  enableSplitDock: z.ZodOptional<z.ZodBoolean>;
475
563
  enableSplitResize: z.ZodOptional<z.ZodBoolean>;
564
+ snap: z.ZodOptional<z.ZodObject<{
565
+ divisions: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"panels">]>>;
566
+ step: z.ZodOptional<z.ZodNumber>;
567
+ threshold: z.ZodOptional<z.ZodNumber>;
568
+ }, z.core.$strip>>;
476
569
  splitterSize: z.ZodOptional<z.ZodNumber>;
477
570
  tabEnableClose: z.ZodOptional<z.ZodBoolean>;
478
571
  tabEnableDrag: z.ZodOptional<z.ZodBoolean>;
@@ -490,6 +583,11 @@ declare const dashfooSchema: z.ZodObject<{
490
583
  global: z.ZodObject<{
491
584
  enableSplitDock: z.ZodOptional<z.ZodBoolean>;
492
585
  enableSplitResize: z.ZodOptional<z.ZodBoolean>;
586
+ snap: z.ZodOptional<z.ZodObject<{
587
+ divisions: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"panels">]>>;
588
+ step: z.ZodOptional<z.ZodNumber>;
589
+ threshold: z.ZodOptional<z.ZodNumber>;
590
+ }, z.core.$strip>>;
493
591
  splitterSize: z.ZodOptional<z.ZodNumber>;
494
592
  tabEnableClose: z.ZodOptional<z.ZodBoolean>;
495
593
  tabEnableDrag: z.ZodOptional<z.ZodBoolean>;
@@ -505,14 +603,30 @@ declare const dashfooSchema: z.ZodObject<{
505
603
  layout: z.ZodType<RowNode, unknown, z.core.$ZodTypeInternals<RowNode, unknown>>;
506
604
  maximizedTabsetId: z.ZodOptional<z.ZodString>;
507
605
  version: z.ZodLiteral<1>;
606
+ floats: z.ZodOptional<z.ZodArray<z.ZodObject<{
607
+ geometry: z.ZodObject<{
608
+ height: z.ZodNumber;
609
+ left: z.ZodNumber;
610
+ top: z.ZodNumber;
611
+ width: z.ZodNumber;
612
+ }, z.core.$strip>;
613
+ id: z.ZodString;
614
+ layout: z.ZodType<RowNode, unknown, z.core.$ZodTypeInternals<RowNode, unknown>>;
615
+ minimized: z.ZodOptional<z.ZodBoolean>;
616
+ name: z.ZodOptional<z.ZodString>;
617
+ type: z.ZodLiteral<"float">;
618
+ }, z.core.$strip>>>;
508
619
  }, z.core.$strip>;
509
620
  type Json = JsonValue;
510
621
  type Edge = z.infer<typeof edgeSchema>;
511
622
  type Unit = z.infer<typeof unitSchema>;
512
623
  type Orientation = z.infer<typeof orientationSchema>;
513
624
  type Dimension = z.infer<typeof dimensionSchema>;
625
+ type SnapConfig = z.infer<typeof snapSchema>;
514
626
  type TabNode = z.infer<typeof tabNodeSchema>;
515
627
  type TabsetNode = z.infer<typeof tabsetNodeSchema>;
628
+ type Geometry = z.infer<typeof geometrySchema>;
629
+ type FloatNode = z.infer<typeof floatNodeSchema>;
516
630
  type GlobalAttributes = z.infer<typeof globalAttributesSchema>;
517
631
  type Dashfoo = z.infer<typeof dashfooSchema>;
518
632
  type Node = RowNode | TabsetNode | TabNode;
@@ -542,11 +656,18 @@ type RowOptions = {
542
656
  max?: number | Dimension;
543
657
  min?: number | Dimension;
544
658
  orientation?: RowNode["orientation"];
659
+ snap?: SnapConfig;
545
660
  weight?: number;
546
661
  };
547
662
  declare const row: (children: RowNode["children"], options?: RowOptions) => RowNode;
663
+ type FloatOptions = {
664
+ id?: string;
665
+ name?: string;
666
+ };
667
+ declare const floatNode: (layout: RowNode, geometry: Geometry, options?: FloatOptions) => FloatNode;
548
668
  type ModelOptions = {
549
669
  activeTabsetId?: string;
670
+ floats?: Array<FloatNode>;
550
671
  global?: GlobalAttributes;
551
672
  maximizedTabsetId?: string;
552
673
  };
@@ -580,6 +701,16 @@ declare const resolveDockTarget: (pointer: Point, rect: Rect, opts?: BandOptions
580
701
  declare const dockZonePolygons: (rect: Rect, opts?: BandOptions) => Array<DockZone>;
581
702
  declare const zoneRect: (rect: Rect, location: DockLocation) => Rect;
582
703
  //#endregion
704
+ //#region src/geometry/snap.d.ts
705
+ declare const DEFAULT_SNAP_THRESHOLD = 4;
706
+ declare const resolveSnapTargets: (config: SnapConfig, panelCount: number) => Array<number>;
707
+ declare const snapEnabled: (config: SnapConfig | null) => boolean;
708
+ type SnapResult = {
709
+ sizes: Array<number>;
710
+ snapped: boolean;
711
+ };
712
+ declare const snapSizes: (sizes: Array<number>, boundaryIndex: number, config: SnapConfig) => SnapResult;
713
+ //#endregion
583
714
  //#region src/state/history.d.ts
584
715
  type History = {
585
716
  future: Array<Dashfoo>;
@@ -703,6 +834,9 @@ type TabLocation = {
703
834
  index: number;
704
835
  tab: TabNode;
705
836
  };
837
+ declare const collectRoots: (model: Dashfoo) => Array<RowNode>;
838
+ declare const findFloat: (model: Dashfoo, floatId: string) => FloatNode | undefined;
839
+ declare const collectTabsetsInRow: (row: RowNode, acc: Array<TabsetNode>) => void;
706
840
  declare const collectTabsets: (model: Dashfoo) => Array<TabsetNode>;
707
841
  declare const getFirstTabset: (model: Dashfoo) => TabsetNode | undefined;
708
842
  declare const findTabset: (model: Dashfoo, tabsetId: string) => TabsetNode | undefined;
@@ -710,11 +844,12 @@ declare const findTab: (model: Dashfoo, tabId: string) => TabLocation | undefine
710
844
  type AttributedNode = RowNode | TabNode | TabsetNode;
711
845
  declare const findRow: (row: RowNode, id: string) => RowNode | undefined;
712
846
  declare const findAttributedNode: (model: Dashfoo, id: string) => AttributedNode | undefined;
847
+ declare const findRootContaining: (model: Dashfoo, nodeId: string) => RowNode | undefined;
713
848
  declare const findTabsetParent: (row: RowNode, tabsetId: string) => {
714
849
  index: number;
715
850
  parent: RowNode;
716
851
  } | undefined;
717
852
  declare const findDuplicateIds: (model: Dashfoo) => Array<string>;
718
853
  //#endregion
719
- export { type Action, type AttributedNode, type BandOptions, type Dashfoo, type DashfooContext, type DashfooEvent, type DashfooInput, type Dimension, type DockLocation, type DockTarget, type DockZone, type DragContext, type DragEmitted, type DragEvent, type DragSubject, type DropIntent, type Edge, type GlobalAttributes, HISTORY_LIMIT, type History, type Json, type ModelOptions, type MutableNodeAttrs, type Node, type Orientation, type Point, type Rect, type RowNode, type RowOptions, type TabContainer, type TabLocation, type TabNode, type TabOptions, type TabsetNode, type TabsetOptions, type Unit, actionSchema, canRedo, canUndo, collectTabsets, createHistory, createNodeId, createTabId, dashfooMachine, dashfooSchema, dimensionSchema, dispatch, dockLocationSchema, dockZonePolygons, dragDockMachine, edgeSchema, findAttributedNode, findDuplicateIds, findRow, findTab, findTabset, findTabsetParent, fromJSON, getFirstTabset, globalAttributesSchema, jsonValueSchema, model, mutableNodeAttrsSchema, normalize, orientationSchema, parseModel, redo, reducer, resolveDockTarget, row, rowNodeSchema, stackModel, tab, tabNodeSchema, tabset, tabsetNodeSchema, toJSON, undo, unitSchema, zoneRect };
854
+ export { type Action, type AttributedNode, type BandOptions, DEFAULT_SNAP_THRESHOLD, type Dashfoo, type DashfooContext, type DashfooEvent, type DashfooInput, type Dimension, type DockLocation, type DockTarget, type DockZone, type DragContext, type DragEmitted, type DragEvent, type DragSubject, type DropIntent, type Edge, type FloatNode, type FloatOptions, type Geometry, type GlobalAttributes, HISTORY_LIMIT, type History, type Json, type ModelOptions, type MutableNodeAttrs, type Node, type Orientation, type Point, type Rect, type RowNode, type RowOptions, type SnapConfig, type SnapResult, type TabContainer, type TabLocation, type TabNode, type TabOptions, type TabsetNode, type TabsetOptions, type Unit, actionSchema, canRedo, canUndo, collectRoots, collectTabsets, collectTabsetsInRow, createHistory, createNodeId, createTabId, dashfooMachine, dashfooSchema, dimensionSchema, dispatch, dockLocationSchema, dockZonePolygons, dragDockMachine, edgeSchema, findAttributedNode, findDuplicateIds, findFloat, findRootContaining, findRow, findTab, findTabset, findTabsetParent, floatNode, floatNodeSchema, fromJSON, geometrySchema, getFirstTabset, globalAttributesSchema, jsonValueSchema, model, mutableNodeAttrsSchema, normalize, orientationSchema, parseModel, redo, reducer, resolveDockTarget, resolveSnapTargets, row, rowNodeSchema, snapEnabled, snapSchema, snapSizes, stackModel, tab, tabNodeSchema, tabset, tabsetNodeSchema, toJSON, undo, unitSchema, zoneRect };
720
855
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/state/actions.ts","../src/model/schema.ts","../src/model/builders.ts","../src/geometry/geometry.ts","../src/state/history.ts","../src/model/ids.ts","../src/model/invariants.ts","../src/machines/dashfoo-machine.ts","../src/machines/drag-dock-machine.ts","../src/state/reducer.ts","../src/model/serialize.ts","../src/model/stack.ts","../src/model/tree.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/state/actions.ts","../src/model/schema.ts","../src/model/builders.ts","../src/geometry/geometry.ts","../src/geometry/snap.ts","../src/state/history.ts","../src/model/ids.ts","../src/model/invariants.ts","../src/machines/dashfoo-machine.ts","../src/machines/drag-dock-machine.ts","../src/state/reducer.ts","../src/model/serialize.ts","../src/model/stack.ts","../src/model/tree.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{z as e}from"zod";import{assign as t,emit as n,setup as r}from"xstate";const i=e.lazy(()=>e.union([e.string(),e.number(),e.boolean(),e.null(),e.array(i),e.record(e.string(),i)])),a=e.enum([`top`,`bottom`,`left`,`right`]),o=e.enum([`px`,`%`,`em`,`rem`,`vh`,`vw`]),s=e.enum([`row`,`column`]),c=e.object({unit:o,value:e.number()}),l=e.object({component:e.string(),config:i.optional(),enableClose:e.boolean().optional(),enableDrag:e.boolean().optional(),enableRename:e.boolean().optional(),id:e.string(),name:e.string(),type:e.literal(`tab`)}),u=e.object({children:e.array(l),enableClose:e.boolean().optional(),enableMaximize:e.boolean().optional(),id:e.string(),max:c.optional(),min:c.optional(),name:e.string().optional(),selected:e.number().int(),type:e.literal(`tabset`),weight:e.number().optional()}),d=e.lazy(()=>e.object({children:e.array(e.union([d,u])),id:e.string(),max:c.optional(),min:c.optional(),orientation:s,type:e.literal(`row`),weight:e.number().optional()})),f=e.object({enableSplitDock:e.boolean().optional(),enableSplitResize:e.boolean().optional(),splitterSize:e.number().optional(),tabEnableClose:e.boolean().optional(),tabEnableDrag:e.boolean().optional(),tabEnableRename:e.boolean().optional(),tabLocation:e.enum([`top`,`bottom`]).optional(),tabSetEnableMaximize:e.boolean().optional(),tabSetEnableTabStrip:e.boolean().optional(),tabSetMinSize:e.number().optional()}),p=e.object({activeTabsetId:e.string().optional(),global:f,layout:d,maximizedTabsetId:e.string().optional(),version:e.literal(1)}),m=l.pick({config:!0,enableClose:!0,enableDrag:!0,enableRename:!0,name:!0}).partial(),h=u.pick({enableClose:!0,enableMaximize:!0,max:!0,min:!0,selected:!0,weight:!0}).partial(),g=e.object({max:c.optional(),min:c.optional(),orientation:s,weight:e.number()}).partial(),_=e.union([m,h,g]),v=e.enum([`center`,`split-bottom`,`split-left`,`split-right`,`split-top`]),y=e.discriminatedUnion(`type`,[e.object({index:e.number().int().optional(),location:v,tab:l,targetId:e.string(),type:e.literal(`addNode`)}),e.object({index:e.number().int().optional(),location:v,sourceId:e.string(),targetId:e.string(),type:e.literal(`moveNode`)}),e.object({index:e.number().int().optional(),location:v,sourceId:e.string(),targetId:e.string(),type:e.literal(`moveTabset`)}),e.object({index:e.number().int(),tabsetId:e.string(),type:e.literal(`selectTab`)}),e.object({tabsetId:e.string(),type:e.literal(`setActiveTabset`)}),e.object({tabsetId:e.string().nullable(),type:e.literal(`setMaximizedTabset`)}),e.object({name:e.string(),tabId:e.string(),type:e.literal(`renameTab`)}),e.object({tabId:e.string(),type:e.literal(`deleteTab`)}),e.object({tabsetId:e.string(),type:e.literal(`deleteTabset`)}),e.object({rowId:e.string(),type:e.literal(`adjustSplit`),weights:e.array(e.number())}),e.object({attrs:_,nodeId:e.string(),type:e.literal(`updateNodeAttributes`)}),e.object({attrs:f.partial(),type:e.literal(`updateGlobalAttributes`)})]),b=(e=`node`)=>`${e}-${crypto.randomUUID()}`,ee=()=>b(`tab`),x=(e,t)=>{for(let n of e.children)n.type===`tabset`?t.push(n):x(n,t)},S=e=>{let t=[];return x(e.layout,t),t},C=e=>S(e)[0],w=(e,t)=>S(e).find(e=>e.id===t),T=(e,t)=>{let n=e.children.findIndex(e=>e.id===t);if(n===-1)return;let r=e.children[n];if(r)return{container:e,index:n,tab:r}},E=(e,t)=>{for(let n of S(e)){let e=T(n,t);if(e)return e}},D=(e,t)=>{if(e.id===t)return e;for(let n of e.children)if(n.type===`row`){let e=D(n,t);if(e)return e}},O=(e,t)=>{if(e.id===t)return e;for(let n of e.children){if(n.type===`row`){let e=O(n,t);if(e)return e;continue}if(n.id===t)return n;let e=n.children.find(e=>e.id===t);if(e)return e}},k=(e,t)=>O(e.layout,t),A=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1)return{index:n,parent:e};for(let n of e.children)if(n.type===`row`){let e=A(n,t);if(e)return e}},j=(e,t)=>{t.push(e.id);for(let n of e.children)if(n.type===`row`)j(n,t);else{t.push(n.id);for(let e of n.children)t.push(e.id)}},M=e=>{let t=[];j(e.layout,t);let n=new Set,r=new Set;for(let e of t)n.has(e)&&r.add(e),n.add(e);return[...r]},N=e=>typeof e==`number`?{unit:`px`,value:e}:e,P=(e,t,n={})=>({component:e,name:t,type:`tab`,...n,id:n.id??e}),te=(e,t={})=>{let{max:n,min:r,...i}=t;return{children:e,type:`tabset`,...i,...n===void 0?{}:{max:N(n)},...r===void 0?{}:{min:N(r)},id:t.id??b(`tabset`),selected:t.selected??0}},ne=(e,t={})=>{let{max:n,min:r,...i}=t;return{children:e,type:`row`,...i,...n===void 0?{}:{max:N(n)},...r===void 0?{}:{min:N(r)},id:t.id??b(`row`),orientation:t.orientation??`row`}},re=(e,t={})=>{let n={layout:e,version:1,...t,global:t.global??{}},r=M(n);return r.length>0&&console.warn(`[dashfoo] builder produced duplicate node ids: ${r.join(`, `)} — pass explicit ids when reusing a component`),n},F=.22,ie=(e,t)=>{let n=(e.x-t.x)/t.width,r=(e.y-t.y)/t.height;return{bottom:1-r,left:n,right:1-n,top:r}},ae=e=>{let t=Math.min(e.left,e.right,e.top,e.bottom);return t===e.left?`left`:t===e.right?`right`:t===e.top?`top`:`bottom`},oe=(e,t,n)=>{let r=n?.bandFraction??F;if(t.width<=0||t.height<=0)return{kind:`tab`};let i=ie(e,t);return Math.min(i.left,i.right,i.top,i.bottom)>r?{kind:`tab`}:{edge:ae(i),kind:`split`}},se=(e,t)=>{let{height:n,width:r,x:i,y:a}=e,o={x:i,y:a},s={x:i+r,y:a},c={x:i+r,y:a+n},l={x:i,y:a+n};if(r<=0||n<=0)return[{location:`center`,points:[o,s,c,l]}];let u=t?.bandFraction??F,d=u*r,f=u*n,p={x:i+d,y:a+f},m={x:i+r-d,y:a+f},h={x:i+r-d,y:a+n-f},g={x:i+d,y:a+n-f};return[{location:`center`,points:[p,m,h,g]},{location:`split-left`,points:[o,p,g,l]},{location:`split-right`,points:[s,c,h,m]},{location:`split-top`,points:[o,s,m,p]},{location:`split-bottom`,points:[l,g,h,c]}]},ce=(e,t)=>{let n=e.width/2,r=e.height/2;switch(t){case`split-bottom`:return{height:r,width:e.width,x:e.x,y:e.y+r};case`split-left`:return{height:e.height,width:n,x:e.x,y:e.y};case`split-right`:return{height:e.height,width:n,x:e.x+n,y:e.y};case`split-top`:return{height:r,width:e.width,x:e.x,y:e.y};default:return e}},I=(e,t)=>{if(e===0)return 0;let n=Number.isFinite(t)?Math.trunc(t):0;return n<0?0:n>e-1?e-1:n},L=e=>({...e,selected:I(e.children.length,e.selected)}),R=e=>{let t=[];for(let n of e){if(n.type===`tabset`){n.children.length>0&&t.push(L(n));continue}let e=R(n.children);if(e.length!==0){if(e.length===1){let r=e[0];r!==void 0&&t.push(n.weight===void 0?r:{...r,weight:n.weight});continue}t.push({...n,children:e})}}return t},z=e=>{let t=R(e.children),n=e.orientation,r=t[0];for(;t.length===1&&r?.type===`row`;)t=r.children,n=r.orientation,r=t[0];return{...e,children:t,orientation:n}},B=e=>{let t=z(e.layout),n={...e,layout:t},r=new Set(S(n).map(e=>e.id)),i=S(n)[0]?.id,a=e.activeTabsetId!==void 0&&r.has(e.activeTabsetId)?e.activeTabsetId:i,o=e.maximizedTabsetId!==void 0&&r.has(e.maximizedTabsetId)?e.maximizedTabsetId:void 0;return{...n,activeTabsetId:a,maximizedTabsetId:o}},V=e=>{throw Error(`Unhandled action: ${JSON.stringify(e)}`)},H=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1)return e.children.splice(n,1),!0;for(let n of e.children)if(n.type===`row`&&H(n,t))return!0;return!1},U=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1){let[t]=e.children.splice(n,1);return t?.type===`tabset`?t:void 0}for(let n of e.children)if(n.type===`row`){let e=U(n,t);if(e)return e}},W=e=>e===`split-left`||e===`split-right`?`row`:`column`,le=e=>e===`split-left`||e===`split-top`,G=(e,t,n,r)=>{let i=w(e,n),a=A(e.layout,n);if(!i||!a)return;let o=W(r),s=le(r);if(a.parent.orientation===o){let e=i.weight??100;i.weight=e/2,t.weight=e/2,a.parent.children.splice(s?a.index:a.index+1,0,t)}else{let e=i.weight;i.weight=50,t.weight=50;let n={children:s?[t,i]:[i,t],id:b(`row`),orientation:o,type:`row`};e!==void 0&&(n.weight=e),a.parent.children.splice(a.index,1,n)}e.activeTabsetId=t.id},K=(e,t,n)=>{let{id:r,index:i,location:a}=n,o=w(e,r);if(o){if(a===`center`){let e=i??o.children.length;o.children.splice(e,0,t),o.selected=e;return}G(e,{children:[t],id:b(`tabset`),selected:0,type:`tabset`,weight:50},r,a)}},ue=(e,t)=>{switch(t.type){case`addNode`:K(e,t.tab,{id:t.targetId,index:t.index,location:t.location});return;case`adjustSplit`:{let n=D(e.layout,t.rowId);if(!n)return;for(let e=0;e<t.weights.length;e++){let r=n.children[e],i=t.weights[e];!r||i===void 0||(r.weight=i)}return}case`deleteTab`:{let n=E(e,t.tabId);n&&n.container.children.splice(n.index,1);return}case`deleteTabset`:H(e.layout,t.tabsetId);return;case`moveNode`:{let n=E(e,t.sourceId);if(!n)return;let[r]=n.container.children.splice(n.index,1);if(!r)return;K(e,r,{id:t.targetId,index:t.index,location:t.location});return}case`moveTabset`:{if(t.sourceId===t.targetId)return;let n=w(e,t.sourceId);if(!n)return;if(t.location===`center`){let r=w(e,t.targetId);r&&(r.children.push(...n.children),H(e.layout,t.sourceId));return}if(t.location.startsWith(`split-`)){let n=U(e.layout,t.sourceId);n&&G(e,n,t.targetId,t.location)}return}case`renameTab`:{let n=E(e,t.tabId);n&&(n.tab.name=t.name);return}case`selectTab`:{let n=w(e,t.tabsetId);n&&(n.selected=t.index);return}case`setActiveTabset`:w(e,t.tabsetId)&&(e.activeTabsetId=t.tabsetId);return;case`setMaximizedTabset`:e.maximizedTabsetId=t.tabsetId??void 0;return;case`updateGlobalAttributes`:Object.assign(e.global,t.attrs);return;case`updateNodeAttributes`:{let n=k(e,t.nodeId);n&&Object.assign(n,t.attrs);return}default:V(t)}},q=(e,t)=>{let n=structuredClone(e);return ue(n,t),B(n)},de=100,J=e=>({future:[],past:[],present:e}),fe=e=>e.past.length>0,pe=e=>e.future.length>0,Y=(e,t)=>{let n=q(e.present,t);return{future:[],past:[...e.past,e.present].slice(-100),present:n}},X=e=>{let t=e.past.at(-1);return t===void 0?e:{future:[e.present,...e.future],past:e.past.slice(0,-1),present:t}},Z=e=>{let[t,...n]=e.future;return t===void 0?e:{future:n,past:[...e.past,e.present].slice(-100),present:t}},me=r({types:{context:{},events:{},input:{}}}).createMachine({context:({input:e})=>({history:J(e.model)}),id:`dashfoo`,initial:`ready`,states:{ready:{on:{DISPATCH:{actions:t({history:({context:e,event:t})=>Y(e.history,t.action)})},REDO:{actions:t({history:({context:e})=>Z(e.history)})},SET_MODEL:{actions:t({history:({event:e})=>J(e.model)})},UNDO:{actions:t({history:({context:e})=>X(e.history)})}}}}}),he=e=>{if(e.subject===null||e.intent===null)throw Error(`drop requires a subject and a drop intent`);return{intent:e.intent,subject:e.subject}},ge=r({guards:{hasValidDrop:({context:e})=>e.subject!==null&&e.intent!==null},types:{context:{},emitted:{},events:{}}}).createMachine({context:{intent:null,subject:null},id:`dragDock`,initial:`idle`,states:{dragging:{on:{CANCEL:{target:`idle`},DROP:[{actions:n(({context:e})=>{let{intent:t,subject:n}=he(e);return{action:n.kind===`external`?{index:t.index,location:t.location,tab:n.tab,targetId:t.targetId,type:`addNode`}:{index:t.index,location:t.location,sourceId:n.id,targetId:t.targetId,type:n.kind===`tabset`?`moveTabset`:`moveNode`},type:`COMMIT`}}),guard:`hasValidDrop`,target:`idle`},{target:`idle`}],OVER:{actions:t({intent:({event:e})=>e.intent})}}},idle:{entry:t({intent:null,subject:null}),on:{START:{actions:t({subject:({event:e})=>e.subject}),target:`dragging`}}}}}),Q=e=>{let t=B(p.parse(e)),n=M(t);return n.length>0&&console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${n.join(`, `)}`),t},$=e=>JSON.stringify(e),_e=e=>Q(JSON.parse(e)),ve=(e,t=`column`)=>{let n=[];for(let t of S(e))n.push({...t,weight:1});return B({...e,layout:{children:n,id:e.layout.id,orientation:t,type:`row`},maximizedTabsetId:void 0})};export{de as HISTORY_LIMIT,y as actionSchema,pe as canRedo,fe as canUndo,S as collectTabsets,J as createHistory,b as createNodeId,ee as createTabId,me as dashfooMachine,p as dashfooSchema,c as dimensionSchema,Y as dispatch,v as dockLocationSchema,se as dockZonePolygons,ge as dragDockMachine,a as edgeSchema,k as findAttributedNode,M as findDuplicateIds,D as findRow,E as findTab,w as findTabset,A as findTabsetParent,_e as fromJSON,C as getFirstTabset,f as globalAttributesSchema,i as jsonValueSchema,re as model,_ as mutableNodeAttrsSchema,B as normalize,s as orientationSchema,Q as parseModel,Z as redo,q as reducer,oe as resolveDockTarget,ne as row,d as rowNodeSchema,ve as stackModel,P as tab,l as tabNodeSchema,te as tabset,u as tabsetNodeSchema,$ as toJSON,X as undo,o as unitSchema,ce as zoneRect};
1
+ import{z as e}from"zod";import{assign as t,emit as n,setup as r}from"xstate";const i=e.lazy(()=>e.union([e.string(),e.number(),e.boolean(),e.null(),e.array(i),e.record(e.string(),i)])),a=e.enum([`top`,`bottom`,`left`,`right`]),o=e.enum([`px`,`%`,`em`,`rem`,`vh`,`vw`]),s=e.enum([`row`,`column`]),c=e.object({unit:o,value:e.number()}),l=e.object({divisions:e.union([e.number().positive(),e.literal(`panels`)]).optional(),step:e.number().min(0).optional(),threshold:e.number().positive().optional()}),u=e.object({component:e.string(),config:i.optional(),enableClose:e.boolean().optional(),enableDrag:e.boolean().optional(),enableRename:e.boolean().optional(),id:e.string(),name:e.string(),type:e.literal(`tab`)}),d=e.object({children:e.array(u),enableClose:e.boolean().optional(),enableMaximize:e.boolean().optional(),id:e.string(),max:c.optional(),min:c.optional(),name:e.string().optional(),selected:e.number().int(),type:e.literal(`tabset`),weight:e.number().optional()}),f=e.lazy(()=>e.object({children:e.array(e.union([f,d])),id:e.string(),max:c.optional(),min:c.optional(),orientation:s,snap:l.optional(),type:e.literal(`row`),weight:e.number().optional()})),p=e.object({height:e.number(),left:e.number(),top:e.number(),width:e.number()}),m=e.object({geometry:p,id:e.string(),layout:f,minimized:e.boolean().optional(),name:e.string().optional(),type:e.literal(`float`)}),h=e.object({enableSplitDock:e.boolean().optional(),enableSplitResize:e.boolean().optional(),snap:l.optional(),splitterSize:e.number().optional(),tabEnableClose:e.boolean().optional(),tabEnableDrag:e.boolean().optional(),tabEnableRename:e.boolean().optional(),tabLocation:e.enum([`top`,`bottom`]).optional(),tabSetEnableMaximize:e.boolean().optional(),tabSetEnableTabStrip:e.boolean().optional(),tabSetMinSize:e.number().optional()}),g=e.object({activeTabsetId:e.string().optional(),global:h,layout:f,maximizedTabsetId:e.string().optional(),version:e.literal(1),floats:e.array(m).optional()}),ee=u.pick({config:!0,enableClose:!0,enableDrag:!0,enableRename:!0,name:!0}).partial(),te=d.pick({enableClose:!0,enableMaximize:!0,max:!0,min:!0,selected:!0,weight:!0}).partial(),ne=e.object({max:c.optional(),min:c.optional(),orientation:s,snap:l.optional(),weight:e.number()}).partial(),_=e.union([ee,te,ne]),v=e.enum([`center`,`split-bottom`,`split-left`,`split-right`,`split-top`]),re=e.discriminatedUnion(`type`,[e.object({index:e.number().int().optional(),location:v,tab:u,targetId:e.string(),type:e.literal(`addNode`)}),e.object({index:e.number().int().optional(),location:v,sourceId:e.string(),targetId:e.string(),type:e.literal(`moveNode`)}),e.object({index:e.number().int().optional(),location:v,sourceId:e.string(),targetId:e.string(),type:e.literal(`moveTabset`)}),e.object({index:e.number().int(),tabsetId:e.string(),type:e.literal(`selectTab`)}),e.object({tabsetId:e.string(),type:e.literal(`setActiveTabset`)}),e.object({tabsetId:e.string().nullable(),type:e.literal(`setMaximizedTabset`)}),e.object({name:e.string(),tabId:e.string(),type:e.literal(`renameTab`)}),e.object({tabId:e.string(),type:e.literal(`deleteTab`)}),e.object({tabsetId:e.string(),type:e.literal(`deleteTabset`)}),e.object({rowId:e.string(),type:e.literal(`adjustSplit`),weights:e.array(e.number())}),e.object({attrs:_,nodeId:e.string(),type:e.literal(`updateNodeAttributes`)}),e.object({attrs:h.partial(),type:e.literal(`updateGlobalAttributes`)}),e.object({floatId:e.string().optional(),geometry:p.optional(),tabId:e.string(),type:e.literal(`floatTab`)}),e.object({floatId:e.string().optional(),geometry:p.optional(),tabsetId:e.string(),type:e.literal(`floatTabset`)}),e.object({floatId:e.string(),location:v.optional(),targetId:e.string().optional(),type:e.literal(`dockFloat`)}),e.object({floatId:e.string(),geometry:p,type:e.literal(`moveFloat`)}),e.object({floatId:e.string(),minimized:e.boolean(),type:e.literal(`setFloatMinimized`)}),e.object({floatId:e.string(),name:e.string(),type:e.literal(`renameFloat`)})]),y=(e=`node`)=>`${e}-${crypto.randomUUID()}`,ie=()=>y(`tab`),b=e=>[e.layout,...(e.floats??[]).map(e=>e.layout)],x=(e,t)=>(e.floats??[]).find(e=>e.id===t),S=(e,t)=>{for(let n of e.children)n.type===`tabset`?t.push(n):S(n,t)},C=e=>{let t=[];for(let n of b(e))S(n,t);return t},ae=e=>C(e)[0],w=(e,t)=>C(e).find(e=>e.id===t),oe=(e,t)=>{let n=e.children.findIndex(e=>e.id===t);if(n===-1)return;let r=e.children[n];if(r)return{container:e,index:n,tab:r}},T=(e,t)=>{for(let n of C(e)){let e=oe(n,t);if(e)return e}},E=(e,t)=>{if(e.id===t)return e;for(let n of e.children)if(n.type===`row`){let e=E(n,t);if(e)return e}},D=(e,t)=>{if(e.id===t)return e;for(let n of e.children){if(n.type===`row`){let e=D(n,t);if(e)return e;continue}if(n.id===t)return n;let e=n.children.find(e=>e.id===t);if(e)return e}},O=(e,t)=>{for(let n of b(e)){let e=D(n,t);if(e)return e}},k=(e,t)=>{for(let n of b(e))if(D(n,t))return n},A=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1)return{index:n,parent:e};for(let n of e.children)if(n.type===`row`){let e=A(n,t);if(e)return e}},se=(e,t)=>{t.push(e.id);for(let n of e.children)if(n.type===`row`)se(n,t);else{t.push(n.id);for(let e of n.children)t.push(e.id)}},j=e=>{let t=[];for(let n of e.floats??[])t.push(n.id);for(let n of b(e))se(n,t);let n=new Set,r=new Set;for(let e of t)n.has(e)&&r.add(e),n.add(e);return[...r]},M=e=>typeof e==`number`?{unit:`px`,value:e}:e,ce=(e,t,n={})=>({component:e,name:t,type:`tab`,...n,id:n.id??e}),le=(e,t={})=>{let{max:n,min:r,...i}=t;return{children:e,type:`tabset`,...i,...n===void 0?{}:{max:M(n)},...r===void 0?{}:{min:M(r)},id:t.id??y(`tabset`),selected:t.selected??0}},ue=(e,t={})=>{let{max:n,min:r,...i}=t;return{children:e,type:`row`,...i,...n===void 0?{}:{max:M(n)},...r===void 0?{}:{min:M(r)},id:t.id??y(`row`),orientation:t.orientation??`row`}},de=(e,t,n={})=>({geometry:t,layout:e,type:`float`,...n,id:n.id??y(`float`)}),fe=(e,t={})=>{let n={layout:e,version:1,...t,global:t.global??{}},r=j(n);return r.length>0&&console.warn(`[dashfoo] builder produced duplicate node ids: ${r.join(`, `)} — pass explicit ids when reusing a component`),n},N=.22,pe=(e,t)=>{let n=(e.x-t.x)/t.width,r=(e.y-t.y)/t.height;return{bottom:1-r,left:n,right:1-n,top:r}},me=e=>{let t=Math.min(e.left,e.right,e.top,e.bottom);return t===e.left?`left`:t===e.right?`right`:t===e.top?`top`:`bottom`},he=(e,t,n)=>{let r=n?.bandFraction??N;if(t.width<=0||t.height<=0)return{kind:`tab`};let i=pe(e,t);return Math.min(i.left,i.right,i.top,i.bottom)>r?{kind:`tab`}:{edge:me(i),kind:`split`}},ge=(e,t)=>{let{height:n,width:r,x:i,y:a}=e,o={x:i,y:a},s={x:i+r,y:a},c={x:i+r,y:a+n},l={x:i,y:a+n};if(r<=0||n<=0)return[{location:`center`,points:[o,s,c,l]}];let u=t?.bandFraction??N,d=u*r,f=u*n,p={x:i+d,y:a+f},m={x:i+r-d,y:a+f},h={x:i+r-d,y:a+n-f},g={x:i+d,y:a+n-f};return[{location:`center`,points:[p,m,h,g]},{location:`split-left`,points:[o,p,g,l]},{location:`split-right`,points:[s,c,h,m]},{location:`split-top`,points:[o,s,m,p]},{location:`split-bottom`,points:[l,g,h,c]}]},_e=(e,t)=>{let n=e.width/2,r=e.height/2;switch(t){case`split-bottom`:return{height:r,width:e.width,x:e.x,y:e.y+r};case`split-left`:return{height:e.height,width:n,x:e.x,y:e.y};case`split-right`:return{height:e.height,width:n,x:e.x+n,y:e.y};case`split-top`:return{height:r,width:e.width,x:e.x,y:e.y};default:return e}},ve=4,P=1e6,F=(e,t)=>{if(!(t<=0))for(let n=t;n<100;n+=t){let t=Math.round(n*P)/P;t<100&&e.add(t)}},I=(e,t)=>{let n=new Set;if(e.step!==void 0&&e.step>0&&F(n,e.step),e.divisions!==void 0){let r=e.divisions===`panels`?t:e.divisions;r>=2&&F(n,100/r)}return[...n].toSorted((e,t)=>e-t)},ye=e=>e!==null&&((e.step??0)>0||e.divisions===`panels`||typeof e.divisions==`number`&&e.divisions>=2),be=(e,t)=>t.reduce((t,n)=>t===void 0||Math.abs(n-e)<Math.abs(t-e)?n:t,void 0),xe=(e,t,n)=>{let r=n.threshold??4,i=e[t],a=e[t+1];if(t<0||i===void 0||a===void 0)return{sizes:e,snapped:!1};let o=I(n,e.length);if(o.length===0)return{sizes:e,snapped:!1};let s=0;for(let n=0;n<=t;n++)s+=e[n]??0;let c=be(s,o);if(c===void 0||Math.abs(c-s)>r)return{sizes:e,snapped:!1};let l=c-s;if(i+l<0||a-l<0)return{sizes:e,snapped:!1};let u=[...e];return u[t]=i+l,u[t+1]=a-l,{sizes:u,snapped:!0}},Se=(e,t)=>{if(e===0)return 0;let n=Number.isFinite(t)?Math.trunc(t):0;return n<0?0:n>e-1?e-1:n},Ce=e=>({...e,selected:Se(e.children.length,e.selected)}),L=e=>{let t=[];for(let n of e){if(n.type===`tabset`){n.children.length>0&&t.push(Ce(n));continue}let e=L(n.children);if(e.length!==0){if(e.length===1){let r=e[0];r!==void 0&&t.push(n.weight===void 0?r:{...r,weight:n.weight});continue}t.push({...n,children:e})}}return t},R=e=>e.children.some(e=>e.type===`tabset`?!0:R(e)),z=e=>{let t=L(e.children),n=e.orientation,r=t[0];for(;t.length===1&&r?.type===`row`;)t=r.children,n=r.orientation,r=t[0];return{...e,children:t,orientation:n}},B=e=>{let t=z(e.layout),n=[];for(let t of e.floats??[]){let e=z(t.layout);R(e)&&n.push({...t,layout:e})}let r=n.length>0?n:void 0,i={...e,floats:r,layout:t},a=new Set(C(i).map(e=>e.id)),o=C(i)[0]?.id,s=e.activeTabsetId!==void 0&&a.has(e.activeTabsetId)?e.activeTabsetId:o,c=e.maximizedTabsetId!==void 0&&a.has(e.maximizedTabsetId)?e.maximizedTabsetId:void 0;return{...i,activeTabsetId:s,maximizedTabsetId:c}},V=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1)return e.children.splice(n,1),!0;for(let n of e.children)if(n.type===`row`&&V(n,t))return!0;return!1},H=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1){let[t]=e.children.splice(n,1);return t?.type===`tabset`?t:void 0}for(let n of e.children)if(n.type===`row`){let e=H(n,t);if(e)return e}},we=e=>e===`split-left`||e===`split-right`?`row`:`column`,Te=e=>e===`split-left`||e===`split-top`,U=(e,t,n,r)=>{let i=w(e,n),a=A(k(e,n)??e.layout,n);if(!i||!a)return;let o=we(r),s=Te(r);if(a.parent.orientation===o){let e=i.weight??100;i.weight=e/2,t.weight=e/2,a.parent.children.splice(s?a.index:a.index+1,0,t)}else{let e=i.weight;i.weight=50,t.weight=50;let n={children:s?[t,i]:[i,t],id:y(`row`),orientation:o,type:`row`};e!==void 0&&(n.weight=e),a.parent.children.splice(a.index,1,n)}e.activeTabsetId=t.id},W=(e,t,n)=>{let{id:r,index:i,location:a}=n,o=w(e,r);if(o){if(a===`center`){let e=i??o.children.length;o.children.splice(e,0,t),o.selected=e;return}U(e,{children:[t],id:y(`tabset`),selected:0,type:`tabset`,weight:50},r,a)}},G=(e,t)=>{for(let n of e.children)n.type===`tabset`?t.push(n):G(n,t)},Ee=e=>({children:[{children:[e],id:y(`tabset`),selected:0,type:`tabset`}],id:y(`row`),orientation:`row`,type:`row`}),De=e=>({children:[e],id:y(`row`),orientation:`row`,type:`row`}),Oe={height:360,left:80,top:80,width:480},K=(e,t)=>{let n=new Set(t.map(e=>e.name));if(!n.has(e))return e;let r=1;for(;n.has(`${e} ${r}`);)r+=1;return`${e} ${r}`},q=(e,t,n,r)=>{let i={geometry:n??Oe,id:r??y(`float`),layout:t,name:K(`Panel`,e.floats??[]),type:`float`};e.floats=[...e.floats??[],i];let a=[];G(t,a);let o=a[0];o&&(e.activeTabsetId=o.id)},ke=(e,t,n,r)=>{let i=k(e,t);if(!i)return;let a=H(i,t);a&&q(e,De(a),n,r)},Ae=(e,t)=>{let n=[];if(G(e.layout,n),t){let e=n.find(e=>e.id===t);if(e)return e}if(e.activeTabsetId!==void 0){let t=n.find(t=>t.id===e.activeTabsetId);if(t)return t}return n[0]},je=(e,t,n,r)=>{let i=e.floats??[],a=i.findIndex(e=>e.id===t);if(a===-1)return;let[o]=i.splice(a,1);if(e.floats=i,!o)return;let s=[];G(o.layout,s);let c=s.flatMap(e=>e.children);if(c.length===0)return;let l=s[0],u=l?Math.min(Math.max(l.selected,0),l.children.length-1):0,d=Ae(e,n);if(!d){e.layout=o.layout,e.activeTabsetId=l?.id;return}if(r===`center`){let t=d.children.length;d.children.push(...c),d.selected=t+u,e.activeTabsetId=d.id;return}let f=r??`split-right`,p=s.length===1&&l?l:{children:c,id:y(`tabset`),selected:u,type:`tabset`};p.weight=50,U(e,p,d.id,f)},Me=e=>{throw Error(`Unhandled action: ${JSON.stringify(e)}`)},Ne=(e,t)=>{switch(t.type){case`addNode`:W(e,t.tab,{id:t.targetId,index:t.index,location:t.location});return;case`adjustSplit`:{let n=E(k(e,t.rowId)??e.layout,t.rowId);if(!n)return;for(let e=0;e<t.weights.length;e++){let r=n.children[e],i=t.weights[e];!r||i===void 0||(r.weight=i)}return}case`deleteTab`:{let n=T(e,t.tabId);n&&n.container.children.splice(n.index,1);return}case`deleteTabset`:{let n=k(e,t.tabsetId);n&&V(n,t.tabsetId);return}case`dockFloat`:je(e,t.floatId,t.targetId,t.location);return;case`floatTab`:{let n=T(e,t.tabId);if(!n)return;let[r]=n.container.children.splice(n.index,1);r&&q(e,Ee(r),t.geometry,t.floatId);return}case`floatTabset`:ke(e,t.tabsetId,t.geometry,t.floatId);return;case`moveFloat`:{let n=x(e,t.floatId);n&&(n.geometry=t.geometry);return}case`moveNode`:{let n=T(e,t.sourceId);if(!n)return;let[r]=n.container.children.splice(n.index,1);if(!r)return;W(e,r,{id:t.targetId,index:t.index,location:t.location});return}case`moveTabset`:{if(t.sourceId===t.targetId)return;let n=w(e,t.sourceId);if(!n)return;if(t.location===`center`){let r=w(e,t.targetId);if(r){let i=r.children.length,a=Math.min(Math.max(n.selected,0),n.children.length-1);r.children.push(...n.children),r.selected=i+a;let o=k(e,t.sourceId);o&&V(o,t.sourceId)}return}if(t.location.startsWith(`split-`)){let n=k(e,t.sourceId),r=n?H(n,t.sourceId):void 0;r&&U(e,r,t.targetId,t.location)}return}case`renameFloat`:{let n=x(e,t.floatId);if(n){let r=(e.floats??[]).filter(e=>e.id!==t.floatId);n.name=K(t.name,r)}return}case`renameTab`:{let n=T(e,t.tabId);n&&(n.tab.name=t.name);return}case`selectTab`:{let n=w(e,t.tabsetId);n&&(n.selected=t.index);return}case`setActiveTabset`:w(e,t.tabsetId)&&(e.activeTabsetId=t.tabsetId);return;case`setFloatMinimized`:{let n=x(e,t.floatId);n&&(n.minimized=t.minimized);return}case`setMaximizedTabset`:e.maximizedTabsetId=t.tabsetId??void 0;return;case`updateGlobalAttributes`:Object.assign(e.global,t.attrs);return;case`updateNodeAttributes`:{let n=O(e,t.nodeId);n&&Object.assign(n,t.attrs);return}default:Me(t)}},J=(e,t)=>{let n=structuredClone(e);return Ne(n,t),B(n)},Pe=100,Y=e=>({future:[],past:[],present:e}),Fe=e=>e.past.length>0,Ie=e=>e.future.length>0,X=(e,t)=>{let n=J(e.present,t);return{future:[],past:[...e.past,e.present].slice(-100),present:n}},Z=e=>{let t=e.past.at(-1);return t===void 0?e:{future:[e.present,...e.future],past:e.past.slice(0,-1),present:t}},Q=e=>{let[t,...n]=e.future;return t===void 0?e:{future:n,past:[...e.past,e.present].slice(-100),present:t}},Le=r({types:{context:{},events:{},input:{}}}).createMachine({context:({input:e})=>({history:Y(e.model)}),id:`dashfoo`,initial:`ready`,states:{ready:{on:{DISPATCH:{actions:t({history:({context:e,event:t})=>X(e.history,t.action)})},REDO:{actions:t({history:({context:e})=>Q(e.history)})},SET_MODEL:{actions:t({history:({event:e})=>Y(e.model)})},UNDO:{actions:t({history:({context:e})=>Z(e.history)})}}}}}),Re=e=>{if(e.subject===null||e.intent===null)throw Error(`drop requires a subject and a drop intent`);return{intent:e.intent,subject:e.subject}},ze=r({guards:{hasValidDrop:({context:e})=>e.subject!==null&&e.intent!==null},types:{context:{},emitted:{},events:{}}}).createMachine({context:{intent:null,subject:null},id:`dragDock`,initial:`idle`,states:{dragging:{on:{CANCEL:{target:`idle`},DROP:[{actions:n(({context:e})=>{let{intent:t,subject:n}=Re(e);return{action:n.kind===`external`?{index:t.index,location:t.location,tab:n.tab,targetId:t.targetId,type:`addNode`}:{index:t.index,location:t.location,sourceId:n.id,targetId:t.targetId,type:n.kind===`tabset`?`moveTabset`:`moveNode`},type:`COMMIT`}}),guard:`hasValidDrop`,target:`idle`},{target:`idle`}],OVER:{actions:t({intent:({event:e})=>e.intent})}}},idle:{entry:t({intent:null,subject:null}),on:{START:{actions:t({subject:({event:e})=>e.subject}),target:`dragging`}}}}}),$=e=>{let t=B(g.parse(e)),n=j(t);return n.length>0&&console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${n.join(`, `)}`),t},Be=e=>JSON.stringify(e),Ve=e=>$(JSON.parse(e)),He=(e,t=`column`)=>{let n=[];S(e.layout,n);let r=[];for(let e of n)r.push({...e,weight:1});return B({...e,layout:{children:r,id:e.layout.id,orientation:t,type:`row`},maximizedTabsetId:void 0})};export{ve as DEFAULT_SNAP_THRESHOLD,Pe as HISTORY_LIMIT,re as actionSchema,Ie as canRedo,Fe as canUndo,b as collectRoots,C as collectTabsets,S as collectTabsetsInRow,Y as createHistory,y as createNodeId,ie as createTabId,Le as dashfooMachine,g as dashfooSchema,c as dimensionSchema,X as dispatch,v as dockLocationSchema,ge as dockZonePolygons,ze as dragDockMachine,a as edgeSchema,O as findAttributedNode,j as findDuplicateIds,x as findFloat,k as findRootContaining,E as findRow,T as findTab,w as findTabset,A as findTabsetParent,de as floatNode,m as floatNodeSchema,Ve as fromJSON,p as geometrySchema,ae as getFirstTabset,h as globalAttributesSchema,i as jsonValueSchema,fe as model,_ as mutableNodeAttrsSchema,B as normalize,s as orientationSchema,$ as parseModel,Q as redo,J as reducer,he as resolveDockTarget,I as resolveSnapTargets,ue as row,f as rowNodeSchema,ye as snapEnabled,l as snapSchema,xe as snapSizes,He as stackModel,ce as tab,u as tabNodeSchema,le as tabset,d as tabsetNodeSchema,Be as toJSON,Z as undo,o as unitSchema,_e as zoneRect};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/model/schema.ts","../src/state/actions.ts","../src/model/ids.ts","../src/model/tree.ts","../src/model/builders.ts","../src/geometry/geometry.ts","../src/model/invariants.ts","../src/state/reducer.ts","../src/state/history.ts","../src/machines/dashfoo-machine.ts","../src/machines/drag-dock-machine.ts","../src/model/serialize.ts","../src/model/stack.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// A JSON-serializable value. Per-tab `config` is validated against this so the\n// model stays losslessly serializable (a function or symbol in config fails).\ntype JsonValue = string | number | boolean | null | Array<JsonValue> | { [key: string]: JsonValue };\n\nconst jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.array(jsonValueSchema),\n z.record(z.string(), jsonValueSchema),\n ]),\n);\n\nconst edgeSchema = z.enum([\"top\", \"bottom\", \"left\", \"right\"]);\nconst unitSchema = z.enum([\"px\", \"%\", \"em\", \"rem\", \"vh\", \"vw\"]);\nconst orientationSchema = z.enum([\"row\", \"column\"]);\n\nconst dimensionSchema = z.object({\n unit: unitSchema,\n value: z.number(),\n});\n\nconst tabNodeSchema = z.object({\n component: z.string(),\n config: jsonValueSchema.optional(),\n enableClose: z.boolean().optional(),\n enableDrag: z.boolean().optional(),\n enableRename: z.boolean().optional(),\n id: z.string(),\n name: z.string(),\n type: z.literal(\"tab\"),\n});\n\nconst tabsetNodeSchema = z.object({\n children: z.array(tabNodeSchema),\n enableClose: z.boolean().optional(),\n enableMaximize: z.boolean().optional(),\n id: z.string(),\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n name: z.string().optional(),\n selected: z.number().int(),\n type: z.literal(\"tabset\"),\n weight: z.number().optional(),\n});\n\n// RowNode is recursive (a row may contain rows), so its type is declared\n// explicitly and the schema is built with z.lazy.\ntype RowNode = {\n children: Array<RowNode | z.infer<typeof tabsetNodeSchema>>;\n id: string;\n max?: z.infer<typeof dimensionSchema>;\n min?: z.infer<typeof dimensionSchema>;\n orientation: z.infer<typeof orientationSchema>;\n type: \"row\";\n weight?: number;\n};\n\nconst rowNodeSchema: z.ZodType<RowNode> = z.lazy(() =>\n z.object({\n children: z.array(z.union([rowNodeSchema, tabsetNodeSchema])),\n id: z.string(),\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n orientation: orientationSchema,\n type: z.literal(\"row\"),\n weight: z.number().optional(),\n }),\n);\n\nconst globalAttributesSchema = z.object({\n enableSplitDock: z.boolean().optional(),\n enableSplitResize: z.boolean().optional(),\n splitterSize: z.number().optional(),\n tabEnableClose: z.boolean().optional(),\n tabEnableDrag: z.boolean().optional(),\n tabEnableRename: z.boolean().optional(),\n tabLocation: z.enum([\"top\", \"bottom\"]).optional(),\n tabSetEnableMaximize: z.boolean().optional(),\n tabSetEnableTabStrip: z.boolean().optional(),\n tabSetMinSize: z.number().optional(),\n});\n\nconst dashfooSchema = z.object({\n activeTabsetId: z.string().optional(),\n global: globalAttributesSchema,\n layout: rowNodeSchema,\n maximizedTabsetId: z.string().optional(),\n // The persisted-payload format version, pinned by the schema itself: any\n // future format change bumps the literal, so foreign payloads fail validation.\n version: z.literal(1),\n});\n\ntype Json = JsonValue;\ntype Edge = z.infer<typeof edgeSchema>;\ntype Unit = z.infer<typeof unitSchema>;\ntype Orientation = z.infer<typeof orientationSchema>;\ntype Dimension = z.infer<typeof dimensionSchema>;\ntype TabNode = z.infer<typeof tabNodeSchema>;\ntype TabsetNode = z.infer<typeof tabsetNodeSchema>;\ntype GlobalAttributes = z.infer<typeof globalAttributesSchema>;\ntype Dashfoo = z.infer<typeof dashfooSchema>;\ntype Node = RowNode | TabsetNode | TabNode;\n\nexport {\n dashfooSchema,\n dimensionSchema,\n edgeSchema,\n globalAttributesSchema,\n jsonValueSchema,\n orientationSchema,\n rowNodeSchema,\n tabNodeSchema,\n tabsetNodeSchema,\n unitSchema,\n};\n\nexport type {\n Dashfoo,\n Dimension,\n Edge,\n GlobalAttributes,\n Json,\n Node,\n Orientation,\n RowNode,\n TabNode,\n TabsetNode,\n Unit,\n};\n","import { z } from \"zod\";\n\nimport {\n dimensionSchema,\n globalAttributesSchema,\n orientationSchema,\n tabNodeSchema,\n tabsetNodeSchema,\n} from \"../model/schema\";\n\nconst mutableTabAttrsSchema = tabNodeSchema\n .pick({\n config: true,\n enableClose: true,\n enableDrag: true,\n enableRename: true,\n name: true,\n })\n .partial();\n\nconst mutableTabsetAttrsSchema = tabsetNodeSchema\n .pick({\n enableClose: true,\n enableMaximize: true,\n max: true,\n min: true,\n selected: true,\n weight: true,\n })\n .partial();\n\nconst mutableRowAttrsSchema = z\n .object({\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n orientation: orientationSchema,\n weight: z.number(),\n })\n .partial();\n\nconst mutableNodeAttrsSchema = z.union([\n mutableTabAttrsSchema,\n mutableTabsetAttrsSchema,\n mutableRowAttrsSchema,\n]);\n\n// Every document mutation is one of these immutable, discriminated actions. The\n// reducer is exhaustive over this union; the React layer validates untrusted\n// payloads against actionSchema before dispatch.\nconst dockLocationSchema = z.enum([\n \"center\",\n \"split-bottom\",\n \"split-left\",\n \"split-right\",\n \"split-top\",\n]);\n\nconst actionSchema = z.discriminatedUnion(\"type\", [\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n tab: tabNodeSchema,\n targetId: z.string(),\n type: z.literal(\"addNode\"),\n }),\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n sourceId: z.string(),\n targetId: z.string(),\n type: z.literal(\"moveNode\"),\n }),\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n sourceId: z.string(),\n targetId: z.string(),\n type: z.literal(\"moveTabset\"),\n }),\n z.object({ index: z.number().int(), tabsetId: z.string(), type: z.literal(\"selectTab\") }),\n z.object({ tabsetId: z.string(), type: z.literal(\"setActiveTabset\") }),\n z.object({ tabsetId: z.string().nullable(), type: z.literal(\"setMaximizedTabset\") }),\n z.object({ name: z.string(), tabId: z.string(), type: z.literal(\"renameTab\") }),\n z.object({ tabId: z.string(), type: z.literal(\"deleteTab\") }),\n z.object({ tabsetId: z.string(), type: z.literal(\"deleteTabset\") }),\n z.object({\n rowId: z.string(),\n type: z.literal(\"adjustSplit\"),\n weights: z.array(z.number()),\n }),\n z.object({\n attrs: mutableNodeAttrsSchema,\n nodeId: z.string(),\n type: z.literal(\"updateNodeAttributes\"),\n }),\n z.object({ attrs: globalAttributesSchema.partial(), type: z.literal(\"updateGlobalAttributes\") }),\n]);\n\ntype DockLocation = z.infer<typeof dockLocationSchema>;\ntype MutableNodeAttrs = z.infer<typeof mutableNodeAttrsSchema>;\ntype Action = z.infer<typeof actionSchema>;\n\n// Where a drag will land: the dock location, the target tabset, and (for a tab\n// stack) the slot index. Co-located with DockLocation; the drag machine and the\n// React adapter both consume it.\ntype DropIntent = { index?: number; location: DockLocation; targetId: string };\n\nexport { actionSchema, dockLocationSchema, mutableNodeAttrsSchema };\nexport type { Action, DockLocation, DropIntent, MutableNodeAttrs };\n","// Unique ids for nodes and tabs. The reducer uses these for split/dock surgery;\n// they are public so consumers building addNode/moveNode payloads (or seed\n// models) have one blessed generator instead of rolling their own.\nconst createNodeId = (prefix = \"node\"): string => `${prefix}-${crypto.randomUUID()}`;\n\nconst createTabId = (): string => createNodeId(\"tab\");\n\nexport { createNodeId, createTabId };\n","import type { Dashfoo, RowNode, TabNode, TabsetNode } from \"./schema\";\n\n// A tab lives inside a tabset.\ntype TabContainer = TabsetNode;\ntype TabLocation = { container: TabContainer; index: number; tab: TabNode };\n\nconst collectTabsetsInRow = (row: RowNode, acc: Array<TabsetNode>): void => {\n for (const child of row.children) {\n if (child.type === \"tabset\") {\n acc.push(child);\n } else {\n collectTabsetsInRow(child, acc);\n }\n }\n};\n\n// Every tabset in the layout tree, depth-first.\nconst collectTabsets = (model: Dashfoo): Array<TabsetNode> => {\n const acc: Array<TabsetNode> = [];\n collectTabsetsInRow(model.layout, acc);\n return acc;\n};\n\nconst getFirstTabset = (model: Dashfoo): TabsetNode | undefined => collectTabsets(model)[0];\n\nconst findTabset = (model: Dashfoo, tabsetId: string): TabsetNode | undefined =>\n collectTabsets(model).find((tabset) => tabset.id === tabsetId);\n\nconst findTabInContainer = (container: TabContainer, tabId: string): TabLocation | undefined => {\n const index = container.children.findIndex((tab) => tab.id === tabId);\n if (index === -1) {\n return undefined;\n }\n\n const tab = container.children[index];\n if (!tab) {\n return undefined;\n }\n\n return { container, index, tab };\n};\n\n// Locate a tab anywhere in the model — searching every tabset — returning the\n// tab, its container, and its index within that container.\nconst findTab = (model: Dashfoo, tabId: string): TabLocation | undefined => {\n for (const tabset of collectTabsets(model)) {\n const found = findTabInContainer(tabset, tabId);\n if (found) {\n return found;\n }\n }\n\n return undefined;\n};\n\ntype AttributedNode = RowNode | TabNode | TabsetNode;\n\n// Find a row anywhere in the layout tree by id.\nconst findRow = (row: RowNode, id: string): RowNode | undefined => {\n if (row.id === id) {\n return row;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findRow(child, id);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst findAttributedNodeInRow = (row: RowNode, id: string): AttributedNode | undefined => {\n if (row.id === id) {\n return row;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findAttributedNodeInRow(child, id);\n if (found) {\n return found;\n }\n continue;\n }\n if (child.id === id) {\n return child;\n }\n const tab = child.children.find((candidate) => candidate.id === id);\n if (tab) {\n return tab;\n }\n }\n return undefined;\n};\n\n// Locate any attributed node (row, tabset, or tab) by id — backs updateNodeAttributes.\nconst findAttributedNode = (model: Dashfoo, id: string): AttributedNode | undefined =>\n findAttributedNodeInRow(model.layout, id);\n\n// The row holding a tabset, and the tabset's index within it — backs split placement.\nconst findTabsetParent = (\n row: RowNode,\n tabsetId: string,\n): { index: number; parent: RowNode } | undefined => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n return { index, parent: row };\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findTabsetParent(child, tabsetId);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst collectIdsInRow = (row: RowNode, acc: Array<string>): void => {\n acc.push(row.id);\n for (const child of row.children) {\n if (child.type === \"row\") {\n collectIdsInRow(child, acc);\n } else {\n acc.push(child.id);\n for (const tab of child.children) {\n acc.push(tab.id);\n }\n }\n }\n};\n\n// Ids that appear more than once across the whole model (rows, tabsets, tabs).\n// Duplicate ids produce duplicate React keys and corrupt the resize/drag\n// plumbing, so this backs a load-time diagnostic.\nconst findDuplicateIds = (model: Dashfoo): Array<string> => {\n const ids: Array<string> = [];\n collectIdsInRow(model.layout, ids);\n const seen = new Set<string>();\n const duplicates = new Set<string>();\n for (const id of ids) {\n if (seen.has(id)) {\n duplicates.add(id);\n }\n seen.add(id);\n }\n return [...duplicates];\n};\n\nexport {\n collectTabsets,\n findAttributedNode,\n findDuplicateIds,\n findRow,\n findTab,\n findTabset,\n findTabsetParent,\n getFirstTabset,\n};\nexport type { AttributedNode, TabContainer, TabLocation };\n","// Ergonomic constructors for the layout model, so consumers seed a Dashfoo\n// without hand-writing `type`/`version`/`selected` boilerplate or nested\n// literals. Every builder fills the required-but-mechanical fields and leaves\n// the meaningful ones (ids that get referenced, weights) to the caller. Output\n// is schema-valid by construction; id uniqueness across reused components is\n// the caller's responsibility (tabs default their id to the component name), so\n// `model` warns once if it detects duplicate node ids.\n\nimport { createNodeId } from \"./ids\";\nimport type {\n Dashfoo,\n Dimension,\n GlobalAttributes,\n Json,\n RowNode,\n TabNode,\n TabsetNode,\n} from \"./schema\";\nimport { findDuplicateIds } from \"./tree\";\n\nconst toDimension = (value: number | Dimension): Dimension =>\n typeof value === \"number\" ? { unit: \"px\", value } : value;\n\ntype TabOptions = {\n config?: Json;\n enableClose?: boolean;\n enableDrag?: boolean;\n enableRename?: boolean;\n id?: string;\n};\n\n// The id defaults to the component name — the common case where a layout holds\n// one tab per component and references it by that name.\nconst tab = (component: string, name: string, options: TabOptions = {}): TabNode => ({\n component,\n name,\n type: \"tab\",\n ...options,\n id: options.id ?? component,\n});\n\ntype TabsetOptions = {\n enableClose?: boolean;\n enableMaximize?: boolean;\n id?: string;\n max?: number | Dimension;\n min?: number | Dimension;\n name?: string;\n selected?: number;\n weight?: number;\n};\n\nconst tabset = (children: Array<TabNode>, options: TabsetOptions = {}): TabsetNode => {\n const { max, min, ...rest } = options;\n return {\n children,\n type: \"tabset\",\n ...rest,\n ...(max === undefined ? {} : { max: toDimension(max) }),\n ...(min === undefined ? {} : { min: toDimension(min) }),\n id: options.id ?? createNodeId(\"tabset\"),\n selected: options.selected ?? 0,\n };\n};\n\ntype RowOptions = {\n id?: string;\n max?: number | Dimension;\n min?: number | Dimension;\n orientation?: RowNode[\"orientation\"];\n weight?: number;\n};\n\nconst row = (children: RowNode[\"children\"], options: RowOptions = {}): RowNode => {\n const { max, min, ...rest } = options;\n return {\n children,\n type: \"row\",\n ...rest,\n ...(max === undefined ? {} : { max: toDimension(max) }),\n ...(min === undefined ? {} : { min: toDimension(min) }),\n id: options.id ?? createNodeId(\"row\"),\n orientation: options.orientation ?? \"row\",\n };\n};\n\ntype ModelOptions = {\n activeTabsetId?: string;\n global?: GlobalAttributes;\n maximizedTabsetId?: string;\n};\n\nconst model = (layout: RowNode, options: ModelOptions = {}): Dashfoo => {\n const built: Dashfoo = {\n layout,\n version: 1,\n ...options,\n global: options.global ?? {},\n };\n const duplicates = findDuplicateIds(built);\n if (duplicates.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(\n `[dashfoo] builder produced duplicate node ids: ${duplicates.join(\", \")} — pass explicit ids when reusing a component`,\n );\n }\n return built;\n};\n\nexport { model, row, tab, tabset };\nexport type { ModelOptions, RowOptions, TabOptions, TabsetOptions };\n","import type { Edge } from \"../model/schema\";\nimport type { DockLocation } from \"../state/actions\";\n\ntype Point = { x: number; y: number };\ntype Rect = { height: number; width: number; x: number; y: number };\ntype DockTarget = { kind: \"tab\" } | { edge: Edge; kind: \"split\" };\ntype BandOptions = { bandFraction?: number };\ntype DockZone = { location: DockLocation; points: Array<Point> };\n\n// Shared by resolveDockTarget and dockZonePolygons so the painted map and the\n// live hit-test can never disagree on where the edge bands end.\nconst DEFAULT_BAND_FRACTION = 0.22;\n\n// Fractional distance of the pointer from each of the four edges of a rect.\nconst edgeDistances = (\n pointer: Point,\n rect: Rect,\n): { bottom: number; left: number; right: number; top: number } => {\n const fx = (pointer.x - rect.x) / rect.width;\n const fy = (pointer.y - rect.y) / rect.height;\n return { bottom: 1 - fy, left: fx, right: 1 - fx, top: fy };\n};\n\nconst closestEdge = (d: { bottom: number; left: number; right: number; top: number }): Edge => {\n const min = Math.min(d.left, d.right, d.top, d.bottom);\n if (min === d.left) {\n return \"left\";\n }\n if (min === d.right) {\n return \"right\";\n }\n if (min === d.top) {\n return \"top\";\n }\n return \"bottom\";\n};\n\n// Where a drag dropped over a tabset should land: stacked as a tab when the\n// pointer is in the center, or splitting the tabset when it is within an outer\n// band (default 22%) of one of the four edges — the closer edge wins in corners.\nconst resolveDockTarget = (pointer: Point, rect: Rect, opts?: BandOptions): DockTarget => {\n const band = opts?.bandFraction ?? DEFAULT_BAND_FRACTION;\n // a zero-size tabset has no meaningful edges; dividing by its width/height\n // yields NaN distances that would silently resolve to a bogus split.\n if (rect.width <= 0 || rect.height <= 0) {\n return { kind: \"tab\" };\n }\n const distances = edgeDistances(pointer, rect);\n const min = Math.min(distances.left, distances.right, distances.top, distances.bottom);\n\n if (min > band) {\n return { kind: \"tab\" };\n }\n return { edge: closestEdge(distances), kind: \"split\" };\n};\n\n// The full hit-region partition of a tabset for resolveDockTarget: four edge\n// trapezoids and the inner center rect. Seams between adjacent edge zones sit\n// where the fractional edge distances tie — straight diagonals from each rect\n// corner to the matching inner-rect corner — so the map is an exact picture of\n// which location a pointer anywhere over the rect resolves to. A zero-size rect\n// collapses to a single center polygon, mirroring resolveDockTarget's fallback.\nconst dockZonePolygons = (rect: Rect, opts?: BandOptions): Array<DockZone> => {\n const { height: h, width: w, x, y } = rect;\n const nw: Point = { x, y };\n const ne: Point = { x: x + w, y };\n const se: Point = { x: x + w, y: y + h };\n const sw: Point = { x, y: y + h };\n if (w <= 0 || h <= 0) {\n return [{ location: \"center\", points: [nw, ne, se, sw] }];\n }\n const band = opts?.bandFraction ?? DEFAULT_BAND_FRACTION;\n const bx = band * w;\n const by = band * h;\n const innerNw: Point = { x: x + bx, y: y + by };\n const innerNe: Point = { x: x + w - bx, y: y + by };\n const innerSe: Point = { x: x + w - bx, y: y + h - by };\n const innerSw: Point = { x: x + bx, y: y + h - by };\n return [\n { location: \"center\", points: [innerNw, innerNe, innerSe, innerSw] },\n { location: \"split-left\", points: [nw, innerNw, innerSw, sw] },\n { location: \"split-right\", points: [ne, se, innerSe, innerNe] },\n { location: \"split-top\", points: [nw, ne, innerNe, innerNw] },\n { location: \"split-bottom\", points: [sw, innerSw, innerSe, se] },\n ];\n};\n\n// The region the dock indicator highlights for a given location over a tabset:\n// the whole tabset for a center stack, the matching half for a split.\nconst zoneRect = (rect: Rect, location: DockLocation): Rect => {\n const halfW = rect.width / 2;\n const halfH = rect.height / 2;\n switch (location) {\n case \"split-bottom\": {\n return { height: halfH, width: rect.width, x: rect.x, y: rect.y + halfH };\n }\n case \"split-left\": {\n return { height: rect.height, width: halfW, x: rect.x, y: rect.y };\n }\n case \"split-right\": {\n return { height: rect.height, width: halfW, x: rect.x + halfW, y: rect.y };\n }\n case \"split-top\": {\n return { height: halfH, width: rect.width, x: rect.x, y: rect.y };\n }\n default: {\n return rect;\n }\n }\n};\n\nexport { dockZonePolygons, resolveDockTarget, zoneRect };\nexport type { BandOptions, DockTarget, DockZone, Point, Rect };\n","import type { Dashfoo, RowNode, TabsetNode } from \"./schema\";\nimport { collectTabsets } from \"./tree\";\n\nconst clampSelected = (length: number, selected: number): number => {\n if (length === 0) {\n return 0;\n }\n // coerce to a non-negative integer first to match the schema's z.number().int():\n // a persisted 1.5 or NaN must not survive as a fractional/NaN index.\n const base = Number.isFinite(selected) ? Math.trunc(selected) : 0;\n if (base < 0) {\n return 0;\n }\n if (base > length - 1) {\n return length - 1;\n }\n return base;\n};\n\nconst normalizeTabset = (tabset: TabsetNode): TabsetNode => ({\n ...tabset,\n selected: clampSelected(tabset.children.length, tabset.selected),\n});\n\n// Drop empty tabsets and empty rows, recurse into child rows, and simplify a\n// single-child row by lifting its lone child (which inherits the lifted row's\n// weight so sizing is preserved).\nconst normalizeRowChildren = (children: RowNode[\"children\"]): RowNode[\"children\"] => {\n const out: RowNode[\"children\"] = [];\n\n for (const child of children) {\n if (child.type === \"tabset\") {\n if (child.children.length > 0) {\n out.push(normalizeTabset(child));\n }\n continue;\n }\n\n const grandchildren = normalizeRowChildren(child.children);\n if (grandchildren.length === 0) {\n continue;\n }\n if (grandchildren.length === 1) {\n const only = grandchildren[0];\n if (only !== undefined) {\n out.push(child.weight === undefined ? only : { ...only, weight: child.weight });\n }\n continue;\n }\n out.push({ ...child, children: grandchildren });\n }\n\n return out;\n};\n\n// The root is always a row; if it reduces to a single child that is itself a\n// row, absorb that row (adopt its children + orientation) to avoid redundant\n// nesting.\nconst normalizeLayout = (root: RowNode): RowNode => {\n let children = normalizeRowChildren(root.children);\n let orientation = root.orientation;\n\n let inner = children[0];\n while (children.length === 1 && inner?.type === \"row\") {\n children = inner.children;\n orientation = inner.orientation;\n inner = children[0];\n }\n\n return { ...root, children, orientation };\n};\n\n// Self-healing pass run after every action so the tree stays canonical: no empty\n// tabsets or rows, no redundant single-child rows, selected indices in range,\n// and active/maximized ids that always point at an existing tabset.\nconst normalize = (model: Dashfoo): Dashfoo => {\n const layout = normalizeLayout(model.layout);\n const withLayout: Dashfoo = { ...model, layout };\n\n const tabsetIds = new Set(collectTabsets(withLayout).map((tabset) => tabset.id));\n const firstTabsetId = collectTabsets(withLayout)[0]?.id;\n\n const activeTabsetId =\n model.activeTabsetId !== undefined && tabsetIds.has(model.activeTabsetId)\n ? model.activeTabsetId\n : firstTabsetId;\n\n const maximizedTabsetId =\n model.maximizedTabsetId !== undefined && tabsetIds.has(model.maximizedTabsetId)\n ? model.maximizedTabsetId\n : undefined;\n\n return { ...withLayout, activeTabsetId, maximizedTabsetId };\n};\n\nexport { normalize };\n","import { createNodeId } from \"../model/ids\";\nimport { normalize } from \"../model/invariants\";\nimport type { Dashfoo, RowNode, TabNode, TabsetNode } from \"../model/schema\";\nimport { findAttributedNode, findRow, findTab, findTabset, findTabsetParent } from \"../model/tree\";\n\nimport type { Action, DockLocation } from \"./actions\";\n\nconst assertNever = (value: never): never => {\n throw new Error(`Unhandled action: ${JSON.stringify(value)}`);\n};\n\nconst removeTabset = (row: RowNode, tabsetId: string): boolean => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n row.children.splice(index, 1);\n return true;\n }\n for (const child of row.children) {\n if (child.type === \"row\" && removeTabset(child, tabsetId)) {\n return true;\n }\n }\n return false;\n};\n\n// Detach a tabset from the tree and return it (for moving a whole tabset).\nconst removeTabsetReturning = (row: RowNode, tabsetId: string): TabsetNode | undefined => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n const [removed] = row.children.splice(index, 1);\n return removed?.type === \"tabset\" ? removed : undefined;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = removeTabsetReturning(child, tabsetId);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst splitOrientation = (location: DockLocation): \"column\" | \"row\" =>\n location === \"split-left\" || location === \"split-right\" ? \"row\" : \"column\";\n\nconst splitsBefore = (location: DockLocation): boolean =>\n location === \"split-left\" || location === \"split-top\";\n\n// Place an (already-detached) tabset beside the target for a split-* location:\n// reuse the parent row when the orientation already matches, else wrap the target\n// in a new row. Shared by insertTab (a fresh tabset) and moveTabset (an existing\n// one). Splits the target's space in half.\nconst placeBesideTarget = (\n draft: Dashfoo,\n placed: TabsetNode,\n targetId: string,\n location: DockLocation,\n): void => {\n const targetTabset = findTabset(draft, targetId);\n const found = findTabsetParent(draft.layout, targetId);\n if (!targetTabset || !found) {\n return;\n }\n\n const orientation = splitOrientation(location);\n const before = splitsBefore(location);\n\n if (found.parent.orientation === orientation) {\n const targetWeight = targetTabset.weight ?? 100;\n targetTabset.weight = targetWeight / 2;\n placed.weight = targetWeight / 2;\n found.parent.children.splice(before ? found.index : found.index + 1, 0, placed);\n } else {\n const targetWeight = targetTabset.weight;\n targetTabset.weight = 50;\n placed.weight = 50;\n const newRow: RowNode = {\n children: before ? [placed, targetTabset] : [targetTabset, placed],\n id: createNodeId(\"row\"),\n orientation,\n type: \"row\",\n };\n if (targetWeight !== undefined) {\n newRow.weight = targetWeight;\n }\n found.parent.children.splice(found.index, 1, newRow);\n }\n\n draft.activeTabsetId = placed.id;\n};\n\n// Insert a tab into the model at a dock target. center stacks it into the target\n// tabset; split-* creates a new tabset beside the target.\ntype DropTarget = { id: string; index?: number; location: DockLocation };\n\nconst insertTab = (draft: Dashfoo, tab: TabNode, target: DropTarget): void => {\n const { id: targetId, index, location } = target;\n const targetTabset = findTabset(draft, targetId);\n if (!targetTabset) {\n return;\n }\n\n if (location === \"center\") {\n const at = index ?? targetTabset.children.length;\n targetTabset.children.splice(at, 0, tab);\n targetTabset.selected = at;\n return;\n }\n\n const newTabset: TabsetNode = {\n children: [tab],\n id: createNodeId(\"tabset\"),\n selected: 0,\n type: \"tabset\",\n weight: 50,\n };\n placeBesideTarget(draft, newTabset, targetId, location);\n};\n\nconst applyAction = (draft: Dashfoo, action: Action): void => {\n switch (action.type) {\n case \"addNode\": {\n insertTab(draft, action.tab, {\n id: action.targetId,\n index: action.index,\n location: action.location,\n });\n return;\n }\n case \"adjustSplit\": {\n const row = findRow(draft.layout, action.rowId);\n if (!row) {\n return;\n }\n for (let index = 0; index < action.weights.length; index++) {\n const child = row.children[index];\n const weight = action.weights[index];\n if (!child || weight === undefined) {\n continue;\n }\n child.weight = weight;\n }\n return;\n }\n case \"deleteTab\": {\n const location = findTab(draft, action.tabId);\n if (location) {\n location.container.children.splice(location.index, 1);\n }\n return;\n }\n case \"deleteTabset\": {\n removeTabset(draft.layout, action.tabsetId);\n return;\n }\n case \"moveNode\": {\n const source = findTab(draft, action.sourceId);\n if (!source) {\n return;\n }\n // Remove the source first; action.index is the destination slot measured\n // against the tabs that remain (the drag adapter excludes the dragged tab),\n // so it indexes the post-removal array directly — no reorder adjustment.\n const [removed] = source.container.children.splice(source.index, 1);\n if (!removed) {\n return;\n }\n insertTab(draft, removed, {\n id: action.targetId,\n index: action.index,\n location: action.location,\n });\n return;\n }\n case \"moveTabset\": {\n if (action.sourceId === action.targetId) {\n return;\n }\n const source = findTabset(draft, action.sourceId);\n if (!source) {\n return;\n }\n if (action.location === \"center\") {\n const target = findTabset(draft, action.targetId);\n if (target) {\n target.children.push(...source.children);\n removeTabset(draft.layout, action.sourceId);\n }\n return;\n }\n // split-* moves the whole tabset beside the target.\n if (action.location.startsWith(\"split-\")) {\n const detached = removeTabsetReturning(draft.layout, action.sourceId);\n if (detached) {\n placeBesideTarget(draft, detached, action.targetId, action.location);\n }\n }\n return;\n }\n case \"renameTab\": {\n const location = findTab(draft, action.tabId);\n if (location) {\n location.tab.name = action.name;\n }\n return;\n }\n case \"selectTab\": {\n const tabset = findTabset(draft, action.tabsetId);\n if (tabset) {\n tabset.selected = action.index;\n }\n return;\n }\n case \"setActiveTabset\": {\n if (findTabset(draft, action.tabsetId)) {\n draft.activeTabsetId = action.tabsetId;\n }\n return;\n }\n case \"setMaximizedTabset\": {\n draft.maximizedTabsetId = action.tabsetId ?? undefined;\n return;\n }\n case \"updateGlobalAttributes\": {\n Object.assign(draft.global, action.attrs);\n return;\n }\n case \"updateNodeAttributes\": {\n const node = findAttributedNode(draft, action.nodeId);\n if (node) {\n Object.assign(node, action.attrs);\n }\n return;\n }\n default: {\n assertNever(action);\n }\n }\n};\n\n// The canonical engine: deep-copy the model (so the input is never mutated),\n// apply one action to the copy, then run the self-healing invariants so the\n// result is always a valid, canonical model.\nconst reducer = (model: Dashfoo, action: Action): Dashfoo => {\n const draft = structuredClone(model);\n applyAction(draft, action);\n return normalize(draft);\n};\n\nexport { reducer };\n","import type { Dashfoo } from \"../model/schema\";\n\nimport type { Action } from \"./actions\";\nimport { reducer } from \"./reducer\";\n\n// past/present/future snapshots. Each committed action is its own undo step:\n// rrp v4's onLayoutChanged fires one adjustSplit per release, so there is no\n// per-frame burst to coalesce.\ntype History = {\n future: Array<Dashfoo>;\n past: Array<Dashfoo>;\n present: Dashfoo;\n};\n\n// Snapshots are full model clones, so an unbounded past grows memory for the\n// life of the session (every action — even a tab click — is one step). 100\n// steps is far beyond practical undo depth while keeping memory flat.\nconst HISTORY_LIMIT = 100;\n\nconst createHistory = (present: Dashfoo): History => ({\n future: [],\n past: [],\n present,\n});\n\nconst canUndo = (history: History): boolean => history.past.length > 0;\nconst canRedo = (history: History): boolean => history.future.length > 0;\n\nconst dispatch = (history: History, action: Action): History => {\n const present = reducer(history.present, action);\n return { future: [], past: [...history.past, history.present].slice(-HISTORY_LIMIT), present };\n};\n\nconst undo = (history: History): History => {\n const previous = history.past.at(-1);\n if (previous === undefined) {\n return history;\n }\n return {\n future: [history.present, ...history.future],\n past: history.past.slice(0, -1),\n present: previous,\n };\n};\n\nconst redo = (history: History): History => {\n const [next, ...rest] = history.future;\n if (next === undefined) {\n return history;\n }\n return {\n future: rest,\n past: [...history.past, history.present].slice(-HISTORY_LIMIT),\n present: next,\n };\n};\n\nexport { canRedo, canUndo, createHistory, dispatch, HISTORY_LIMIT, redo, undo };\nexport type { History };\n","import { assign, setup } from \"xstate\";\n\nimport type { Dashfoo } from \"../model/schema\";\nimport type { Action } from \"../state/actions\";\nimport type { History } from \"../state/history\";\nimport { createHistory, dispatch, redo, undo } from \"../state/history\";\n\ntype DashfooContext = { history: History };\n\ntype DashfooEvent =\n | { action: Action; type: \"DISPATCH\" }\n | { model: Dashfoo; type: \"SET_MODEL\" }\n | { type: \"REDO\" }\n | { type: \"UNDO\" };\n\ntype DashfooInput = { model: Dashfoo };\n\n// The document actor: it owns the undo/redo history (whose `present` is the live\n// model) and processes document mutations by invoking the pure reducer via the\n// history helpers. It has no finite states beyond `ready` — the document is data,\n// not a lifecycle — so every mutation is an event handled with assign.\nconst dashfooMachine = setup({\n types: {\n context: {} as DashfooContext,\n events: {} as DashfooEvent,\n input: {} as DashfooInput,\n },\n}).createMachine({\n context: ({ input }) => ({ history: createHistory(input.model) }),\n id: \"dashfoo\",\n initial: \"ready\",\n states: {\n ready: {\n on: {\n DISPATCH: {\n actions: assign({\n history: ({ context, event }) => dispatch(context.history, event.action),\n }),\n },\n REDO: {\n actions: assign({ history: ({ context }) => redo(context.history) }),\n },\n SET_MODEL: {\n actions: assign({ history: ({ event }) => createHistory(event.model) }),\n },\n UNDO: {\n actions: assign({ history: ({ context }) => undo(context.history) }),\n },\n },\n },\n },\n});\n\nexport { dashfooMachine };\nexport type { DashfooContext, DashfooEvent, DashfooInput };\n","import { assign, emit, setup } from \"xstate\";\n\nimport type { TabNode } from \"../model/schema\";\nimport type { Action, DropIntent } from \"../state/actions\";\n\n// Internal subjects reference a node already in the document; an external\n// subject carries the TabNode to insert on drop (a widget dragged in from\n// outside the layout), so its id never has to exist in the model.\ntype DragSubject =\n | { id: string; kind: \"tab\" | \"tabset\" }\n | { id: string; kind: \"external\"; tab: TabNode };\n\ntype DragContext = { intent: DropIntent | null; subject: DragSubject | null };\n\ntype DragEvent =\n | { intent: DropIntent | null; type: \"OVER\" }\n | { subject: DragSubject; type: \"START\" }\n | { type: \"CANCEL\" }\n | { type: \"DROP\" };\n\ntype DragEmitted = { action: Action; type: \"COMMIT\" };\n\nconst requireDrop = (context: DragContext): { intent: DropIntent; subject: DragSubject } => {\n if (context.subject === null || context.intent === null) {\n throw new Error(\"drop requires a subject and a drop intent\");\n }\n return { intent: context.intent, subject: context.subject };\n};\n\n// The drag/dock interaction lifecycle, driven by abstract events the dnd-kit\n// adapter maps from pointer/keyboard input. It owns transient drag state only and\n// never touches the document — on a valid drop it emits a COMMIT carrying the\n// resulting action (moveNode/moveTabset, or addNode for an external subject),\n// which the React layer forwards to the document machine.\nconst dragDockMachine = setup({\n guards: {\n hasValidDrop: ({ context }) => context.subject !== null && context.intent !== null,\n },\n types: {\n context: {} as DragContext,\n emitted: {} as DragEmitted,\n events: {} as DragEvent,\n },\n}).createMachine({\n context: { intent: null, subject: null },\n id: \"dragDock\",\n initial: \"idle\",\n states: {\n dragging: {\n on: {\n CANCEL: { target: \"idle\" },\n DROP: [\n {\n actions: emit(({ context }) => {\n const { intent, subject } = requireDrop(context);\n const action: Action =\n subject.kind === \"external\"\n ? {\n index: intent.index,\n location: intent.location,\n tab: subject.tab,\n targetId: intent.targetId,\n type: \"addNode\",\n }\n : {\n index: intent.index,\n location: intent.location,\n sourceId: subject.id,\n targetId: intent.targetId,\n type: subject.kind === \"tabset\" ? \"moveTabset\" : \"moveNode\",\n };\n return { action, type: \"COMMIT\" };\n }),\n guard: \"hasValidDrop\",\n target: \"idle\",\n },\n { target: \"idle\" },\n ],\n OVER: { actions: assign({ intent: ({ event }) => event.intent }) },\n },\n },\n idle: {\n entry: assign({ intent: null, subject: null }),\n on: {\n START: { actions: assign({ subject: ({ event }) => event.subject }), target: \"dragging\" },\n },\n },\n },\n});\n\nexport { dragDockMachine };\nexport type { DragContext, DragEmitted, DragEvent, DragSubject };\n","import { normalize } from \"./invariants\";\nimport type { Dashfoo } from \"./schema\";\nimport { dashfooSchema } from \"./schema\";\nimport { findDuplicateIds } from \"./tree\";\n\n// Validate an untrusted value against the schema and return a normalized,\n// canonical model. Throws if the value is not a valid model — including a\n// payload whose `version` is not the pinned `1`; warns (does not throw) on\n// duplicate ids, which corrupt React keys and the plumbing.\nconst parseModel = (value: unknown): Dashfoo => {\n const model = normalize(dashfooSchema.parse(value));\n const duplicates = findDuplicateIds(model);\n if (duplicates.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${duplicates.join(\", \")}`);\n }\n return model;\n};\n\nconst toJSON = (model: Dashfoo): string => JSON.stringify(model);\n\nconst fromJSON = (json: string): Dashfoo => parseModel(JSON.parse(json));\n\nexport { fromJSON, parseModel, toJSON };\n","import { normalize } from \"./invariants\";\nimport type { Dashfoo, Orientation, TabsetNode } from \"./schema\";\nimport { collectTabsets } from \"./tree\";\n\n// Flatten any layout into a single row (default a column) of all its tabsets, in\n// document order, each with equal weight, so nothing is lost on a narrow screen;\n// maximize is cleared. The building block for a mobile breakpoint:\n// `stackModel(desktopModel)`.\nconst stackModel = (model: Dashfoo, orientation: Orientation = \"column\"): Dashfoo => {\n // Equal weight so stacked panels share the space evenly. A loop (not map) so we\n // create fresh nodes without mutating the shared input tabsets.\n const children: Array<TabsetNode> = [];\n for (const tabset of collectTabsets(model)) {\n children.push({ ...tabset, weight: 1 });\n }\n\n return normalize({\n ...model,\n layout: { children, id: model.layout.id, orientation, type: \"row\" },\n maximizedTabsetId: undefined,\n });\n};\n\nexport { stackModel };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/model/schema.ts","../src/state/actions.ts","../src/model/ids.ts","../src/model/tree.ts","../src/model/builders.ts","../src/geometry/geometry.ts","../src/geometry/snap.ts","../src/model/invariants.ts","../src/state/surgery.ts","../src/state/floats.ts","../src/state/reducer.ts","../src/state/history.ts","../src/machines/dashfoo-machine.ts","../src/machines/drag-dock-machine.ts","../src/model/serialize.ts","../src/model/stack.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// A JSON-serializable value. Per-tab `config` is validated against this so the\n// model stays losslessly serializable (a function or symbol in config fails).\ntype JsonValue = string | number | boolean | null | Array<JsonValue> | { [key: string]: JsonValue };\n\nconst jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.array(jsonValueSchema),\n z.record(z.string(), jsonValueSchema),\n ]),\n);\n\nconst edgeSchema = z.enum([\"top\", \"bottom\", \"left\", \"right\"]);\nconst unitSchema = z.enum([\"px\", \"%\", \"em\", \"rem\", \"vh\", \"vw\"]);\nconst orientationSchema = z.enum([\"row\", \"column\"]);\n\nconst dimensionSchema = z.object({\n unit: unitSchema,\n value: z.number(),\n});\n\n// Magnetic snap config for split resize. The grid the dragged boundary snaps to is\n// the union of two optional sources: `step` (multiples of a fixed percent) and\n// `divisions` (even splits — multiples of 100/d, where d is the number or, for\n// \"panels\", the row's own child count, so a 3-panel row snaps to thirds and a\n// 4-panel row to quarters). `threshold` is the magnetic grab distance in percent.\n// An empty config (or a `step` of 0 with no `divisions`) disables snapping — used\n// to opt a single row out of an inherited global default.\nconst snapSchema = z.object({\n divisions: z.union([z.number().positive(), z.literal(\"panels\")]).optional(),\n step: z.number().min(0).optional(),\n threshold: z.number().positive().optional(),\n});\n\nconst tabNodeSchema = z.object({\n component: z.string(),\n config: jsonValueSchema.optional(),\n enableClose: z.boolean().optional(),\n enableDrag: z.boolean().optional(),\n enableRename: z.boolean().optional(),\n id: z.string(),\n name: z.string(),\n type: z.literal(\"tab\"),\n});\n\nconst tabsetNodeSchema = z.object({\n children: z.array(tabNodeSchema),\n enableClose: z.boolean().optional(),\n enableMaximize: z.boolean().optional(),\n id: z.string(),\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n name: z.string().optional(),\n selected: z.number().int(),\n type: z.literal(\"tabset\"),\n weight: z.number().optional(),\n});\n\n// RowNode is recursive (a row may contain rows), so its type is declared\n// explicitly and the schema is built with z.lazy.\ntype RowNode = {\n children: Array<RowNode | z.infer<typeof tabsetNodeSchema>>;\n id: string;\n max?: z.infer<typeof dimensionSchema>;\n min?: z.infer<typeof dimensionSchema>;\n orientation: z.infer<typeof orientationSchema>;\n snap?: z.infer<typeof snapSchema>;\n type: \"row\";\n weight?: number;\n};\n\nconst rowNodeSchema: z.ZodType<RowNode> = z.lazy(() =>\n z.object({\n children: z.array(z.union([rowNodeSchema, tabsetNodeSchema])),\n id: z.string(),\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n orientation: orientationSchema,\n snap: snapSchema.optional(),\n type: z.literal(\"row\"),\n weight: z.number().optional(),\n }),\n);\n\n// A floating panel's rectangle, in CSS pixels relative to the layout container.\n// Captured when the panel is floated and refreshed as the user drags or resizes\n// it, so the model round-trips the float's position.\nconst geometrySchema = z.object({\n height: z.number(),\n left: z.number(),\n top: z.number(),\n width: z.number(),\n});\n\n// A floating panel: a tabset (or split) lifted out of the docked layout into a\n// draggable, resizable overlay that still lives in the same app. It owns a full\n// RowNode layout (the same shape as the main root), so a float can hold a single\n// tab, several tabs, or nested splits and reuses the exact same rendering.\nconst floatNodeSchema = z.object({\n geometry: geometrySchema,\n id: z.string(),\n layout: rowNodeSchema,\n // Collapsed to a small chip (the window minimized); geometry is preserved so\n // restoring reopens it at its previous rect.\n minimized: z.boolean().optional(),\n name: z.string().optional(),\n type: z.literal(\"float\"),\n});\n\nconst globalAttributesSchema = z.object({\n enableSplitDock: z.boolean().optional(),\n enableSplitResize: z.boolean().optional(),\n snap: snapSchema.optional(),\n splitterSize: z.number().optional(),\n tabEnableClose: z.boolean().optional(),\n tabEnableDrag: z.boolean().optional(),\n tabEnableRename: z.boolean().optional(),\n tabLocation: z.enum([\"top\", \"bottom\"]).optional(),\n tabSetEnableMaximize: z.boolean().optional(),\n tabSetEnableTabStrip: z.boolean().optional(),\n tabSetMinSize: z.number().optional(),\n});\n\nconst dashfooSchema = z.object({\n activeTabsetId: z.string().optional(),\n global: globalAttributesSchema,\n layout: rowNodeSchema,\n maximizedTabsetId: z.string().optional(),\n // The persisted-payload format version, pinned by the schema itself: any\n // future format change bumps the literal, so foreign payloads fail validation.\n version: z.literal(1),\n // Floating panels, each owning its own layout subtree. Optional so existing\n // payloads (and hand-built models) stay valid; absent means \"nothing floating\".\n floats: z.array(floatNodeSchema).optional(),\n});\n\ntype Json = JsonValue;\ntype Edge = z.infer<typeof edgeSchema>;\ntype Unit = z.infer<typeof unitSchema>;\ntype Orientation = z.infer<typeof orientationSchema>;\ntype Dimension = z.infer<typeof dimensionSchema>;\ntype SnapConfig = z.infer<typeof snapSchema>;\ntype TabNode = z.infer<typeof tabNodeSchema>;\ntype TabsetNode = z.infer<typeof tabsetNodeSchema>;\ntype Geometry = z.infer<typeof geometrySchema>;\ntype FloatNode = z.infer<typeof floatNodeSchema>;\ntype GlobalAttributes = z.infer<typeof globalAttributesSchema>;\ntype Dashfoo = z.infer<typeof dashfooSchema>;\ntype Node = RowNode | TabsetNode | TabNode;\n\nexport {\n dashfooSchema,\n dimensionSchema,\n edgeSchema,\n floatNodeSchema,\n geometrySchema,\n globalAttributesSchema,\n jsonValueSchema,\n orientationSchema,\n rowNodeSchema,\n snapSchema,\n tabNodeSchema,\n tabsetNodeSchema,\n unitSchema,\n};\n\nexport type {\n Dashfoo,\n Dimension,\n Edge,\n FloatNode,\n Geometry,\n GlobalAttributes,\n Json,\n Node,\n Orientation,\n RowNode,\n SnapConfig,\n TabNode,\n TabsetNode,\n Unit,\n};\n","import { z } from \"zod\";\n\nimport {\n dimensionSchema,\n geometrySchema,\n globalAttributesSchema,\n orientationSchema,\n snapSchema,\n tabNodeSchema,\n tabsetNodeSchema,\n} from \"../model/schema\";\n\nconst mutableTabAttrsSchema = tabNodeSchema\n .pick({\n config: true,\n enableClose: true,\n enableDrag: true,\n enableRename: true,\n name: true,\n })\n .partial();\n\nconst mutableTabsetAttrsSchema = tabsetNodeSchema\n .pick({\n enableClose: true,\n enableMaximize: true,\n max: true,\n min: true,\n selected: true,\n weight: true,\n })\n .partial();\n\nconst mutableRowAttrsSchema = z\n .object({\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n orientation: orientationSchema,\n snap: snapSchema.optional(),\n weight: z.number(),\n })\n .partial();\n\nconst mutableNodeAttrsSchema = z.union([\n mutableTabAttrsSchema,\n mutableTabsetAttrsSchema,\n mutableRowAttrsSchema,\n]);\n\n// Every document mutation is one of these immutable, discriminated actions. The\n// reducer is exhaustive over this union; the React layer validates untrusted\n// payloads against actionSchema before dispatch.\nconst dockLocationSchema = z.enum([\n \"center\",\n \"split-bottom\",\n \"split-left\",\n \"split-right\",\n \"split-top\",\n]);\n\nconst actionSchema = z.discriminatedUnion(\"type\", [\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n tab: tabNodeSchema,\n targetId: z.string(),\n type: z.literal(\"addNode\"),\n }),\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n sourceId: z.string(),\n targetId: z.string(),\n type: z.literal(\"moveNode\"),\n }),\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n sourceId: z.string(),\n targetId: z.string(),\n type: z.literal(\"moveTabset\"),\n }),\n z.object({ index: z.number().int(), tabsetId: z.string(), type: z.literal(\"selectTab\") }),\n z.object({ tabsetId: z.string(), type: z.literal(\"setActiveTabset\") }),\n z.object({ tabsetId: z.string().nullable(), type: z.literal(\"setMaximizedTabset\") }),\n z.object({ name: z.string(), tabId: z.string(), type: z.literal(\"renameTab\") }),\n z.object({ tabId: z.string(), type: z.literal(\"deleteTab\") }),\n z.object({ tabsetId: z.string(), type: z.literal(\"deleteTabset\") }),\n z.object({\n rowId: z.string(),\n type: z.literal(\"adjustSplit\"),\n weights: z.array(z.number()),\n }),\n z.object({\n attrs: mutableNodeAttrsSchema,\n nodeId: z.string(),\n type: z.literal(\"updateNodeAttributes\"),\n }),\n z.object({ attrs: globalAttributesSchema.partial(), type: z.literal(\"updateGlobalAttributes\") }),\n // Float a single tab out of its tabset into a new floating panel. `geometry` is\n // the float's initial in-app rect; `floatId` lets the caller mint the id so the\n // model node and any UI tracking it agree.\n z.object({\n floatId: z.string().optional(),\n geometry: geometrySchema.optional(),\n tabId: z.string(),\n type: z.literal(\"floatTab\"),\n }),\n // Float a whole tabset (with all its tabs) into a new floating panel.\n z.object({\n floatId: z.string().optional(),\n geometry: geometrySchema.optional(),\n tabsetId: z.string(),\n type: z.literal(\"floatTabset\"),\n }),\n // Dock a floating panel's tabs back into the main layout, then drop the float.\n // `targetId`/`location` default to the active main tabset, center.\n z.object({\n floatId: z.string(),\n location: dockLocationSchema.optional(),\n targetId: z.string().optional(),\n type: z.literal(\"dockFloat\"),\n }),\n // Persist a float's rect as the user drags or resizes it.\n z.object({\n floatId: z.string(),\n geometry: geometrySchema,\n type: z.literal(\"moveFloat\"),\n }),\n // Collapse a float to a chip (minimized: true) or restore it (false).\n z.object({\n floatId: z.string(),\n minimized: z.boolean(),\n type: z.literal(\"setFloatMinimized\"),\n }),\n // Rename a floating panel (its window title).\n z.object({\n floatId: z.string(),\n name: z.string(),\n type: z.literal(\"renameFloat\"),\n }),\n]);\n\ntype DockLocation = z.infer<typeof dockLocationSchema>;\ntype MutableNodeAttrs = z.infer<typeof mutableNodeAttrsSchema>;\ntype Action = z.infer<typeof actionSchema>;\n\n// Where a drag will land: the dock location, the target tabset, and (for a tab\n// stack) the slot index. Co-located with DockLocation; the drag machine and the\n// React adapter both consume it.\ntype DropIntent = { index?: number; location: DockLocation; targetId: string };\n\nexport { actionSchema, dockLocationSchema, mutableNodeAttrsSchema };\nexport type { Action, DockLocation, DropIntent, MutableNodeAttrs };\n","// Unique ids for nodes and tabs. The reducer uses these for split/dock surgery;\n// they are public so consumers building addNode/moveNode payloads (or seed\n// models) have one blessed generator instead of rolling their own.\nconst createNodeId = (prefix = \"node\"): string => `${prefix}-${crypto.randomUUID()}`;\n\nconst createTabId = (): string => createNodeId(\"tab\");\n\nexport { createNodeId, createTabId };\n","import type { Dashfoo, FloatNode, RowNode, TabNode, TabsetNode } from \"./schema\";\n\n// A tab lives inside a tabset.\ntype TabContainer = TabsetNode;\ntype TabLocation = { container: TabContainer; index: number; tab: TabNode };\n\n// Every layout root: the main layout first, then each floating panel's layout.\n// Traversals fan out over this so a floated tabset is as reachable as a docked\n// one; \"first\" semantics (active tabset fallbacks) prefer the main layout.\nconst collectRoots = (model: Dashfoo): Array<RowNode> => [\n model.layout,\n ...(model.floats ?? []).map((float) => float.layout),\n];\n\nconst findFloat = (model: Dashfoo, floatId: string): FloatNode | undefined =>\n (model.floats ?? []).find((float) => float.id === floatId);\n\nconst collectTabsetsInRow = (row: RowNode, acc: Array<TabsetNode>): void => {\n for (const child of row.children) {\n if (child.type === \"tabset\") {\n acc.push(child);\n } else {\n collectTabsetsInRow(child, acc);\n }\n }\n};\n\n// Every tabset across all roots (main layout + windows), depth-first.\nconst collectTabsets = (model: Dashfoo): Array<TabsetNode> => {\n const acc: Array<TabsetNode> = [];\n for (const root of collectRoots(model)) {\n collectTabsetsInRow(root, acc);\n }\n return acc;\n};\n\nconst getFirstTabset = (model: Dashfoo): TabsetNode | undefined => collectTabsets(model)[0];\n\nconst findTabset = (model: Dashfoo, tabsetId: string): TabsetNode | undefined =>\n collectTabsets(model).find((tabset) => tabset.id === tabsetId);\n\nconst findTabInContainer = (container: TabContainer, tabId: string): TabLocation | undefined => {\n const index = container.children.findIndex((tab) => tab.id === tabId);\n if (index === -1) {\n return undefined;\n }\n\n const tab = container.children[index];\n if (!tab) {\n return undefined;\n }\n\n return { container, index, tab };\n};\n\n// Locate a tab anywhere in the model — searching every tabset — returning the\n// tab, its container, and its index within that container.\nconst findTab = (model: Dashfoo, tabId: string): TabLocation | undefined => {\n for (const tabset of collectTabsets(model)) {\n const found = findTabInContainer(tabset, tabId);\n if (found) {\n return found;\n }\n }\n\n return undefined;\n};\n\ntype AttributedNode = RowNode | TabNode | TabsetNode;\n\n// Find a row anywhere in the layout tree by id.\nconst findRow = (row: RowNode, id: string): RowNode | undefined => {\n if (row.id === id) {\n return row;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findRow(child, id);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst findAttributedNodeInRow = (row: RowNode, id: string): AttributedNode | undefined => {\n if (row.id === id) {\n return row;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findAttributedNodeInRow(child, id);\n if (found) {\n return found;\n }\n continue;\n }\n if (child.id === id) {\n return child;\n }\n // Single-id recursive search invoked once per lookup over small layout\n // trees — building a Map per call would cost more than it saves.\n // oxlint-disable-next-line react-doctor/js-index-maps\n const tab = child.children.find((candidate) => candidate.id === id);\n if (tab) {\n return tab;\n }\n }\n return undefined;\n};\n\n// Locate any attributed node (row, tabset, or tab) by id — backs updateNodeAttributes.\nconst findAttributedNode = (model: Dashfoo, id: string): AttributedNode | undefined => {\n for (const root of collectRoots(model)) {\n const found = findAttributedNodeInRow(root, id);\n if (found) {\n return found;\n }\n }\n return undefined;\n};\n\n// The root row (main layout or a float's layout) whose subtree contains the\n// node. Lets the reducer run root-relative surgery (findRow, findTabsetParent,\n// removeTabset) against the correct tree instead of assuming the main layout.\nconst findRootContaining = (model: Dashfoo, nodeId: string): RowNode | undefined => {\n for (const root of collectRoots(model)) {\n if (findAttributedNodeInRow(root, nodeId)) {\n return root;\n }\n }\n return undefined;\n};\n\n// The row holding a tabset, and the tabset's index within it — backs split placement.\nconst findTabsetParent = (\n row: RowNode,\n tabsetId: string,\n): { index: number; parent: RowNode } | undefined => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n return { index, parent: row };\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findTabsetParent(child, tabsetId);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst collectIdsInRow = (row: RowNode, acc: Array<string>): void => {\n acc.push(row.id);\n for (const child of row.children) {\n if (child.type === \"row\") {\n collectIdsInRow(child, acc);\n } else {\n acc.push(child.id);\n for (const tab of child.children) {\n acc.push(tab.id);\n }\n }\n }\n};\n\n// Ids that appear more than once across the whole model (rows, tabsets, tabs).\n// Duplicate ids produce duplicate React keys and corrupt the resize/drag\n// plumbing, so this backs a load-time diagnostic.\nconst findDuplicateIds = (model: Dashfoo): Array<string> => {\n const ids: Array<string> = [];\n for (const float of model.floats ?? []) {\n ids.push(float.id);\n }\n for (const root of collectRoots(model)) {\n collectIdsInRow(root, ids);\n }\n const seen = new Set<string>();\n const duplicates = new Set<string>();\n for (const id of ids) {\n if (seen.has(id)) {\n duplicates.add(id);\n }\n seen.add(id);\n }\n return [...duplicates];\n};\n\nexport {\n collectRoots,\n collectTabsets,\n collectTabsetsInRow,\n findAttributedNode,\n findDuplicateIds,\n findFloat,\n findRootContaining,\n findRow,\n findTab,\n findTabset,\n findTabsetParent,\n getFirstTabset,\n};\nexport type { AttributedNode, TabContainer, TabLocation };\n","// Ergonomic constructors for the layout model, so consumers seed a Dashfoo\n// without hand-writing `type`/`version`/`selected` boilerplate or nested\n// literals. Every builder fills the required-but-mechanical fields and leaves\n// the meaningful ones (ids that get referenced, weights) to the caller. Output\n// is schema-valid by construction; id uniqueness across reused components is\n// the caller's responsibility (tabs default their id to the component name), so\n// `model` warns once if it detects duplicate node ids.\n\nimport { createNodeId } from \"./ids\";\nimport type {\n Dashfoo,\n Dimension,\n FloatNode,\n Geometry,\n GlobalAttributes,\n Json,\n RowNode,\n SnapConfig,\n TabNode,\n TabsetNode,\n} from \"./schema\";\nimport { findDuplicateIds } from \"./tree\";\n\nconst toDimension = (value: number | Dimension): Dimension =>\n typeof value === \"number\" ? { unit: \"px\", value } : value;\n\ntype TabOptions = {\n config?: Json;\n enableClose?: boolean;\n enableDrag?: boolean;\n enableRename?: boolean;\n id?: string;\n};\n\n// The id defaults to the component name — the common case where a layout holds\n// one tab per component and references it by that name.\nconst tab = (component: string, name: string, options: TabOptions = {}): TabNode => ({\n component,\n name,\n type: \"tab\",\n ...options,\n id: options.id ?? component,\n});\n\ntype TabsetOptions = {\n enableClose?: boolean;\n enableMaximize?: boolean;\n id?: string;\n max?: number | Dimension;\n min?: number | Dimension;\n name?: string;\n selected?: number;\n weight?: number;\n};\n\nconst tabset = (children: Array<TabNode>, options: TabsetOptions = {}): TabsetNode => {\n const { max, min, ...rest } = options;\n return {\n children,\n type: \"tabset\",\n ...rest,\n ...(max === undefined ? {} : { max: toDimension(max) }),\n ...(min === undefined ? {} : { min: toDimension(min) }),\n id: options.id ?? createNodeId(\"tabset\"),\n selected: options.selected ?? 0,\n };\n};\n\ntype RowOptions = {\n id?: string;\n max?: number | Dimension;\n min?: number | Dimension;\n orientation?: RowNode[\"orientation\"];\n snap?: SnapConfig;\n weight?: number;\n};\n\nconst row = (children: RowNode[\"children\"], options: RowOptions = {}): RowNode => {\n const { max, min, ...rest } = options;\n return {\n children,\n type: \"row\",\n ...rest,\n ...(max === undefined ? {} : { max: toDimension(max) }),\n ...(min === undefined ? {} : { min: toDimension(min) }),\n id: options.id ?? createNodeId(\"row\"),\n orientation: options.orientation ?? \"row\",\n };\n};\n\ntype FloatOptions = {\n id?: string;\n name?: string;\n};\n\n// A floating panel wrapping a layout subtree at a given in-app rect.\nconst floatNode = (layout: RowNode, geometry: Geometry, options: FloatOptions = {}): FloatNode => ({\n geometry,\n layout,\n type: \"float\",\n ...options,\n id: options.id ?? createNodeId(\"float\"),\n});\n\ntype ModelOptions = {\n activeTabsetId?: string;\n floats?: Array<FloatNode>;\n global?: GlobalAttributes;\n maximizedTabsetId?: string;\n};\n\nconst model = (layout: RowNode, options: ModelOptions = {}): Dashfoo => {\n const built: Dashfoo = {\n layout,\n version: 1,\n ...options,\n global: options.global ?? {},\n };\n const duplicates = findDuplicateIds(built);\n if (duplicates.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(\n `[dashfoo] builder produced duplicate node ids: ${duplicates.join(\", \")} — pass explicit ids when reusing a component`,\n );\n }\n return built;\n};\n\nexport { floatNode, model, row, tab, tabset };\nexport type { FloatOptions, ModelOptions, RowOptions, TabOptions, TabsetOptions };\n","import type { Edge } from \"../model/schema\";\nimport type { DockLocation } from \"../state/actions\";\n\ntype Point = { x: number; y: number };\ntype Rect = { height: number; width: number; x: number; y: number };\ntype DockTarget = { kind: \"tab\" } | { edge: Edge; kind: \"split\" };\ntype BandOptions = { bandFraction?: number };\ntype DockZone = { location: DockLocation; points: Array<Point> };\n\n// Shared by resolveDockTarget and dockZonePolygons so the painted map and the\n// live hit-test can never disagree on where the edge bands end.\nconst DEFAULT_BAND_FRACTION = 0.22;\n\n// Fractional distance of the pointer from each of the four edges of a rect.\nconst edgeDistances = (\n pointer: Point,\n rect: Rect,\n): { bottom: number; left: number; right: number; top: number } => {\n const fx = (pointer.x - rect.x) / rect.width;\n const fy = (pointer.y - rect.y) / rect.height;\n return { bottom: 1 - fy, left: fx, right: 1 - fx, top: fy };\n};\n\nconst closestEdge = (d: { bottom: number; left: number; right: number; top: number }): Edge => {\n const min = Math.min(d.left, d.right, d.top, d.bottom);\n if (min === d.left) {\n return \"left\";\n }\n if (min === d.right) {\n return \"right\";\n }\n if (min === d.top) {\n return \"top\";\n }\n return \"bottom\";\n};\n\n// Where a drag dropped over a tabset should land: stacked as a tab when the\n// pointer is in the center, or splitting the tabset when it is within an outer\n// band (default 22%) of one of the four edges — the closer edge wins in corners.\nconst resolveDockTarget = (pointer: Point, rect: Rect, opts?: BandOptions): DockTarget => {\n const band = opts?.bandFraction ?? DEFAULT_BAND_FRACTION;\n // a zero-size tabset has no meaningful edges; dividing by its width/height\n // yields NaN distances that would silently resolve to a bogus split.\n if (rect.width <= 0 || rect.height <= 0) {\n return { kind: \"tab\" };\n }\n const distances = edgeDistances(pointer, rect);\n const min = Math.min(distances.left, distances.right, distances.top, distances.bottom);\n\n if (min > band) {\n return { kind: \"tab\" };\n }\n return { edge: closestEdge(distances), kind: \"split\" };\n};\n\n// The full hit-region partition of a tabset for resolveDockTarget: four edge\n// trapezoids and the inner center rect. Seams between adjacent edge zones sit\n// where the fractional edge distances tie — straight diagonals from each rect\n// corner to the matching inner-rect corner — so the map is an exact picture of\n// which location a pointer anywhere over the rect resolves to. A zero-size rect\n// collapses to a single center polygon, mirroring resolveDockTarget's fallback.\nconst dockZonePolygons = (rect: Rect, opts?: BandOptions): Array<DockZone> => {\n const { height: h, width: w, x, y } = rect;\n const nw: Point = { x, y };\n const ne: Point = { x: x + w, y };\n const se: Point = { x: x + w, y: y + h };\n const sw: Point = { x, y: y + h };\n if (w <= 0 || h <= 0) {\n return [{ location: \"center\", points: [nw, ne, se, sw] }];\n }\n const band = opts?.bandFraction ?? DEFAULT_BAND_FRACTION;\n const bx = band * w;\n const by = band * h;\n const innerNw: Point = { x: x + bx, y: y + by };\n const innerNe: Point = { x: x + w - bx, y: y + by };\n const innerSe: Point = { x: x + w - bx, y: y + h - by };\n const innerSw: Point = { x: x + bx, y: y + h - by };\n return [\n { location: \"center\", points: [innerNw, innerNe, innerSe, innerSw] },\n { location: \"split-left\", points: [nw, innerNw, innerSw, sw] },\n { location: \"split-right\", points: [ne, se, innerSe, innerNe] },\n { location: \"split-top\", points: [nw, ne, innerNe, innerNw] },\n { location: \"split-bottom\", points: [sw, innerSw, innerSe, se] },\n ];\n};\n\n// The region the dock indicator highlights for a given location over a tabset:\n// the whole tabset for a center stack, the matching half for a split.\nconst zoneRect = (rect: Rect, location: DockLocation): Rect => {\n const halfW = rect.width / 2;\n const halfH = rect.height / 2;\n switch (location) {\n case \"split-bottom\": {\n return { height: halfH, width: rect.width, x: rect.x, y: rect.y + halfH };\n }\n case \"split-left\": {\n return { height: rect.height, width: halfW, x: rect.x, y: rect.y };\n }\n case \"split-right\": {\n return { height: rect.height, width: halfW, x: rect.x + halfW, y: rect.y };\n }\n case \"split-top\": {\n return { height: halfH, width: rect.width, x: rect.x, y: rect.y };\n }\n default: {\n return rect;\n }\n }\n};\n\nexport { dockZonePolygons, resolveDockTarget, zoneRect };\nexport type { BandOptions, DockTarget, DockZone, Point, Rect };\n","import type { SnapConfig } from \"../model/schema\";\n\n// Magnetic snapping for split resize. Pure math over ordered percentage arrays\n// (sizes sum to ~100) so the engine stays framework-free; the React adapter maps\n// rrp's id-keyed layout to/from these arrays. The dragged boundary index is passed\n// in rather than inferred, so a cascading min-size resize can't fool the snap onto\n// the wrong boundary.\n\nconst DEFAULT_SNAP_THRESHOLD = 4;\n// Round grid positions so equal targets from different sources (step 25 vs\n// divisions 4) dedupe and float drift (100/3) compares cleanly.\nconst TARGET_PRECISION = 1e6;\n\nconst addMultiples = (targets: Set<number>, increment: number): void => {\n if (increment <= 0) {\n return;\n }\n for (let position = increment; position < 100; position += increment) {\n const rounded = Math.round(position * TARGET_PRECISION) / TARGET_PRECISION;\n if (rounded < 100) {\n targets.add(rounded);\n }\n }\n};\n\n// The grid the boundary can snap to: the union of the fixed `step` grid and the\n// even-`divisions` grid (multiples of 100/d, where d is the number or, for\n// \"panels\", the row's panel count). Positions strictly inside 0..100.\nconst resolveSnapTargets = (config: SnapConfig, panelCount: number): Array<number> => {\n const targets = new Set<number>();\n if (config.step !== undefined && config.step > 0) {\n addMultiples(targets, config.step);\n }\n if (config.divisions !== undefined) {\n const divisions = config.divisions === \"panels\" ? panelCount : config.divisions;\n if (divisions >= 2) {\n addMultiples(targets, 100 / divisions);\n }\n }\n return [...targets].toSorted((a, b) => a - b);\n};\n\n// Whether a config produces any snap grid at all — used to gate the resize\n// listeners so a non-snapping layout pays nothing per pointer move.\nconst snapEnabled = (config: SnapConfig | null): boolean =>\n config !== null &&\n ((config.step ?? 0) > 0 ||\n config.divisions === \"panels\" ||\n (typeof config.divisions === \"number\" && config.divisions >= 2));\n\nconst nearestTarget = (position: number, targets: Array<number>): number | undefined =>\n targets.reduce<number | undefined>((best, target) => {\n if (best === undefined || Math.abs(target - position) < Math.abs(best - position)) {\n return target;\n }\n return best;\n }, undefined);\n\ntype SnapResult = { sizes: Array<number>; snapped: boolean };\n\n// Snap the dragged boundary (between panel `boundaryIndex` and the next) to the\n// nearest grid target within the threshold, moving only that pair so siblings keep\n// their sizes. Returns the input unchanged when snapping is off, nothing is within\n// reach, the index is out of range, or the correction would drive a panel negative\n// (rrp re-clamps real px minimums on the next frame).\nconst snapSizes = (sizes: Array<number>, boundaryIndex: number, config: SnapConfig): SnapResult => {\n const threshold = config.threshold ?? DEFAULT_SNAP_THRESHOLD;\n const left = sizes[boundaryIndex];\n const right = sizes[boundaryIndex + 1];\n if (boundaryIndex < 0 || left === undefined || right === undefined) {\n return { sizes, snapped: false };\n }\n\n const targets = resolveSnapTargets(config, sizes.length);\n if (targets.length === 0) {\n return { sizes, snapped: false };\n }\n\n let position = 0;\n for (let index = 0; index <= boundaryIndex; index++) {\n position += sizes[index] ?? 0;\n }\n\n const target = nearestTarget(position, targets);\n if (target === undefined || Math.abs(target - position) > threshold) {\n return { sizes, snapped: false };\n }\n\n const delta = target - position;\n if (left + delta < 0 || right - delta < 0) {\n return { sizes, snapped: false };\n }\n\n const next = [...sizes];\n next[boundaryIndex] = left + delta;\n next[boundaryIndex + 1] = right - delta;\n return { sizes: next, snapped: true };\n};\n\nexport { DEFAULT_SNAP_THRESHOLD, resolveSnapTargets, snapEnabled, snapSizes };\nexport type { SnapResult };\n","import type { Dashfoo, FloatNode, RowNode, TabsetNode } from \"./schema\";\nimport { collectTabsets } from \"./tree\";\n\nconst clampSelected = (length: number, selected: number): number => {\n if (length === 0) {\n return 0;\n }\n // coerce to a non-negative integer first to match the schema's z.number().int():\n // a persisted 1.5 or NaN must not survive as a fractional/NaN index.\n const base = Number.isFinite(selected) ? Math.trunc(selected) : 0;\n if (base < 0) {\n return 0;\n }\n if (base > length - 1) {\n return length - 1;\n }\n return base;\n};\n\nconst normalizeTabset = (tabset: TabsetNode): TabsetNode => ({\n ...tabset,\n selected: clampSelected(tabset.children.length, tabset.selected),\n});\n\n// Drop empty tabsets and empty rows, recurse into child rows, and simplify a\n// single-child row by lifting its lone child (which inherits the lifted row's\n// weight so sizing is preserved).\nconst normalizeRowChildren = (children: RowNode[\"children\"]): RowNode[\"children\"] => {\n const out: RowNode[\"children\"] = [];\n\n for (const child of children) {\n if (child.type === \"tabset\") {\n if (child.children.length > 0) {\n out.push(normalizeTabset(child));\n }\n continue;\n }\n\n const grandchildren = normalizeRowChildren(child.children);\n if (grandchildren.length === 0) {\n continue;\n }\n if (grandchildren.length === 1) {\n const only = grandchildren[0];\n if (only !== undefined) {\n out.push(child.weight === undefined ? only : { ...only, weight: child.weight });\n }\n continue;\n }\n out.push({ ...child, children: grandchildren });\n }\n\n return out;\n};\n\nconst rowContainsTabset = (row: RowNode): boolean =>\n row.children.some((child) => (child.type === \"tabset\" ? true : rowContainsTabset(child)));\n\n// The root is always a row; if it reduces to a single child that is itself a\n// row, absorb that row (adopt its children + orientation) to avoid redundant\n// nesting.\nconst normalizeLayout = (root: RowNode): RowNode => {\n let children = normalizeRowChildren(root.children);\n let orientation = root.orientation;\n\n let inner = children[0];\n while (children.length === 1 && inner?.type === \"row\") {\n children = inner.children;\n orientation = inner.orientation;\n inner = children[0];\n }\n\n return { ...root, children, orientation };\n};\n\n// Self-healing pass run after every action so the tree stays canonical: no empty\n// tabsets or rows, no redundant single-child rows, selected indices in range,\n// and active/maximized ids that always point at an existing tabset.\nconst normalize = (model: Dashfoo): Dashfoo => {\n const layout = normalizeLayout(model.layout);\n\n // Heal each float's own layout, then drop any float that emptied out (its last\n // tab closed/moved away) — the float equivalent of empty-tabset cleanup.\n const healedFloats: Array<FloatNode> = [];\n for (const float of model.floats ?? []) {\n const floatLayout = normalizeLayout(float.layout);\n if (rowContainsTabset(floatLayout)) {\n healedFloats.push({ ...float, layout: floatLayout });\n }\n }\n const floats = healedFloats.length > 0 ? healedFloats : undefined;\n\n const withRoots: Dashfoo = { ...model, floats, layout };\n\n const tabsetIds = new Set(collectTabsets(withRoots).map((tabset) => tabset.id));\n const firstTabsetId = collectTabsets(withRoots)[0]?.id;\n\n const activeTabsetId =\n model.activeTabsetId !== undefined && tabsetIds.has(model.activeTabsetId)\n ? model.activeTabsetId\n : firstTabsetId;\n\n const maximizedTabsetId =\n model.maximizedTabsetId !== undefined && tabsetIds.has(model.maximizedTabsetId)\n ? model.maximizedTabsetId\n : undefined;\n\n return { ...withRoots, activeTabsetId, maximizedTabsetId };\n};\n\nexport { normalize };\n","import { createNodeId } from \"../model/ids\";\nimport type { Dashfoo, RowNode, TabNode, TabsetNode } from \"../model/schema\";\nimport { findRootContaining, findTabset, findTabsetParent } from \"../model/tree\";\n\nimport type { DockLocation } from \"./actions\";\n\n// In-place tree-surgery helpers shared by the reducer and the float operations:\n// removing/placing tabsets and tabs across any root. Each mutates the draft model\n// the reducer deep-cloned, so they never touch the caller's input.\n\nconst removeTabset = (row: RowNode, tabsetId: string): boolean => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n row.children.splice(index, 1);\n return true;\n }\n for (const child of row.children) {\n if (child.type === \"row\" && removeTabset(child, tabsetId)) {\n return true;\n }\n }\n return false;\n};\n\n// Detach a tabset from the tree and return it (for moving a whole tabset).\nconst removeTabsetReturning = (row: RowNode, tabsetId: string): TabsetNode | undefined => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n const [removed] = row.children.splice(index, 1);\n return removed?.type === \"tabset\" ? removed : undefined;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = removeTabsetReturning(child, tabsetId);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst splitOrientation = (location: DockLocation): \"column\" | \"row\" =>\n location === \"split-left\" || location === \"split-right\" ? \"row\" : \"column\";\n\nconst splitsBefore = (location: DockLocation): boolean =>\n location === \"split-left\" || location === \"split-top\";\n\n// Place an (already-detached) tabset beside the target for a split-* location:\n// reuse the parent row when the orientation already matches, else wrap the target\n// in a new row. Shared by insertTab (a fresh tabset), moveTabset (an existing one),\n// and dockFloat. Splits the target's space in half.\nconst placeBesideTarget = (\n draft: Dashfoo,\n placed: TabsetNode,\n targetId: string,\n location: DockLocation,\n): void => {\n const targetTabset = findTabset(draft, targetId);\n // Operate within whichever root holds the target (main layout or a float), so a\n // split docked inside a floated panel stays in that float.\n const targetRoot = findRootContaining(draft, targetId) ?? draft.layout;\n const found = findTabsetParent(targetRoot, targetId);\n if (!targetTabset || !found) {\n return;\n }\n\n const orientation = splitOrientation(location);\n const before = splitsBefore(location);\n\n if (found.parent.orientation === orientation) {\n const targetWeight = targetTabset.weight ?? 100;\n targetTabset.weight = targetWeight / 2;\n placed.weight = targetWeight / 2;\n found.parent.children.splice(before ? found.index : found.index + 1, 0, placed);\n } else {\n const targetWeight = targetTabset.weight;\n targetTabset.weight = 50;\n placed.weight = 50;\n const newRow: RowNode = {\n children: before ? [placed, targetTabset] : [targetTabset, placed],\n id: createNodeId(\"row\"),\n orientation,\n type: \"row\",\n };\n if (targetWeight !== undefined) {\n newRow.weight = targetWeight;\n }\n found.parent.children.splice(found.index, 1, newRow);\n }\n\n draft.activeTabsetId = placed.id;\n};\n\n// Insert a tab into the model at a dock target. center stacks it into the target\n// tabset; split-* creates a new tabset beside the target.\ntype DropTarget = { id: string; index?: number; location: DockLocation };\n\nconst insertTab = (draft: Dashfoo, tab: TabNode, target: DropTarget): void => {\n const { id: targetId, index, location } = target;\n const targetTabset = findTabset(draft, targetId);\n if (!targetTabset) {\n return;\n }\n\n if (location === \"center\") {\n const at = index ?? targetTabset.children.length;\n targetTabset.children.splice(at, 0, tab);\n targetTabset.selected = at;\n return;\n }\n\n const newTabset: TabsetNode = {\n children: [tab],\n id: createNodeId(\"tabset\"),\n selected: 0,\n type: \"tabset\",\n weight: 50,\n };\n placeBesideTarget(draft, newTabset, targetId, location);\n};\n\nconst tabsetsInRow = (row: RowNode, acc: Array<TabsetNode>): void => {\n for (const child of row.children) {\n if (child.type === \"tabset\") {\n acc.push(child);\n } else {\n tabsetsInRow(child, acc);\n }\n }\n};\n\n// Wrap a detached node in the row → tabset scaffolding a float's layout needs.\nconst wrapTabInLayout = (tab: TabNode): RowNode => ({\n children: [{ children: [tab], id: createNodeId(\"tabset\"), selected: 0, type: \"tabset\" }],\n id: createNodeId(\"row\"),\n orientation: \"row\",\n type: \"row\",\n});\n\nconst wrapTabsetInLayout = (tabset: TabsetNode): RowNode => ({\n children: [tabset],\n id: createNodeId(\"row\"),\n orientation: \"row\",\n type: \"row\",\n});\n\nexport {\n insertTab,\n placeBesideTarget,\n removeTabset,\n removeTabsetReturning,\n tabsetsInRow,\n wrapTabInLayout,\n wrapTabsetInLayout,\n};\nexport type { DropTarget };\n","import { createNodeId } from \"../model/ids\";\nimport type { Dashfoo, FloatNode, Geometry, RowNode, TabsetNode } from \"../model/schema\";\nimport { findRootContaining } from \"../model/tree\";\n\nimport type { DockLocation } from \"./actions\";\nimport {\n placeBesideTarget,\n removeTabsetReturning,\n tabsetsInRow,\n wrapTabsetInLayout,\n} from \"./surgery\";\n\n// Float operations: floating a tabset out, docking it back, and naming. Floats are\n// a second set of roots, so these lean on the same surgery helpers as the main\n// layout. Split out of the reducer to keep each file focused (and under the cap).\n\n// Default float rect when the caller doesn't measure the source (e.g. a\n// programmatic float). The React layer normally supplies a rect over the panel.\nconst DEFAULT_FLOAT_GEOMETRY: Geometry = { height: 360, left: 80, top: 80, width: 480 };\n\n// A unique window name: `base` if free among the floats, else `base 1`, `base 2`, …\n// Floats are auto-named \"Panel\" and renames are deduped through this, so two floats\n// never share a title.\nconst uniqueFloatName = (base: string, floats: Array<FloatNode>): string => {\n const used = new Set(floats.map((float) => float.name));\n if (!used.has(base)) {\n return base;\n }\n let index = 1;\n while (used.has(`${base} ${index}`)) {\n index += 1;\n }\n return `${base} ${index}`;\n};\n\n// Append a new floating panel holding `layout`, and move focus to its first tabset\n// so active-tabset/keyboard logic tracks the floated panel. `floatId` is supplied\n// when the caller minted the id up front, so the model node and any UI agree.\nconst pushFloat = (\n draft: Dashfoo,\n layout: RowNode,\n geometry: Geometry | undefined,\n floatId: string | undefined,\n): void => {\n const float: FloatNode = {\n geometry: geometry ?? DEFAULT_FLOAT_GEOMETRY,\n id: floatId ?? createNodeId(\"float\"),\n layout,\n name: uniqueFloatName(\"Panel\", draft.floats ?? []),\n type: \"float\",\n };\n draft.floats = [...(draft.floats ?? []), float];\n\n const tabsets: Array<TabsetNode> = [];\n tabsetsInRow(layout, tabsets);\n const first = tabsets[0];\n if (first) {\n draft.activeTabsetId = first.id;\n }\n};\n\nconst floatTabsetById = (\n draft: Dashfoo,\n tabsetId: string,\n geometry: Geometry | undefined,\n floatId: string | undefined,\n): void => {\n const root = findRootContaining(draft, tabsetId);\n if (!root) {\n return;\n }\n const detached = removeTabsetReturning(root, tabsetId);\n if (detached) {\n pushFloat(draft, wrapTabsetInLayout(detached), geometry, floatId);\n }\n};\n\n// Dock-back targets the MAIN layout only (never another float): prefer an explicit\n// target, then the active tabset, then the first main tabset.\nconst resolveMainTarget = (\n draft: Dashfoo,\n targetId: string | undefined,\n): TabsetNode | undefined => {\n const mainTabsets: Array<TabsetNode> = [];\n tabsetsInRow(draft.layout, mainTabsets);\n if (targetId) {\n const explicit = mainTabsets.find((tabset) => tabset.id === targetId);\n if (explicit) {\n return explicit;\n }\n }\n if (draft.activeTabsetId !== undefined) {\n const active = mainTabsets.find((tabset) => tabset.id === draft.activeTabsetId);\n if (active) {\n return active;\n }\n }\n return mainTabsets[0];\n};\n\nconst dockFloat = (\n draft: Dashfoo,\n floatId: string,\n targetId: string | undefined,\n location: DockLocation | undefined,\n): void => {\n const floats = draft.floats ?? [];\n const index = floats.findIndex((float) => float.id === floatId);\n if (index === -1) {\n return;\n }\n const [float] = floats.splice(index, 1);\n draft.floats = floats;\n if (!float) {\n return;\n }\n\n const tabsets: Array<TabsetNode> = [];\n tabsetsInRow(float.layout, tabsets);\n const tabs = tabsets.flatMap((tabset) => tabset.children);\n if (tabs.length === 0) {\n return;\n }\n\n // Carry over which tab the user had selected in the float. The float's first\n // tabset leads the flattened list (the common single-tabset float), so its\n // clamped `selected` is the offset of the focused tab — clamp against the\n // tabset's own length, like the moveTabset center merge, so a stale index can't\n // resolve to the wrong tab.\n const leadTabset = tabsets[0];\n const selectedOffset = leadTabset\n ? Math.min(Math.max(leadTabset.selected, 0), leadTabset.children.length - 1)\n : 0;\n\n const target = resolveMainTarget(draft, targetId);\n if (!target) {\n // Nothing left in the main layout to dock into — promote the float's own layout\n // to be the main layout so its tabs aren't lost.\n draft.layout = float.layout;\n draft.activeTabsetId = leadTabset?.id;\n return;\n }\n\n // An explicit `center` (e.g. a drag dropped onto a tab strip) merges the float's\n // tabs into the target. The default dock-back, though, restores the float as its\n // OWN panel beside the target — bringing all its tabs back as a group rather than\n // flattening them into another tabset.\n if (location === \"center\") {\n const mergeStart = target.children.length;\n target.children.push(...tabs);\n target.selected = mergeStart + selectedOffset;\n draft.activeTabsetId = target.id;\n return;\n }\n\n const where = location ?? \"split-right\";\n // A single-tabset float (the common case) re-docks that exact tabset, preserving\n // its id, tabs, and selected index; a split float collapses to one new tabset.\n const placed: TabsetNode =\n tabsets.length === 1 && leadTabset\n ? leadTabset\n : { children: tabs, id: createNodeId(\"tabset\"), selected: selectedOffset, type: \"tabset\" };\n placed.weight = 50;\n placeBesideTarget(draft, placed, target.id, where);\n};\n\nexport { dockFloat, floatTabsetById, pushFloat, uniqueFloatName };\n","import { normalize } from \"../model/invariants\";\nimport type { Dashfoo } from \"../model/schema\";\nimport {\n findAttributedNode,\n findFloat,\n findRootContaining,\n findRow,\n findTab,\n findTabset,\n} from \"../model/tree\";\n\nimport type { Action } from \"./actions\";\nimport { dockFloat, floatTabsetById, pushFloat, uniqueFloatName } from \"./floats\";\nimport {\n insertTab,\n placeBesideTarget,\n removeTabset,\n removeTabsetReturning,\n wrapTabInLayout,\n} from \"./surgery\";\n\nconst assertNever = (value: never): never => {\n throw new Error(`Unhandled action: ${JSON.stringify(value)}`);\n};\n\nconst applyAction = (draft: Dashfoo, action: Action): void => {\n switch (action.type) {\n case \"addNode\": {\n insertTab(draft, action.tab, {\n id: action.targetId,\n index: action.index,\n location: action.location,\n });\n return;\n }\n case \"adjustSplit\": {\n const root = findRootContaining(draft, action.rowId) ?? draft.layout;\n const row = findRow(root, action.rowId);\n if (!row) {\n return;\n }\n for (let index = 0; index < action.weights.length; index++) {\n const child = row.children[index];\n const weight = action.weights[index];\n if (!child || weight === undefined) {\n continue;\n }\n child.weight = weight;\n }\n return;\n }\n case \"deleteTab\": {\n const location = findTab(draft, action.tabId);\n if (location) {\n location.container.children.splice(location.index, 1);\n }\n return;\n }\n case \"deleteTabset\": {\n const root = findRootContaining(draft, action.tabsetId);\n if (root) {\n removeTabset(root, action.tabsetId);\n }\n return;\n }\n case \"dockFloat\": {\n dockFloat(draft, action.floatId, action.targetId, action.location);\n return;\n }\n case \"floatTab\": {\n const location = findTab(draft, action.tabId);\n if (!location) {\n return;\n }\n const [removed] = location.container.children.splice(location.index, 1);\n if (removed) {\n pushFloat(draft, wrapTabInLayout(removed), action.geometry, action.floatId);\n }\n return;\n }\n case \"floatTabset\": {\n floatTabsetById(draft, action.tabsetId, action.geometry, action.floatId);\n return;\n }\n case \"moveFloat\": {\n const float = findFloat(draft, action.floatId);\n if (float) {\n float.geometry = action.geometry;\n }\n return;\n }\n case \"moveNode\": {\n const source = findTab(draft, action.sourceId);\n if (!source) {\n return;\n }\n // Remove the source first; action.index is the destination slot measured\n // against the tabs that remain (the drag adapter excludes the dragged tab),\n // so it indexes the post-removal array directly — no reorder adjustment.\n const [removed] = source.container.children.splice(source.index, 1);\n if (!removed) {\n return;\n }\n insertTab(draft, removed, {\n id: action.targetId,\n index: action.index,\n location: action.location,\n });\n return;\n }\n case \"moveTabset\": {\n if (action.sourceId === action.targetId) {\n return;\n }\n const source = findTabset(draft, action.sourceId);\n if (!source) {\n return;\n }\n if (action.location === \"center\") {\n const target = findTabset(draft, action.targetId);\n if (target) {\n const mergeStart = target.children.length;\n // Clamp the source's selection against the SOURCE's own tab count before\n // offsetting. normalize's clampSelected only bounds against the FINAL\n // merged length, so a stale/out-of-range source.selected would otherwise\n // resolve to a valid-but-wrong merged index (an original target tab, or\n // the wrong source tab) that normalize cannot catch. Clamp here so the\n // merged selection always lands on a tab that came from the source.\n const sourceSelected = Math.min(Math.max(source.selected, 0), source.children.length - 1);\n target.children.push(...source.children);\n target.selected = mergeStart + sourceSelected;\n const sourceRoot = findRootContaining(draft, action.sourceId);\n if (sourceRoot) {\n removeTabset(sourceRoot, action.sourceId);\n }\n }\n return;\n }\n // split-* moves the whole tabset beside the target.\n if (action.location.startsWith(\"split-\")) {\n const sourceRoot = findRootContaining(draft, action.sourceId);\n const detached = sourceRoot\n ? removeTabsetReturning(sourceRoot, action.sourceId)\n : undefined;\n if (detached) {\n placeBesideTarget(draft, detached, action.targetId, action.location);\n }\n }\n return;\n }\n case \"renameFloat\": {\n const float = findFloat(draft, action.floatId);\n if (float) {\n // Dedupe against the other floats so two windows never share a title.\n const others = (draft.floats ?? []).filter((other) => other.id !== action.floatId);\n float.name = uniqueFloatName(action.name, others);\n }\n return;\n }\n case \"renameTab\": {\n const location = findTab(draft, action.tabId);\n if (location) {\n location.tab.name = action.name;\n }\n return;\n }\n case \"selectTab\": {\n const tabset = findTabset(draft, action.tabsetId);\n if (tabset) {\n tabset.selected = action.index;\n }\n return;\n }\n case \"setActiveTabset\": {\n if (findTabset(draft, action.tabsetId)) {\n draft.activeTabsetId = action.tabsetId;\n }\n return;\n }\n case \"setFloatMinimized\": {\n const float = findFloat(draft, action.floatId);\n if (float) {\n float.minimized = action.minimized;\n }\n return;\n }\n case \"setMaximizedTabset\": {\n draft.maximizedTabsetId = action.tabsetId ?? undefined;\n return;\n }\n case \"updateGlobalAttributes\": {\n Object.assign(draft.global, action.attrs);\n return;\n }\n case \"updateNodeAttributes\": {\n const node = findAttributedNode(draft, action.nodeId);\n if (node) {\n Object.assign(node, action.attrs);\n }\n return;\n }\n default: {\n assertNever(action);\n }\n }\n};\n\n// The canonical engine: deep-copy the model (so the input is never mutated), apply\n// one action to the copy, then run the self-healing invariants so the result is\n// always a valid, canonical model.\nconst reducer = (model: Dashfoo, action: Action): Dashfoo => {\n const draft = structuredClone(model);\n applyAction(draft, action);\n return normalize(draft);\n};\n\nexport { reducer };\n","import type { Dashfoo } from \"../model/schema\";\n\nimport type { Action } from \"./actions\";\nimport { reducer } from \"./reducer\";\n\n// past/present/future snapshots. Each committed action is its own undo step:\n// rrp v4's onLayoutChanged fires one adjustSplit per release, so there is no\n// per-frame burst to coalesce.\ntype History = {\n future: Array<Dashfoo>;\n past: Array<Dashfoo>;\n present: Dashfoo;\n};\n\n// Snapshots are full model clones, so an unbounded past grows memory for the\n// life of the session (every action — even a tab click — is one step). 100\n// steps is far beyond practical undo depth while keeping memory flat.\nconst HISTORY_LIMIT = 100;\n\nconst createHistory = (present: Dashfoo): History => ({\n future: [],\n past: [],\n present,\n});\n\nconst canUndo = (history: History): boolean => history.past.length > 0;\nconst canRedo = (history: History): boolean => history.future.length > 0;\n\nconst dispatch = (history: History, action: Action): History => {\n const present = reducer(history.present, action);\n return { future: [], past: [...history.past, history.present].slice(-HISTORY_LIMIT), present };\n};\n\nconst undo = (history: History): History => {\n const previous = history.past.at(-1);\n if (previous === undefined) {\n return history;\n }\n return {\n future: [history.present, ...history.future],\n past: history.past.slice(0, -1),\n present: previous,\n };\n};\n\nconst redo = (history: History): History => {\n const [next, ...rest] = history.future;\n if (next === undefined) {\n return history;\n }\n return {\n future: rest,\n past: [...history.past, history.present].slice(-HISTORY_LIMIT),\n present: next,\n };\n};\n\nexport { canRedo, canUndo, createHistory, dispatch, HISTORY_LIMIT, redo, undo };\nexport type { History };\n","import { assign, setup } from \"xstate\";\n\nimport type { Dashfoo } from \"../model/schema\";\nimport type { Action } from \"../state/actions\";\nimport type { History } from \"../state/history\";\nimport { createHistory, dispatch, redo, undo } from \"../state/history\";\n\ntype DashfooContext = { history: History };\n\ntype DashfooEvent =\n | { action: Action; type: \"DISPATCH\" }\n | { model: Dashfoo; type: \"SET_MODEL\" }\n | { type: \"REDO\" }\n | { type: \"UNDO\" };\n\ntype DashfooInput = { model: Dashfoo };\n\n// The document actor: it owns the undo/redo history (whose `present` is the live\n// model) and processes document mutations by invoking the pure reducer via the\n// history helpers. It has no finite states beyond `ready` — the document is data,\n// not a lifecycle — so every mutation is an event handled with assign.\nconst dashfooMachine = setup({\n types: {\n context: {} as DashfooContext,\n events: {} as DashfooEvent,\n input: {} as DashfooInput,\n },\n}).createMachine({\n context: ({ input }) => ({ history: createHistory(input.model) }),\n id: \"dashfoo\",\n initial: \"ready\",\n states: {\n ready: {\n on: {\n DISPATCH: {\n actions: assign({\n history: ({ context, event }) => dispatch(context.history, event.action),\n }),\n },\n REDO: {\n actions: assign({ history: ({ context }) => redo(context.history) }),\n },\n SET_MODEL: {\n actions: assign({ history: ({ event }) => createHistory(event.model) }),\n },\n UNDO: {\n actions: assign({ history: ({ context }) => undo(context.history) }),\n },\n },\n },\n },\n});\n\nexport { dashfooMachine };\nexport type { DashfooContext, DashfooEvent, DashfooInput };\n","import { assign, emit, setup } from \"xstate\";\n\nimport type { TabNode } from \"../model/schema\";\nimport type { Action, DropIntent } from \"../state/actions\";\n\n// Internal subjects reference a node already in the document; an external\n// subject carries the TabNode to insert on drop (a widget dragged in from\n// outside the layout), so its id never has to exist in the model.\ntype DragSubject =\n | { id: string; kind: \"tab\" | \"tabset\" }\n | { id: string; kind: \"external\"; tab: TabNode };\n\ntype DragContext = { intent: DropIntent | null; subject: DragSubject | null };\n\ntype DragEvent =\n | { intent: DropIntent | null; type: \"OVER\" }\n | { subject: DragSubject; type: \"START\" }\n | { type: \"CANCEL\" }\n | { type: \"DROP\" };\n\ntype DragEmitted = { action: Action; type: \"COMMIT\" };\n\nconst requireDrop = (context: DragContext): { intent: DropIntent; subject: DragSubject } => {\n if (context.subject === null || context.intent === null) {\n throw new Error(\"drop requires a subject and a drop intent\");\n }\n return { intent: context.intent, subject: context.subject };\n};\n\n// The drag/dock interaction lifecycle, driven by abstract events the dnd-kit\n// adapter maps from pointer/keyboard input. It owns transient drag state only and\n// never touches the document — on a valid drop it emits a COMMIT carrying the\n// resulting action (moveNode/moveTabset, or addNode for an external subject),\n// which the React layer forwards to the document machine.\nconst dragDockMachine = setup({\n guards: {\n hasValidDrop: ({ context }) => context.subject !== null && context.intent !== null,\n },\n types: {\n context: {} as DragContext,\n emitted: {} as DragEmitted,\n events: {} as DragEvent,\n },\n}).createMachine({\n context: { intent: null, subject: null },\n id: \"dragDock\",\n initial: \"idle\",\n states: {\n dragging: {\n on: {\n CANCEL: { target: \"idle\" },\n DROP: [\n {\n actions: emit(({ context }) => {\n const { intent, subject } = requireDrop(context);\n const action: Action =\n subject.kind === \"external\"\n ? {\n index: intent.index,\n location: intent.location,\n tab: subject.tab,\n targetId: intent.targetId,\n type: \"addNode\",\n }\n : {\n index: intent.index,\n location: intent.location,\n sourceId: subject.id,\n targetId: intent.targetId,\n type: subject.kind === \"tabset\" ? \"moveTabset\" : \"moveNode\",\n };\n return { action, type: \"COMMIT\" };\n }),\n guard: \"hasValidDrop\",\n target: \"idle\",\n },\n { target: \"idle\" },\n ],\n OVER: { actions: assign({ intent: ({ event }) => event.intent }) },\n },\n },\n idle: {\n entry: assign({ intent: null, subject: null }),\n on: {\n START: { actions: assign({ subject: ({ event }) => event.subject }), target: \"dragging\" },\n },\n },\n },\n});\n\nexport { dragDockMachine };\nexport type { DragContext, DragEmitted, DragEvent, DragSubject };\n","import { normalize } from \"./invariants\";\nimport type { Dashfoo } from \"./schema\";\nimport { dashfooSchema } from \"./schema\";\nimport { findDuplicateIds } from \"./tree\";\n\n// Validate an untrusted value against the schema and return a normalized,\n// canonical model. Throws if the value is not a valid model — including a\n// payload whose `version` is not the pinned `1`; warns (does not throw) on\n// duplicate ids, which corrupt React keys and the plumbing.\nconst parseModel = (value: unknown): Dashfoo => {\n const model = normalize(dashfooSchema.parse(value));\n const duplicates = findDuplicateIds(model);\n if (duplicates.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${duplicates.join(\", \")}`);\n }\n return model;\n};\n\nconst toJSON = (model: Dashfoo): string => JSON.stringify(model);\n\nconst fromJSON = (json: string): Dashfoo => parseModel(JSON.parse(json));\n\nexport { fromJSON, parseModel, toJSON };\n","import { normalize } from \"./invariants\";\nimport type { Dashfoo, Orientation, TabsetNode } from \"./schema\";\nimport { collectTabsetsInRow } from \"./tree\";\n\n// Flatten the MAIN layout into a single row (default a column) of all its\n// tabsets, in document order, each with equal weight, so nothing is lost on a\n// narrow screen; maximize is cleared. Detached windows are independent frames, so\n// `windows` passes through untouched — pulling their tabsets into the main layout\n// would duplicate panels already rendered in their popups. The building block for\n// a mobile breakpoint: `stackModel(desktopModel)`.\nconst stackModel = (model: Dashfoo, orientation: Orientation = \"column\"): Dashfoo => {\n // Equal weight so stacked panels share the space evenly. A loop (not map) so we\n // create fresh nodes without mutating the shared input tabsets.\n const mainTabsets: Array<TabsetNode> = [];\n collectTabsetsInRow(model.layout, mainTabsets);\n const children: Array<TabsetNode> = [];\n for (const tabset of mainTabsets) {\n children.push({ ...tabset, weight: 1 });\n }\n\n return normalize({\n ...model,\n layout: { children, id: model.layout.id, orientation, type: \"row\" },\n maximizedTabsetId: undefined,\n });\n};\n\nexport { stackModel };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashfoo/core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Headless docking-layout engine: model, actions, reducer, serialization, geometry, and the XState actor system. No React.",
5
5
  "license": "MIT",
6
6
  "author": "Pedro Filho <pedro@filho.me>",
@@ -31,8 +31,8 @@
31
31
  "devDependencies": {
32
32
  "typescript": "^6.0.3",
33
33
  "vitest": "^4.1.8",
34
- "@repo/typescript-config": "0.0.0",
35
- "@repo/config-vitest": "0.0.0"
34
+ "@repo/config-vitest": "0.0.0",
35
+ "@repo/typescript-config": "0.0.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsdown",