@ixfx/components 0.15.5 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/bundle/index.d.ts +986 -49
  2. package/bundle/index.js +765 -729
  3. package/dist/ac-token.js +1 -1
  4. package/dist/crumb-navigation-BbqfmkYs.d.ts +147 -0
  5. package/dist/crumb-navigation-YnFVFqrx.js +1290 -0
  6. package/dist/crumbs.d.ts +2 -115
  7. package/dist/crumbs.js +1 -1179
  8. package/dist/{data-grid-C1sZX6n9.js → data-grid-Cqxuwgbb.js} +615 -210
  9. package/dist/{data-grid-D7qzmMMp.d.ts → data-grid-Csh5EJW9.d.ts} +55 -4
  10. package/dist/data-grid.d.ts +3 -3
  11. package/dist/data-grid.js +1 -1
  12. package/dist/{detail-list-q8bs67BM.js → detail-list-Bo5ze3SN.js} +234 -9
  13. package/dist/{detail-list-CKIKlszv.d.ts → detail-list-DSZ1DDor.d.ts} +48 -4
  14. package/dist/detail-list.d.ts +4 -4
  15. package/dist/detail-list.js +1 -1
  16. package/dist/{flat-list-DXMyDiSH.d.ts → flat-list-CT9kX3TQ.d.ts} +27 -4
  17. package/dist/flat-list.d.ts +2 -2
  18. package/dist/flat-list.js +123 -4
  19. package/dist/{grid-list-y6dWJYu3.js → grid-list-D0dJNVEF.js} +278 -76
  20. package/dist/{grid-list-0e7snW6t.d.ts → grid-list-DDLrWRxm.d.ts} +128 -22
  21. package/dist/grid-list.d.ts +3 -3
  22. package/dist/grid-list.js +1 -1
  23. package/dist/{incr-search-JIhwB49y.js → incr-search-CgbL-OlS.js} +140 -10
  24. package/dist/incr-search.d.ts +1 -1
  25. package/dist/incr-search.js +1 -1
  26. package/dist/index-9ttcdwlL.d.ts +71 -0
  27. package/dist/index-Bnm-kKGC.d.ts +475 -0
  28. package/dist/{index-KN2MW_xD.d.ts → index-CUErGhCg.d.ts} +31 -4
  29. package/dist/{index-DEVyVIn8.d.ts → index-D-lZNlHa.d.ts} +52 -6
  30. package/dist/index.d.ts +341 -21
  31. package/dist/index.js +2286 -884
  32. package/dist/{list-native-drag-controller-O2Nruqpi.d.ts → list-native-drag-controller-D_UrXipS.d.ts} +1 -1
  33. package/dist/{list-selection-controller-BeE70lbB.js → list-selection-controller-5L0Nj9tR.js} +87 -2
  34. package/dist/miller-DUqiUAWQ.js +2230 -0
  35. package/dist/miller.d.ts +4 -437
  36. package/dist/miller.js +1 -1815
  37. package/dist/narrowed-text.js +1 -1
  38. package/dist/pending-BGidHppL.js +91 -0
  39. package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
  40. package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-CophkNeU.d.ts} +41 -3
  41. package/dist/{split-layout-Deoxy47d.js → split-layout-DTY4PJRp.js} +251 -19
  42. package/dist/split-layout.d.ts +1 -1
  43. package/dist/split-layout.js +1 -1
  44. package/dist/sunburst-tree.d.ts +2 -2
  45. package/dist/sunburst-tree.js +1 -1
  46. package/dist/{sunbursttree-list-yXwS-uEI.js → sunbursttree-list-B_M0YTEA.js} +299 -80
  47. package/dist/{sunbursttree-list--Z0uyh8O.d.ts → sunbursttree-list-DePuFYlU.d.ts} +40 -3
  48. package/dist/tree-CftHwtEb.js +146 -0
  49. package/dist/{tree-D8VxvR9u.js → tree-DAlgvaHB.js} +276 -48
  50. package/dist/{tree-component-C6UHO-w9.d.ts → tree-component-DgfkxmVY.d.ts} +19 -8
  51. package/dist/tree.d.ts +3 -3
  52. package/dist/tree.js +1 -1
  53. package/dist/types-LU1u0CB8.d.ts +87 -0
  54. package/dist/vertical-list-DyKPKGxK.js +1993 -0
  55. package/dist/{vertical-list-D8MksoTp.d.ts → vertical-list-SAx56U-l.d.ts} +31 -3
  56. package/dist/vertical-list.d.ts +3 -3
  57. package/dist/vertical-list.js +1 -1789
  58. package/dist/view-state-controller-BdyXWUUM.js +305 -0
  59. package/docs-user/README.md +2 -0
  60. package/docs-user/crumbs.md +27 -0
  61. package/docs-user/data-grid.md +79 -5
  62. package/docs-user/detail-list.md +69 -0
  63. package/docs-user/dock.md +63 -12
  64. package/docs-user/flat-list.md +27 -0
  65. package/docs-user/grid-list.md +69 -13
  66. package/docs-user/hyperbolic-tree.md +31 -4
  67. package/docs-user/icicle-tree.md +36 -0
  68. package/docs-user/index.json +12 -2
  69. package/docs-user/llms.txt +2 -0
  70. package/docs-user/miller.md +52 -21
  71. package/docs-user/multicol-list.md +50 -8
  72. package/docs-user/split-layout.md +45 -17
  73. package/docs-user/sunburst-tree.md +32 -2
  74. package/docs-user/tree.md +45 -6
  75. package/docs-user/treemap.md +32 -0
  76. package/docs-user/twosplit-layout.md +22 -0
  77. package/docs-user/user-catalog.md +1 -1
  78. package/docs-user/vertical-list.md +55 -7
  79. package/llms.txt +2 -0
  80. package/package.json +2 -2
@@ -3,7 +3,7 @@ import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
3
3
  import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
4
4
  import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
5
5
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
6
- import { p as createIncrSearch } from "./incr-search-JIhwB49y.js";
6
+ import { p as createIncrSearch } from "./incr-search-CgbL-OlS.js";
7
7
  import "./interaction-E-uMGHkJ.js";
8
8
  import { LitElement, css, html, nothing } from "lit";
9
9
  import { property, query, state } from "lit/decorators.js";
@@ -0,0 +1,91 @@
1
+ //#region src/common/view-state/pending.ts
2
+ /**
3
+ * Pure helpers for *pending* view state: keyed intent that is waiting for a
4
+ * matching item to appear in the data.
5
+ *
6
+ * Pending sets / maps are insertion-ordered (oldest first) and capped so a
7
+ * long-running app with churning data can't grow them without bound.
8
+ */
9
+ /** Default cap on pending keys per facet. Oldest entries are dropped first. */
10
+ const MAX_PENDING_KEYS = 2e3;
11
+ const EMPTY_KEYS = /* @__PURE__ */ new Set();
12
+ const EMPTY_FLAGS = /* @__PURE__ */ new Map();
13
+ /**
14
+ * Split `keys` into those `has` reports present (to apply now) and those
15
+ * still missing (to keep pending). Returns the input set untouched as
16
+ * `missing` when nothing matched, so callers can cheaply detect "no change".
17
+ */
18
+ function partitionKeys(keys, has) {
19
+ if (keys.size === 0) return {
20
+ present: [],
21
+ missing: keys
22
+ };
23
+ const present = [];
24
+ const missing = /* @__PURE__ */ new Set();
25
+ for (const key of keys) if (has(key)) present.push(key);
26
+ else missing.add(key);
27
+ return {
28
+ present,
29
+ missing: present.length === 0 ? keys : missing
30
+ };
31
+ }
32
+ /** As {@link partitionKeys}, for keyed boolean flags (e.g. expanded / collapsed). */
33
+ function partitionFlags(flags, has) {
34
+ if (flags.size === 0) return {
35
+ present: [],
36
+ missing: flags
37
+ };
38
+ const present = [];
39
+ const missing = /* @__PURE__ */ new Map();
40
+ for (const [key, value] of flags) if (has(key)) present.push([key, value]);
41
+ else missing.set(key, value);
42
+ return {
43
+ present,
44
+ missing: present.length === 0 ? flags : missing
45
+ };
46
+ }
47
+ /** Keep at most `max` keys, dropping the oldest. */
48
+ function capKeys(keys, max = MAX_PENDING_KEYS) {
49
+ if (keys.size <= max) return keys;
50
+ return new Set([...keys].slice(keys.size - max));
51
+ }
52
+ /** Keep at most `max` flags, dropping the oldest. */
53
+ function capFlags(flags, max = MAX_PENDING_KEYS) {
54
+ if (flags.size <= max) return flags;
55
+ return new Map([...flags].slice(flags.size - max));
56
+ }
57
+ /** `flags` without `key` (same instance when absent). */
58
+ function withoutFlag(flags, key) {
59
+ if (!flags.has(key)) return flags;
60
+ const next = new Map(flags);
61
+ next.delete(key);
62
+ return next;
63
+ }
64
+ /** JSON record → flags map. Non-boolean values are ignored. */
65
+ function flagsFromRecord(record) {
66
+ if (!record) return EMPTY_FLAGS;
67
+ const out = /* @__PURE__ */ new Map();
68
+ for (const [key, value] of Object.entries(record)) if (typeof value === `boolean`) out.set(key, value);
69
+ return out;
70
+ }
71
+ /**
72
+ * Combine pending and live flags into one JSON record. Live entries win when
73
+ * a key is in both (it shouldn't be — matched keys leave pending).
74
+ */
75
+ function flagsToRecord(...sources) {
76
+ const out = {};
77
+ for (const source of sources) for (const [key, value] of source) out[key] = value;
78
+ return out;
79
+ }
80
+ /** Pending keys followed by live keys, de-duplicated, as a JSON array. */
81
+ function keysToArray(...sources) {
82
+ const out = /* @__PURE__ */ new Set();
83
+ for (const source of sources) for (const key of source) out.add(key);
84
+ return [...out];
85
+ }
86
+ /** Map `keys` to `true` flags — for components that record a plain key set. */
87
+ function keysAsFlags(keys, value = true) {
88
+ return [...keys].map((k) => [k, value]);
89
+ }
90
+ //#endregion
91
+ export { capKeys as a, keysAsFlags as c, partitionKeys as d, withoutFlag as f, capFlags as i, keysToArray as l, EMPTY_KEYS as n, flagsFromRecord as o, MAX_PENDING_KEYS as r, flagsToRecord as s, EMPTY_FLAGS as t, partitionFlags as u };
@@ -0,0 +1,35 @@
1
+ //#region src/interaction/scroll-hover-guard.ts
2
+ const SCROLL_HOVER_SUPPRESS_ATTR = `data-scroll-hover-suppress`;
3
+ function createScrollHoverGuard(config) {
4
+ const { host, enabled, onResume } = config;
5
+ let suppressed = false;
6
+ let lastX = NaN;
7
+ let lastY = NaN;
8
+ const end = () => {
9
+ if (!suppressed) return;
10
+ suppressed = false;
11
+ host.toggleAttribute(SCROLL_HOVER_SUPPRESS_ATTR, false);
12
+ };
13
+ const onScroll = () => {
14
+ if (suppressed || !enabled()) return;
15
+ suppressed = true;
16
+ host.toggleAttribute(SCROLL_HOVER_SUPPRESS_ATTR, true);
17
+ };
18
+ const onPointerMove = (e) => {
19
+ const moved = !Number.isNaN(lastX) && (e.clientX !== lastX || e.clientY !== lastY);
20
+ lastX = e.clientX;
21
+ lastY = e.clientY;
22
+ if (!moved || !suppressed) return;
23
+ end();
24
+ onResume?.(e.clientX, e.clientY);
25
+ };
26
+ return {
27
+ suppressed: () => suppressed,
28
+ onScroll,
29
+ onPointerMove,
30
+ onPointerLeave: end,
31
+ end
32
+ };
33
+ }
34
+ //#endregion
35
+ export { createScrollHoverGuard as n, SCROLL_HOVER_SUPPRESS_ATTR as t };
@@ -1,3 +1,5 @@
1
+ import { a as ViewStateBase, t as GetViewStateOptions } from "./types-LU1u0CB8.js";
2
+ import "./index-9ttcdwlL.js";
1
3
  import { LitElement, nothing } from "lit";
2
4
  //#region src/split-layout/types.d.ts
3
5
  type Axis = `row` | `column`;
@@ -35,6 +37,24 @@ interface LayoutSnapshot {
35
37
  version: 1;
36
38
  root: SnapshotNode;
37
39
  }
40
+ /**
41
+ * Saved size of one direct child of an `ixfx-split-layout`, keyed by the
42
+ * child's `id` in {@link SplitLayoutViewState.layout}.
43
+ */
44
+ interface SplitPanelViewState {
45
+ /**
46
+ * Share (0..1) of the weighted flow. For a collapsed panel, the share it
47
+ * returns to when expanded.
48
+ */
49
+ readonly weight: number;
50
+ /** Collapsed to the gutter. Absent ⇒ expanded. */
51
+ readonly collapsed?: boolean;
52
+ }
53
+ /** `ixfx-split-layout` view state (`kind: 'split-layout'`). */
54
+ type SplitLayoutViewState = ViewStateBase & {
55
+ /** Direct children with an author-given `id`, keyed by that id. */
56
+ readonly layout?: Readonly<Record<string, SplitPanelViewState>>;
57
+ };
38
58
  interface PanelInfo {
39
59
  id: string;
40
60
  el: HTMLElement;
@@ -100,10 +120,17 @@ declare global {
100
120
  //#endregion
101
121
  //#region src/split-layout/split-layout.d.ts
102
122
  declare class SplitLayoutElement extends LitElement {
123
+ #private;
103
124
  direction: Axis;
104
125
  sizes: string;
105
126
  gutterSize: string;
106
127
  snap: string;
128
+ /**
129
+ * Forget the saved size of a child that leaves instead of re-applying it
130
+ * when a child with that id returns. For hosts that drive child sizes
131
+ * themselves (e.g. `ixfx-dock-host`, whose leaf ids are positional).
132
+ */
133
+ forgetAbsent: boolean;
107
134
  private _model;
108
135
  private _resizing;
109
136
  private _groupEl?;
@@ -116,9 +143,20 @@ declare class SplitLayoutElement extends LitElement {
116
143
  firstUpdated(): void;
117
144
  private _setupObserver;
118
145
  private _rebuildModel;
146
+ private _writeCollapsedAttributes;
119
147
  get panels(): PanelInfo[];
120
- getLayout(): LayoutSnapshot | null;
121
- setLayout(s: LayoutSnapshot): void;
148
+ /**
149
+ * Snapshot the size and collapse of every direct child with an `id`.
150
+ * Includes saved entries for children not currently slotted.
151
+ */
152
+ getViewState(options?: GetViewStateOptions<SplitLayoutViewState>): SplitLayoutViewState;
153
+ /**
154
+ * Apply saved sizes. Entries for children that aren't slotted yet are kept
155
+ * and applied when a child with that id appears. Present children without
156
+ * an entry keep their relative size.
157
+ */
158
+ setViewState(state: Partial<SplitLayoutViewState>): void;
159
+ clearPendingViewState(): void;
122
160
  getSizes(_groupId?: string): Record<string, number>;
123
161
  setSizes(weights: number[] | Record<string, number>, _groupId?: string): void;
124
162
  collapse(idOrEl: string | HTMLElement): void;
@@ -175,4 +213,4 @@ declare global {
175
213
  }
176
214
  }
177
215
  //#endregion
178
- export { SplitExpandEvent as _, LayoutNode as a, SplitResizeEvent as b, PanelNode as c, SplitChangeEvent as d, SplitCloseDetail as f, SplitExpandDetail as g, SplitCollapseEvent as h, GroupNode as i, SnapshotNode as l, SplitCollapseDetail as m, SplitPanelElement as n, LayoutSnapshot as o, SplitCloseEvent as p, Axis as r, PanelInfo as s, SplitLayoutElement as t, SplitChangeDetail as u, SplitLayoutElementEventMap as v, WhenSmall as x, SplitResizeDetail as y };
216
+ export { WhenSmall as C, SplitResizeEvent as S, SplitExpandEvent as _, LayoutNode as a, SplitPanelViewState as b, PanelNode as c, SplitChangeEvent as d, SplitCloseDetail as f, SplitExpandDetail as g, SplitCollapseEvent as h, GroupNode as i, SnapshotNode as l, SplitCollapseDetail as m, SplitPanelElement as n, LayoutSnapshot as o, SplitCloseEvent as p, Axis as r, PanelInfo as s, SplitLayoutElement as t, SplitChangeDetail as u, SplitLayoutElementEventMap as v, SplitResizeDetail as x, SplitLayoutViewState as y };
@@ -1,6 +1,8 @@
1
1
  import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
2
2
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
3
3
  import { t as handleStyles } from "./handle-DAXs7kbJ.js";
4
+ import { r as MAX_PENDING_KEYS } from "./pending-BGidHppL.js";
5
+ import { t as ViewStateController } from "./view-state-controller-BdyXWUUM.js";
4
6
  import { LitElement, css, html, nothing } from "lit";
5
7
  import { property, query, state } from "lit/decorators.js";
6
8
  //#region src/split-layout/split-drag.ts
@@ -389,6 +391,163 @@ function walkInner(node, path, visitor) {
389
391
  if (node.kind === `group`) for (let i = 0; i < node.children.length; i++) walkInner(node.children[i], [...path, i], visitor);
390
392
  }
391
393
  //#endregion
394
+ //#region src/split-layout/split-view-state.ts
395
+ const EMPTY_ENTRIES = /* @__PURE__ */ new Map();
396
+ /** Whether `node` has an author-given id (and can therefore carry view state). */
397
+ function isKeyed(node) {
398
+ return node.id !== `` && node.el?.id === node.id;
399
+ }
400
+ /** Saved form of one child. Collapsed panels record the share they expand back to. */
401
+ function entryOf(node) {
402
+ if (node.kind === `panel` && node.collapsed) return {
403
+ weight: node.restoreWeight,
404
+ collapsed: true
405
+ };
406
+ return { weight: node.weight };
407
+ }
408
+ /** Entries for every keyed direct child of `group`. */
409
+ function captureLayout(group) {
410
+ const out = /* @__PURE__ */ new Map();
411
+ for (const child of group.children) if (isKeyed(child)) out.set(child.id, entryOf(child));
412
+ return out;
413
+ }
414
+ /** Combine entry sources into one JSON record (later sources win). */
415
+ function layoutToRecord(...sources) {
416
+ const out = {};
417
+ for (const source of sources) for (const [id, entry] of source) out[id] = entry;
418
+ return out;
419
+ }
420
+ /** JSON record → entries. Entries without a finite, non-negative `weight` are dropped. */
421
+ function layoutFromRecord(record) {
422
+ if (!record || typeof record !== `object`) return EMPTY_ENTRIES;
423
+ const out = /* @__PURE__ */ new Map();
424
+ for (const [id, entry] of Object.entries(record)) {
425
+ const weight = entry?.weight;
426
+ if (typeof weight !== `number` || !Number.isFinite(weight) || weight < 0) continue;
427
+ out.set(id, entry.collapsed === true ? {
428
+ weight,
429
+ collapsed: true
430
+ } : { weight });
431
+ }
432
+ return capEntries(out);
433
+ }
434
+ /** Keep at most `max` entries, dropping the oldest. */
435
+ function capEntries(entries, max = MAX_PENDING_KEYS) {
436
+ if (entries.size <= max) return entries;
437
+ return new Map([...entries].slice(entries.size - max));
438
+ }
439
+ /**
440
+ * Reconcile a freshly built (normalised) `next` model with the previous one
441
+ * and pending entries:
442
+ *
443
+ * - keyed children of `prev` that are gone → pending (when `retainAbsent`)
444
+ * - pending entries whose child is now present → applied (weight + collapse)
445
+ * and removed from pending
446
+ * - with `carry`, children that were already there keep their live weight;
447
+ * children that are new keep the share the DOM gives them
448
+ *
449
+ * Applied and new children are *pinned* to their share; carried children
450
+ * scale to fill what is left, so restoring three of five saved panels and
451
+ * later slotting the other two lands on the saved proportions.
452
+ */
453
+ function reconcileLayout(prev, next, pending, options) {
454
+ const nextIds = new Set(next.children.map((c) => c.id));
455
+ const nextPending = new Map(pending);
456
+ const demoted = [];
457
+ if (prev && options.retainAbsent) {
458
+ for (const child of prev.children) if (isKeyed(child) && !nextIds.has(child.id)) {
459
+ nextPending.set(child.id, entryOf(child));
460
+ demoted.push(child.id);
461
+ }
462
+ }
463
+ const prevById = options.carry && prev ? new Map(prev.children.map((c) => [c.id, c])) : void 0;
464
+ const applied = [];
465
+ const collapse = [];
466
+ const shares = [];
467
+ const children = next.children.map((child, i) => {
468
+ const entry = isKeyed(child) ? nextPending.get(child.id) : void 0;
469
+ if (entry) {
470
+ nextPending.delete(child.id);
471
+ applied.push(child.id);
472
+ shares[i] = {
473
+ value: entry.weight,
474
+ pinned: true
475
+ };
476
+ if (child.kind === `group`) return {
477
+ ...child,
478
+ weight: entry.weight
479
+ };
480
+ const collapsed = entry.collapsed === true;
481
+ if (collapsed !== child.collapsed) collapse.push([child.el, collapsed]);
482
+ return {
483
+ ...child,
484
+ collapsed,
485
+ weight: entry.weight,
486
+ restoreWeight: entry.weight
487
+ };
488
+ }
489
+ const before = prevById?.get(child.id);
490
+ if (before && sameShape(before, child)) {
491
+ shares[i] = {
492
+ value: before.weight,
493
+ pinned: false
494
+ };
495
+ return {
496
+ ...child,
497
+ weight: before.weight
498
+ };
499
+ }
500
+ shares[i] = {
501
+ value: child.weight,
502
+ pinned: prevById !== void 0
503
+ };
504
+ return child;
505
+ });
506
+ return {
507
+ model: {
508
+ ...next,
509
+ children: mergeShares(children, shares)
510
+ },
511
+ pending: demoted.length === 0 && applied.length === 0 ? pending : capEntries(nextPending),
512
+ applied,
513
+ demoted,
514
+ collapse
515
+ };
516
+ }
517
+ function sameShape(a, b) {
518
+ if (a.kind !== b.kind) return false;
519
+ return a.kind === `group` || b.kind === `panel` && a.collapsed === b.collapsed;
520
+ }
521
+ function isFlexible(node) {
522
+ return node.kind === `group` || node.fixedPx === null && !node.collapsed;
523
+ }
524
+ /**
525
+ * Pinned flexible children keep their share; floating ones scale to fill the
526
+ * rest. With nothing floating (or pins over-full) every flexible share is
527
+ * simply normalised.
528
+ */
529
+ function mergeShares(children, shares) {
530
+ let pinnedTotal = 0;
531
+ let floatingTotal = 0;
532
+ children.forEach((child, i) => {
533
+ if (!isFlexible(child)) return;
534
+ if (shares[i].pinned) pinnedTotal += shares[i].value;
535
+ else floatingTotal += shares[i].value;
536
+ });
537
+ const scaleFloating = floatingTotal > 0 && pinnedTotal < 1;
538
+ const total = scaleFloating ? 1 : pinnedTotal + floatingTotal || 1;
539
+ const floatScale = scaleFloating ? (1 - pinnedTotal) / floatingTotal : 1 / total;
540
+ const pinScale = scaleFloating ? 1 : 1 / total;
541
+ return children.map((child, i) => {
542
+ if (!isFlexible(child)) return child;
543
+ const weight = shares[i].value * (shares[i].pinned ? pinScale : floatScale);
544
+ return weight === child.weight ? child : {
545
+ ...child,
546
+ weight
547
+ };
548
+ });
549
+ }
550
+ //#endregion
392
551
  //#region src/split-layout/split-panel.ts
393
552
  let SplitPanelElement = class SplitPanelElement extends LitElement {
394
553
  constructor(..._args) {
@@ -449,11 +608,17 @@ let SplitLayoutElement = class SplitLayoutElement extends LitElement {
449
608
  this.sizes = ``;
450
609
  this.gutterSize = ``;
451
610
  this.snap = ``;
611
+ this.forgetAbsent = false;
452
612
  this._model = null;
453
613
  this._resizing = false;
454
614
  this._observer = null;
455
615
  this._applyingOwnMutation = false;
456
616
  this._dragStructurallyChanged = false;
617
+ this.#viewState = new ViewStateController(this, {
618
+ kind: `split-layout`,
619
+ capture: () => this.getViewState()
620
+ });
621
+ this.#pendingLayout = EMPTY_ENTRIES;
457
622
  this._dragMachine = createDragMachine({
458
623
  onDragStart: (state) => this._handleDragStart(state),
459
624
  onDragMove: (state, deltaX, deltaY) => this._handleDragMove(state, deltaX, deltaY),
@@ -479,6 +644,9 @@ let SplitLayoutElement = class SplitLayoutElement extends LitElement {
479
644
  window.removeEventListener(`pointerup`, this._onPointerUp);
480
645
  };
481
646
  }
647
+ #viewState;
648
+ /** Saved entries for children not currently present. */
649
+ #pendingLayout;
482
650
  connectedCallback() {
483
651
  super.connectedCallback();
484
652
  this._setupObserver();
@@ -491,8 +659,11 @@ let SplitLayoutElement = class SplitLayoutElement extends LitElement {
491
659
  this._rebuildModel();
492
660
  }
493
661
  _setupObserver() {
494
- this._observer = new MutationObserver(() => {
495
- if (!this._applyingOwnMutation) Promise.resolve().then(() => this._rebuildModel());
662
+ this._observer = new MutationObserver((records) => {
663
+ if (!this._applyingOwnMutation) {
664
+ const carry = !records.some((r) => isOwnAttributeChange(this, r));
665
+ Promise.resolve().then(() => this._rebuildModel(carry));
666
+ }
496
667
  });
497
668
  this._observer.observe(this, {
498
669
  childList: true,
@@ -507,15 +678,33 @@ let SplitLayoutElement = class SplitLayoutElement extends LitElement {
507
678
  `direction`,
508
679
  `collapsed`,
509
680
  `sizes`
510
- ]
681
+ ],
682
+ attributeOldValue: true
511
683
  });
512
684
  }
513
- _rebuildModel() {
685
+ _rebuildModel(carry = true) {
514
686
  this._reindexSlots();
515
- const normalized = normalise(buildModel(this));
516
- this._model = preserveRestoreWeights(this._model, normalized);
687
+ const model = buildModel(this);
688
+ const normalized = preserveRestoreWeights(this._model, normalise(model));
689
+ const result = reconcileLayout(this._model, normalized, this.#pendingLayout, {
690
+ carry,
691
+ retainAbsent: !this.forgetAbsent
692
+ });
693
+ this._model = result.model;
694
+ this.#pendingLayout = result.pending;
695
+ this._writeCollapsedAttributes(result.collapse);
696
+ if (result.applied.length > 0) this.#viewState.restoring(() => this.#viewState.notify());
697
+ else if (result.demoted.length > 0) this.#viewState.notify(`data`);
517
698
  this.requestUpdate();
518
699
  }
700
+ _writeCollapsedAttributes(writes) {
701
+ if (writes.length === 0) return;
702
+ for (const [el, collapsed] of writes) {
703
+ el.toggleAttribute(`collapsed`, collapsed);
704
+ el.toggleAttribute(`data-collapsed`, collapsed);
705
+ }
706
+ this._observer?.takeRecords();
707
+ }
519
708
  get panels() {
520
709
  if (!this._model) return [];
521
710
  const panels = [];
@@ -534,17 +723,38 @@ let SplitLayoutElement = class SplitLayoutElement extends LitElement {
534
723
  });
535
724
  return panels;
536
725
  }
537
- getLayout() {
538
- if (!this._model) return null;
539
- return toSnapshot(this._model);
726
+ /**
727
+ * Snapshot the size and collapse of every direct child with an `id`.
728
+ * Includes saved entries for children not currently slotted.
729
+ */
730
+ getViewState(options) {
731
+ const withPending = options?.includePending ?? true;
732
+ const live = this._model ? captureLayout(this._model) : [];
733
+ return this.#viewState.shape({ layout: layoutToRecord(withPending ? this.#pendingLayout : [], live) }, options);
540
734
  }
541
- setLayout(s) {
542
- if (!this._model) return;
543
- this._applyingOwnMutation = true;
544
- this._model = applySnapshot(this._model, s);
545
- this.requestUpdate();
546
- this._applyingOwnMutation = false;
547
- this._emitChange();
735
+ /**
736
+ * Apply saved sizes. Entries for children that aren't slotted yet are kept
737
+ * and applied when a child with that id appears. Present children without
738
+ * an entry keep their relative size.
739
+ */
740
+ setViewState(state) {
741
+ if (!this.#viewState.accepts(state) || state.layout === void 0) return;
742
+ this.#viewState.restoring(() => {
743
+ this.#pendingLayout = layoutFromRecord(state.layout);
744
+ if (!this._model) {
745
+ this.#viewState.notify();
746
+ return;
747
+ }
748
+ this._applyingOwnMutation = true;
749
+ this._rebuildModel();
750
+ this._observer?.takeRecords();
751
+ this._applyingOwnMutation = false;
752
+ this._emitResize();
753
+ this._emitChange();
754
+ });
755
+ }
756
+ clearPendingViewState() {
757
+ this.#pendingLayout = EMPTY_ENTRIES;
548
758
  }
549
759
  getSizes(_groupId) {
550
760
  if (!this._model) return {};
@@ -818,9 +1028,9 @@ let SplitLayoutElement = class SplitLayoutElement extends LitElement {
818
1028
  }));
819
1029
  }
820
1030
  _emitChange() {
821
- const layout = this.getLayout();
822
- if (!layout) return;
823
- const detail = { layout };
1031
+ if (!this._model) return;
1032
+ this.#viewState.notify();
1033
+ const detail = { layout: toSnapshot(this._model) };
824
1034
  this.dispatchEvent(new CustomEvent(`ixfx-split-change`, {
825
1035
  detail,
826
1036
  bubbles: true,
@@ -1018,9 +1228,31 @@ __decorate([property({
1018
1228
  attribute: `gutter-size`
1019
1229
  })], SplitLayoutElement.prototype, "gutterSize", void 0);
1020
1230
  __decorate([property({ type: String })], SplitLayoutElement.prototype, "snap", void 0);
1231
+ __decorate([property({
1232
+ type: Boolean,
1233
+ attribute: `forget-absent`
1234
+ })], SplitLayoutElement.prototype, "forgetAbsent", void 0);
1021
1235
  __decorate([state()], SplitLayoutElement.prototype, "_model", void 0);
1022
1236
  __decorate([state()], SplitLayoutElement.prototype, "_resizing", void 0);
1023
1237
  __decorate([query(`.grid`)], SplitLayoutElement.prototype, "_groupEl", void 0);
1024
1238
  SplitLayoutElement = __decorate([safeCustomElement(`ixfx-split-layout`)], SplitLayoutElement);
1239
+ /** Attributes that set a child's share (or take it out of the weighted flow). */
1240
+ const WEIGHT_ATTRIBUTES = /* @__PURE__ */ new Set([
1241
+ `size`,
1242
+ `weight`,
1243
+ `sizes`,
1244
+ `fixed`,
1245
+ `collapsed`
1246
+ ]);
1247
+ /**
1248
+ * A weight attribute on `host` or one of its direct children whose value
1249
+ * really changed (not e.g. `direction` reflecting on first render).
1250
+ */
1251
+ function isOwnAttributeChange(host, record) {
1252
+ if (record.type !== `attributes` || record.attributeName === null || !WEIGHT_ATTRIBUTES.has(record.attributeName)) return false;
1253
+ const target = record.target;
1254
+ if (target !== host && target.parentElement !== host) return false;
1255
+ return target.getAttribute(record.attributeName) !== record.oldValue;
1256
+ }
1025
1257
  //#endregion
1026
1258
  export { distribute as a, preserveRestoreWeights as c, toSnapshot as d, walk as f, buildModel as i, resolveTracks as l, SplitPanelElement as n, findPanelNode as o, applySnapshot as r, normalise as s, SplitLayoutElement as t, setPanelRestoreWeight as u };
@@ -1,2 +1,2 @@
1
- import { t as SplitLayoutElement } from "./split-layout-JK3xWVX2.js";
1
+ import { t as SplitLayoutElement } from "./split-layout-CophkNeU.js";
2
2
  export { SplitLayoutElement };
@@ -1,2 +1,2 @@
1
- import { t as SplitLayoutElement } from "./split-layout-Deoxy47d.js";
1
+ import { t as SplitLayoutElement } from "./split-layout-DTY4PJRp.js";
2
2
  export { SplitLayoutElement };
@@ -1,2 +1,2 @@
1
- import { i as SunburstZoomEvent, n as SunburstTreeListElementEventMap, r as SunburstZoomDetail, t as SunburstTreeListElement } from "./sunbursttree-list--Z0uyh8O.js";
2
- export { SunburstTreeListElement, SunburstTreeListElementEventMap, SunburstZoomDetail, SunburstZoomEvent };
1
+ import { a as SunburstViewState, i as SunburstZoomEvent, n as SunburstTreeListElementEventMap, r as SunburstZoomDetail, t as SunburstTreeListElement } from "./sunbursttree-list-DePuFYlU.js";
2
+ export { SunburstTreeListElement, SunburstTreeListElementEventMap, type SunburstViewState, SunburstZoomDetail, SunburstZoomEvent };
@@ -1,2 +1,2 @@
1
- import { t as SunburstTreeListElement } from "./sunbursttree-list-yXwS-uEI.js";
1
+ import { t as SunburstTreeListElement } from "./sunbursttree-list-B_M0YTEA.js";
2
2
  export { SunburstTreeListElement };