@ixfx/components 0.7.0 → 0.7.2

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 (125) hide show
  1. package/bundle/index.d.ts +259 -44
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +828 -125
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.d.ts +1 -1
  6. package/dist/ac-text.js +1 -1
  7. package/dist/{ac-token-BAa6ke09.d.ts → ac-token--_vCHXWw.d.ts} +2 -2
  8. package/dist/{ac-token-BAa6ke09.d.ts.map → ac-token--_vCHXWw.d.ts.map} +1 -1
  9. package/dist/ac-token.d.ts +1 -1
  10. package/dist/ac-token.js +1 -1
  11. package/dist/{button-Dat2yHAU.js → button-DsFbV4XD.js} +3 -3
  12. package/dist/{button-Dat2yHAU.js.map → button-DsFbV4XD.js.map} +1 -1
  13. package/dist/button.d.ts.map +1 -1
  14. package/dist/button.js +1 -1
  15. package/dist/{colour-picker-BSDBL-QL.js → colour-picker-Ddsk-7E_.js} +2 -2
  16. package/dist/{colour-picker-BSDBL-QL.js.map → colour-picker-Ddsk-7E_.js.map} +1 -1
  17. package/dist/colour-picker.js +1 -1
  18. package/dist/crumbs.d.ts +1 -1
  19. package/dist/crumbs.js +1 -1
  20. package/dist/{data-grid-C8qIo1-u.d.ts → data-grid-Bh4YrQGK.d.ts} +2 -2
  21. package/dist/{data-grid-C8qIo1-u.d.ts.map → data-grid-Bh4YrQGK.d.ts.map} +1 -1
  22. package/dist/data-grid.d.ts +1 -1
  23. package/dist/data-grid.js +1 -1
  24. package/dist/{defaults-BkWCPJFL.js → defaults-BJwDsMH4.js} +26 -2
  25. package/dist/defaults-BJwDsMH4.js.map +1 -0
  26. package/dist/{hex-editor-Bk6rxhfg.js → hex-editor-BiRfwTz1.js} +2 -2
  27. package/dist/{hex-editor-Bk6rxhfg.js.map → hex-editor-BiRfwTz1.js.map} +1 -1
  28. package/dist/hex.js +1 -1
  29. package/dist/{icon-DpA9x2Ve.d.ts → icon-CDGprSdE.d.ts} +1 -12
  30. package/dist/icon-CDGprSdE.d.ts.map +1 -0
  31. package/dist/{icon-CvnPanhA.js → icon-DcEEGQa_.js} +37 -8
  32. package/dist/icon-DcEEGQa_.js.map +1 -0
  33. package/dist/icons.d.ts +3 -3
  34. package/dist/icons.js +4 -4
  35. package/dist/icons.js.map +1 -1
  36. package/dist/{index-CKtLhgYj.d.ts → index-9AhaHIT2.d.ts} +3 -3
  37. package/dist/{index-CKtLhgYj.d.ts.map → index-9AhaHIT2.d.ts.map} +1 -1
  38. package/dist/{index-BtGFYfaK.d.ts → index-CS9yM8L-.d.ts} +14 -4
  39. package/dist/index-CS9yM8L-.d.ts.map +1 -0
  40. package/dist/{index-CG2vIgPv.d.ts → index-UvnoZUAo.d.ts} +4 -4
  41. package/dist/{index-CG2vIgPv.d.ts.map → index-UvnoZUAo.d.ts.map} +1 -1
  42. package/dist/index.d.ts +231 -26
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +701 -110
  45. package/dist/index.js.map +1 -1
  46. package/dist/labelled-radial-input.d.ts +1 -1
  47. package/dist/labelled-radial-input.js +1 -1
  48. package/dist/{labelled-slider-input-DzigjMwP.js → labelled-slider-input-BInB5GKL.js} +3 -3
  49. package/dist/{labelled-slider-input-DzigjMwP.js.map → labelled-slider-input-BInB5GKL.js.map} +1 -1
  50. package/dist/labelled-slider-input.d.ts +1 -1
  51. package/dist/labelled-slider-input.js +1 -1
  52. package/dist/{menu-BjRvlgPy.js → menu-CXDFJxNm.js} +3 -3
  53. package/dist/{menu-BjRvlgPy.js.map → menu-CXDFJxNm.js.map} +1 -1
  54. package/dist/{menu-item-Bs2MYyHq.d.ts → menu-item-CZi9vy5O.d.ts} +4 -2
  55. package/dist/menu-item-CZi9vy5O.d.ts.map +1 -0
  56. package/dist/{menu-item-B-zYrTTi.js → menu-item-D2S4i8u1.js} +16 -4
  57. package/dist/menu-item-D2S4i8u1.js.map +1 -0
  58. package/dist/menu.d.ts +2 -2
  59. package/dist/menu.js +2 -2
  60. package/dist/notification-pill-DyZydXoZ.d.ts.map +1 -1
  61. package/dist/notifier.js +2 -2
  62. package/dist/notifier.js.map +1 -1
  63. package/dist/panel.d.ts +1 -1
  64. package/dist/plots.js +1 -1
  65. package/dist/polar-pad.d.ts +2 -2
  66. package/dist/polar-pad.js +1 -1
  67. package/dist/{radial-input-Dp5qz0ly.js → radial-input-BfA3GO0H.js} +2 -2
  68. package/dist/{radial-input-Dp5qz0ly.js.map → radial-input-BfA3GO0H.js.map} +1 -1
  69. package/dist/{radial-input-DXMN55Nz.d.ts → radial-input-DU6t2hYO.d.ts} +3 -3
  70. package/dist/{radial-input-DXMN55Nz.d.ts.map → radial-input-DU6t2hYO.d.ts.map} +1 -1
  71. package/dist/radial-input.d.ts +1 -1
  72. package/dist/radial-input.js +1 -1
  73. package/dist/range-input.d.ts +2 -2
  74. package/dist/range-input.js +1 -1
  75. package/dist/range.d.ts +2 -2
  76. package/dist/range.js +1 -1
  77. package/dist/registry-BEtb8_wB.js +46 -0
  78. package/dist/registry-BEtb8_wB.js.map +1 -0
  79. package/dist/select-horiz.d.ts +2 -2
  80. package/dist/select-horiz.js +1 -1
  81. package/dist/{slider-input-De4eoT11.js → slider-input-Bl1hFE89.js} +2 -2
  82. package/dist/{slider-input-De4eoT11.js.map → slider-input-Bl1hFE89.js.map} +1 -1
  83. package/dist/{slider-input-BfRUW2ZB.d.ts → slider-input-BxURAAmD.d.ts} +3 -3
  84. package/dist/{slider-input-BfRUW2ZB.d.ts.map → slider-input-BxURAAmD.d.ts.map} +1 -1
  85. package/dist/slider-input.d.ts +1 -1
  86. package/dist/slider-input.js +1 -1
  87. package/dist/swipe.d.ts +2 -2
  88. package/dist/swipe.js +1 -1
  89. package/dist/{tab-list-MPwq_1Jj.d.ts → tab-list-CGyPt07Z.d.ts} +9 -2
  90. package/dist/tab-list-CGyPt07Z.d.ts.map +1 -0
  91. package/dist/tabs.d.ts +1 -1
  92. package/dist/tabs.js +32 -13
  93. package/dist/tabs.js.map +1 -1
  94. package/dist/{titlebar-DaCfpliv.d.ts → titlebar-Bj5pfdw_.d.ts} +2 -2
  95. package/dist/{titlebar-DaCfpliv.d.ts.map → titlebar-Bj5pfdw_.d.ts.map} +1 -1
  96. package/dist/titlebar.d.ts +1 -1
  97. package/dist/titlebar.js +1 -1
  98. package/dist/tooltip-BlvBUQxx.d.ts.map +1 -1
  99. package/dist/wheel-nudge-By5s_ioH.d.ts +31 -0
  100. package/dist/wheel-nudge-By5s_ioH.d.ts.map +1 -0
  101. package/dist/{wheel-nudge-BlSCe_hD.js → wheel-nudge-D5rmht3v.js} +6 -1
  102. package/dist/wheel-nudge-D5rmht3v.js.map +1 -0
  103. package/dist/{xy-axis-CxHC8z5E.js → xy-axis-DWizs0bt.js} +3 -3
  104. package/dist/xy-axis-DWizs0bt.js.map +1 -0
  105. package/dist/xy-pad.d.ts +2 -2
  106. package/dist/xy-pad.js +1 -1
  107. package/docs-user/dock.md +166 -15
  108. package/docs-user/icons.md +4 -0
  109. package/docs-user/index.json +2 -2
  110. package/docs-user/tabs.md +20 -0
  111. package/docs-user/user-theming.md +109 -0
  112. package/package.json +1 -1
  113. package/dist/defaults-BkWCPJFL.js.map +0 -1
  114. package/dist/icon-CvnPanhA.js.map +0 -1
  115. package/dist/icon-DpA9x2Ve.d.ts.map +0 -1
  116. package/dist/index-BtGFYfaK.d.ts.map +0 -1
  117. package/dist/menu-item-B-zYrTTi.js.map +0 -1
  118. package/dist/menu-item-Bs2MYyHq.d.ts.map +0 -1
  119. package/dist/registry-CkW7q09Q.js +0 -21
  120. package/dist/registry-CkW7q09Q.js.map +0 -1
  121. package/dist/tab-list-MPwq_1Jj.d.ts.map +0 -1
  122. package/dist/wheel-nudge-BlSCe_hD.js.map +0 -1
  123. package/dist/wheel-nudge-DGrNzWuE.d.ts +0 -29
  124. package/dist/wheel-nudge-DGrNzWuE.d.ts.map +0 -1
  125. package/dist/xy-axis-CxHC8z5E.js.map +0 -1
package/dist/index.js CHANGED
@@ -3,21 +3,21 @@ import { t as TickledController } from "./tickled-controller-h9GmJ_bU.js";
3
3
  import { t as themeFallbacks } from "./fallbacks-DNI9xt0F.js";
4
4
  import { t as popupSurfaceStyles } from "./popup-D80sj2QC.js";
5
5
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
6
- import { a as svgToDataUri, i as registerIcon, n as hasIcon, r as iconBus, t as getIcon } from "./registry-CkW7q09Q.js";
7
- import { t as IxfxIconElement } from "./icon-CvnPanhA.js";
6
+ import { a as iconBus, c as setIconFont, i as hasIcon, l as svgToDataUri, n as getIconEntry, o as registerFontIcon, r as getIconFont, s as registerIcon, t as getIcon } from "./registry-BEtb8_wB.js";
7
+ import { t as IxfxIconElement } from "./icon-DcEEGQa_.js";
8
8
  import { AcTextElement } from "./ac-text.js";
9
9
  import { t as DataController } from "./data-controller-DOlmqNKb.js";
10
10
  import { n as createIncrSearch, r as createFuzzySearch, t as createElementIncrSearch } from "./element-search-Y8SRfPGM.js";
11
11
  import { n as createAcCompletionProvider, t as IncrSearchTreeController } from "./incr-search-vCzuao-2.js";
12
12
  import { AcTokenElement } from "./ac-token.js";
13
- import { i as ICON_CLOSE, n as ICON_CHECK, r as ICON_CHEVRON_DOWN, t as ICON_CARET_RIGHT } from "./defaults-BkWCPJFL.js";
13
+ import { i as ICON_CLOSE, n as ICON_CHECK, r as ICON_CHEVRON_DOWN, t as ICON_CARET_RIGHT } from "./defaults-BJwDsMH4.js";
14
14
  import "./icons.js";
15
15
  import { t as prominenceStyles } from "./prominence-DlFShic3.js";
16
- import { n as buttonStyles, t as ButtonElement } from "./button-Dat2yHAU.js";
16
+ import { n as buttonStyles, t as ButtonElement } from "./button-DsFbV4XD.js";
17
17
  import { n as ListSelectionController, t as VerticalListElement } from "./vertical-list-ClnqTkF3.js";
18
- import "./menu-item-B-zYrTTi.js";
18
+ import "./menu-item-D2S4i8u1.js";
19
19
  import { Checkbox } from "./checkbox.js";
20
- import { n as ColourPicker, t as ColourPickerPopup } from "./colour-picker-BSDBL-QL.js";
20
+ import { n as ColourPicker, t as ColourPickerPopup } from "./colour-picker-Ddsk-7E_.js";
21
21
  import { LabelledRangeInput } from "./labelled-range-input.js";
22
22
  import { t as collectDataResult } from "./data-provider-35TyWclX.js";
23
23
  import { i as TreeDataModel, n as TreeListElement, r as TreeBaseElement, t as TreeController } from "./tree-Ch4W8qXQ.js";
@@ -26,24 +26,24 @@ import { NarrowedTextElement } from "./narrowed-text.js";
26
26
  import { CrumbNavigationElement } from "./crumbs.js";
27
27
  import { DataDisplayComponent } from "./data-display.js";
28
28
  import { t as createDragGhost } from "./drag-ghost-DtSDvjri.js";
29
- import { t as menu_exports } from "./menu-BjRvlgPy.js";
29
+ import { t as menu_exports } from "./menu-CXDFJxNm.js";
30
30
  import { DataGridElement } from "./data-grid.js";
31
31
  import { a as distribute, c as toSnapshot, i as buildModel, l as walk, n as SplitPanelElement, o as normalise, r as applySnapshot, s as resolveTracks, t as SplitLayoutElement } from "./split-layout-BFSbUZQv.js";
32
32
  import { TabListElement } from "./tabs.js";
33
33
  import { TitlebarElement } from "./titlebar.js";
34
34
  import { EditableLabel } from "./editable-label.js";
35
- import { n as EditableNumber, t as LabelledSliderInput } from "./labelled-slider-input-DzigjMwP.js";
35
+ import { n as EditableNumber, t as LabelledSliderInput } from "./labelled-slider-input-BInB5GKL.js";
36
36
  import { t as FormElement } from "./form-CsXJX15p.js";
37
37
  import { PanelElement } from "./panel.js";
38
38
  import { GroupedItemListerElement } from "./grouped-item-lister.js";
39
- import { t as IxfxHexEditorElement } from "./hex-editor-Bk6rxhfg.js";
40
- import { t as RadialInputElement } from "./radial-input-Dp5qz0ly.js";
39
+ import { t as IxfxHexEditorElement } from "./hex-editor-BiRfwTz1.js";
40
+ import { t as RadialInputElement } from "./radial-input-BfA3GO0H.js";
41
41
  import { LabelledRadialInput } from "./labelled-radial-input.js";
42
- import { t as SliderInputElement } from "./slider-input-De4eoT11.js";
42
+ import { t as SliderInputElement } from "./slider-input-Bl1hFE89.js";
43
43
  import { LedElement } from "./led.js";
44
44
  import { MillerBaseElement, MillerListElement, MillerTreeController } from "./miller.js";
45
45
  import { NotificationPillElement } from "./notifier.js";
46
- import { a as TooltipElement, c as computeColour, i as PlotMultiAxis, l as parseColourScaleAttr, n as PlotSingleAxis, o as PlotDataSeries, r as PlotHistogram, s as applyAgeModification, t as PlotXyAxis, u as readBaseColour } from "./xy-axis-CxHC8z5E.js";
46
+ import { a as TooltipElement, c as computeColour, i as PlotMultiAxis, l as parseColourScaleAttr, n as PlotSingleAxis, o as PlotDataSeries, r as PlotHistogram, s as applyAgeModification, t as PlotXyAxis, u as readBaseColour } from "./xy-axis-DWizs0bt.js";
47
47
  import { PolarPad } from "./polar-pad.js";
48
48
  import { t as tooltipValueStyles } from "./tooltip-CJt3WLmN.js";
49
49
  import { RangeInput } from "./range-input.js";
@@ -2526,10 +2526,11 @@ function unregisterDockHost(host) {
2526
2526
  * Begin a dock-panel drag. Called by hosts in response to their own
2527
2527
  * `dock-panel-drag-start` events (titlebar or tab-item handles).
2528
2528
  */
2529
- function startDrag(panelId, sourceHost, pointerId, clientX, clientY) {
2529
+ function startDrag(panelId, sourceHost, pointerId, clientX, clientY, kind) {
2530
2530
  if (activeDrag !== null);
2531
2531
  activeDrag = {
2532
2532
  panelId,
2533
+ kind,
2533
2534
  sourceHost,
2534
2535
  pointerId,
2535
2536
  ghost: createGhostForPanel(sourceHost, panelId),
@@ -2601,8 +2602,10 @@ function onKeyDown(event) {
2601
2602
  endDrag();
2602
2603
  }
2603
2604
  function findDropZone(drag, clientX, clientY) {
2605
+ const sourceIsolated = drag.sourceHost.isolated;
2604
2606
  for (const host of dragHosts) {
2605
- const zone = host.computeDropZone(clientX, clientY, drag.panelId);
2607
+ if (sourceIsolated && host !== drag.sourceHost) continue;
2608
+ const zone = host.computeDropZone(clientX, clientY, drag.panelId, drag.kind);
2606
2609
  if (zone !== null) return zone;
2607
2610
  }
2608
2611
  return null;
@@ -2690,12 +2693,15 @@ function createEmptyLayout() {
2690
2693
  root: createEmptyStack()
2691
2694
  };
2692
2695
  }
2693
- function createStack(panels, active) {
2694
- return {
2696
+ function createStack(panels, active, name) {
2697
+ const stack = {
2695
2698
  kind: `stack`,
2696
2699
  panels: [...panels],
2697
2700
  active: active ?? panels[0] ?? ``
2698
2701
  };
2702
+ const trimmed = name?.trim();
2703
+ if (trimmed) stack.name = trimmed;
2704
+ return stack;
2699
2705
  }
2700
2706
  function createSplit(direction, children, sizes) {
2701
2707
  const mean = children.length > 0 ? 1 / children.length : 1;
@@ -2709,7 +2715,8 @@ function createSplit(direction, children, sizes) {
2709
2715
  /**
2710
2716
  * Runs after every mutation:
2711
2717
  * - stack `active` fixed to a member of `panels` (or `''` when empty)
2712
- * - empty stacks removed from their parent split (and their `sizes` entry)
2718
+ * - empty *unnamed* stacks removed from their parent split (and their
2719
+ * `sizes` entry); named empty stacks are kept so the name stays addressable
2713
2720
  * - splits with ≤1 child replaced by that child (spliced into the parent)
2714
2721
  * - a child split whose direction equals its parent's is flattened into it,
2715
2722
  * spreading its sizes across the slot it vacated
@@ -2718,13 +2725,14 @@ function createSplit(direction, children, sizes) {
2718
2725
  */
2719
2726
  function normalize(root) {
2720
2727
  if (isStack(root)) {
2728
+ sanitizeStackName(root);
2721
2729
  if (!root.panels.includes(root.active)) root.active = root.panels[0] ?? ``;
2722
2730
  return root;
2723
2731
  }
2724
2732
  for (let i = root.children.length - 1; i >= 0; i--) {
2725
2733
  normalize(root.children[i]);
2726
2734
  const child = root.children[i];
2727
- if (isStack(child) && child.panels.length === 0) {
2735
+ if (isStack(child) && child.panels.length === 0 && !child.name) {
2728
2736
  root.children.splice(i, 1);
2729
2737
  root.sizes.splice(i, 1);
2730
2738
  continue;
@@ -2758,17 +2766,36 @@ function replaceNodeContents(target, source) {
2758
2766
  const t = target;
2759
2767
  t.panels = source.panels;
2760
2768
  t.active = source.active;
2769
+ if (source.name !== void 0) t.name = source.name;
2770
+ else delete t.name;
2771
+ if (source.hideLabels !== void 0) t.hideLabels = source.hideLabels;
2772
+ else delete t.hideLabels;
2761
2773
  delete t.collapsed;
2762
2774
  delete t.preCollapseSize;
2775
+ if (source.collapsed !== void 0) t.collapsed = source.collapsed;
2776
+ if (source.preCollapseSize !== void 0) t.preCollapseSize = source.preCollapseSize;
2763
2777
  } else {
2764
2778
  target.kind = `split`;
2765
2779
  const t = target;
2766
2780
  t.direction = source.direction;
2767
2781
  t.children = source.children;
2768
2782
  t.sizes = source.sizes;
2783
+ const stale = t;
2784
+ delete stale.panels;
2785
+ delete stale.active;
2786
+ delete stale.name;
2787
+ delete stale.collapsed;
2788
+ delete stale.preCollapseSize;
2769
2789
  }
2770
2790
  return target;
2771
2791
  }
2792
+ /** Trim `name`; drop it when empty / non-string so lookups stay predictable. */
2793
+ function sanitizeStackName(stack) {
2794
+ if (stack.name === void 0) return;
2795
+ const trimmed = typeof stack.name === `string` ? stack.name.trim() : ``;
2796
+ if (trimmed) stack.name = trimmed;
2797
+ else delete stack.name;
2798
+ }
2772
2799
  function findNode(root, path) {
2773
2800
  let current = root;
2774
2801
  for (const index of path) {
@@ -2802,6 +2829,36 @@ function collectPanelIds(root) {
2802
2829
  return root.children.flatMap(collectPanelIds);
2803
2830
  }
2804
2831
  /**
2832
+ * Path of the first stack (tree order) whose `name` matches, or null.
2833
+ * Names are expected to be unique per layout; duplicates resolve first-wins.
2834
+ */
2835
+ function findStackPathByName(root, name) {
2836
+ const wanted = name.trim();
2837
+ if (!wanted) return null;
2838
+ return findStackPathByNameInner(root, wanted, []);
2839
+ }
2840
+ function findStackPathByNameInner(node, name, path) {
2841
+ if (isStack(node)) return node.name === name ? path : null;
2842
+ for (let i = 0; i < node.children.length; i++) {
2843
+ const found = findStackPathByNameInner(node.children[i], name, [...path, i]);
2844
+ if (found !== null) return found;
2845
+ }
2846
+ return null;
2847
+ }
2848
+ /** Names of every named stack leaf, in tree order. */
2849
+ function collectContainerNames(root) {
2850
+ const out = [];
2851
+ const visit = (node) => {
2852
+ if (isStack(node)) {
2853
+ if (node.name) out.push(node.name);
2854
+ return;
2855
+ }
2856
+ node.children.forEach(visit);
2857
+ };
2858
+ visit(root);
2859
+ return out;
2860
+ }
2861
+ /**
2805
2862
  * Paths of every stack leaf, in tree order. `[]` is the root itself when it
2806
2863
  * is a stack.
2807
2864
  */
@@ -2883,11 +2940,27 @@ function insertBeside(root, targetPath, direction, newNode, side) {
2883
2940
  * the caller's reference stays valid. Returns true on success.
2884
2941
  */
2885
2942
  function wrapRoot(root, direction, newNode, side) {
2886
- replaceNodeContents(root, createSplit(direction, side === `before` ? [newNode, root] : [root, newNode]));
2943
+ const inner = cloneNodeShallow(root);
2944
+ replaceNodeContents(root, createSplit(direction, side === `before` ? [newNode, inner] : [inner, newNode]));
2887
2945
  normalize(root);
2888
2946
  return true;
2889
2947
  }
2890
2948
  /**
2949
+ * Detached copy of `node` (own arrays copied) so it can become a child of a
2950
+ * wrapper that then replaces `node` in place.
2951
+ */
2952
+ function cloneNodeShallow(node) {
2953
+ if (isStack(node)) return {
2954
+ ...node,
2955
+ panels: [...node.panels]
2956
+ };
2957
+ return {
2958
+ ...node,
2959
+ children: [...node.children],
2960
+ sizes: [...node.sizes]
2961
+ };
2962
+ }
2963
+ /**
2891
2964
  * Remove the stack at `stackPath` from the tree. Its panels are moved into
2892
2965
  * `fallbackStack` (if given and still attached) so they are not silently
2893
2966
  * lost; otherwise they are dropped from the model (elements return to the
@@ -2916,6 +2989,251 @@ function extractStack(root, stackPath, fallbackStack) {
2916
2989
  normalize(root);
2917
2990
  return stack;
2918
2991
  }
2992
+ /** Tolerance for edge-touch comparisons in unit-square geometry. */
2993
+ const GEOM_EPS = 1e-9;
2994
+ function pathsEqual(a, b) {
2995
+ return a.length === b.length && a.every((v, i) => v === b[i]);
2996
+ }
2997
+ /**
2998
+ * Resolve a {@link DockLeafRef} to a stack path. Strings match container
2999
+ * `name` first-wins; arrays must index an existing stack leaf. Returns null
3000
+ * when unresolvable.
3001
+ */
3002
+ function resolveLeafPath(root, ref) {
3003
+ if (typeof ref === `string`) return findStackPathByName(root, ref);
3004
+ if (!Array.isArray(ref)) return null;
3005
+ let current = root;
3006
+ for (const index of ref) {
3007
+ if (!Number.isInteger(index) || index < 0) return null;
3008
+ if (!isSplit(current)) return null;
3009
+ const next = current.children[index];
3010
+ if (!next) return null;
3011
+ current = next;
3012
+ }
3013
+ return isStack(current) ? [...ref] : null;
3014
+ }
3015
+ /** Absolute (unit-square) rect of every stack leaf, in tree order. */
3016
+ function computeLeafGeoms(root) {
3017
+ const out = [];
3018
+ const visit = (node, path, rect) => {
3019
+ if (isStack(node)) {
3020
+ out.push({
3021
+ path,
3022
+ name: node.name ?? null,
3023
+ rect
3024
+ });
3025
+ return;
3026
+ }
3027
+ const total = node.sizes.reduce((sum, w) => sum + w, 0);
3028
+ const safe = total > 0 ? total : node.children.length || 1;
3029
+ let offset = 0;
3030
+ node.children.forEach((child, i) => {
3031
+ const frac = (node.sizes[i] ?? safe / node.children.length) / safe;
3032
+ if (node.direction === `row`) {
3033
+ const w = rect.w * frac;
3034
+ visit(child, [...path, i], {
3035
+ x: rect.x + offset,
3036
+ y: rect.y,
3037
+ w,
3038
+ h: rect.h
3039
+ });
3040
+ offset += w;
3041
+ } else {
3042
+ const h = rect.h * frac;
3043
+ visit(child, [...path, i], {
3044
+ x: rect.x,
3045
+ y: rect.y + offset,
3046
+ w: rect.w,
3047
+ h
3048
+ });
3049
+ offset += h;
3050
+ }
3051
+ });
3052
+ };
3053
+ visit(root, [], {
3054
+ x: 0,
3055
+ y: 0,
3056
+ w: 1,
3057
+ h: 1
3058
+ });
3059
+ return out;
3060
+ }
3061
+ function intervalOverlap(a0, a1, b0, b1) {
3062
+ return Math.min(a1, b1) - Math.max(a0, b0);
3063
+ }
3064
+ /**
3065
+ * Geometric neighbours of a leaf — what the user sees above / below / left /
3066
+ * right of it. Each list holds `{ name, path }` (name is null for unnamed
3067
+ * leaves), ordered most-overlapping first. Returns null when `ref` is
3068
+ * unresolvable.
3069
+ */
3070
+ function getLeafNeighbours(root, ref) {
3071
+ const path = resolveLeafPath(root, ref);
3072
+ if (path === null) return null;
3073
+ const geoms = computeLeafGeoms(root);
3074
+ const self = geoms.find((g) => pathsEqual(g.path, path));
3075
+ if (!self) return null;
3076
+ const s = self.rect;
3077
+ const above = [];
3078
+ const below = [];
3079
+ const left = [];
3080
+ const right = [];
3081
+ geoms.forEach((g, order) => {
3082
+ if (g === self) return;
3083
+ const r = g.rect;
3084
+ const info = {
3085
+ name: g.name,
3086
+ path: [...g.path]
3087
+ };
3088
+ if (Math.abs(r.y + r.h - s.y) < GEOM_EPS) {
3089
+ const o = intervalOverlap(r.x, r.x + r.w, s.x, s.x + s.w);
3090
+ if (o > GEOM_EPS) above.push({
3091
+ info,
3092
+ overlap: o,
3093
+ order
3094
+ });
3095
+ }
3096
+ if (Math.abs(s.y + s.h - r.y) < GEOM_EPS) {
3097
+ const o = intervalOverlap(r.x, r.x + r.w, s.x, s.x + s.w);
3098
+ if (o > GEOM_EPS) below.push({
3099
+ info,
3100
+ overlap: o,
3101
+ order
3102
+ });
3103
+ }
3104
+ if (Math.abs(r.x + r.w - s.x) < GEOM_EPS) {
3105
+ const o = intervalOverlap(r.y, r.y + r.h, s.y, s.y + s.h);
3106
+ if (o > GEOM_EPS) left.push({
3107
+ info,
3108
+ overlap: o,
3109
+ order
3110
+ });
3111
+ }
3112
+ if (Math.abs(s.x + s.w - r.x) < GEOM_EPS) {
3113
+ const o = intervalOverlap(r.y, r.y + r.h, s.y, s.y + s.h);
3114
+ if (o > GEOM_EPS) right.push({
3115
+ info,
3116
+ overlap: o,
3117
+ order
3118
+ });
3119
+ }
3120
+ });
3121
+ const rank = (hits) => hits.sort((a, b) => b.overlap - a.overlap || a.order - b.order).map((h) => h.info);
3122
+ return {
3123
+ above: rank(above),
3124
+ below: rank(below),
3125
+ left: rank(left),
3126
+ right: rank(right)
3127
+ };
3128
+ }
3129
+ /**
3130
+ * Exchange two leaves' positions in the tree, each carrying its size slot so
3131
+ * the remaining structure and proportions are untouched. Returns false when
3132
+ * either path is not a stack leaf.
3133
+ */
3134
+ function swapLeaves(root, pathA, pathB) {
3135
+ if (pathsEqual(pathA, pathB)) return false;
3136
+ const nodeA = findNode(root, pathA);
3137
+ const nodeB = findNode(root, pathB);
3138
+ if (nodeA === null || nodeB === null || !isStack(nodeA) || !isStack(nodeB)) return false;
3139
+ const parentA = findParentOf(root, pathA);
3140
+ const parentB = findParentOf(root, pathB);
3141
+ if (parentA === null || parentB === null) return false;
3142
+ const indexA = pathA[pathA.length - 1];
3143
+ const indexB = pathB[pathB.length - 1];
3144
+ if (parentA === parentB) {
3145
+ const p = parentA;
3146
+ [p.children[indexA], p.children[indexB]] = [p.children[indexB], p.children[indexA]];
3147
+ [p.sizes[indexA], p.sizes[indexB]] = [p.sizes[indexB] ?? 1, p.sizes[indexA] ?? 1];
3148
+ return true;
3149
+ }
3150
+ const carriedNode = parentA.children[indexA];
3151
+ const carriedSize = parentA.sizes[indexA] ?? 1;
3152
+ parentA.children[indexA] = parentB.children[indexB];
3153
+ parentA.sizes[indexA] = parentB.sizes[indexB] ?? 1;
3154
+ parentB.children[indexB] = carriedNode;
3155
+ parentB.sizes[indexB] = carriedSize;
3156
+ return true;
3157
+ }
3158
+ /**
3159
+ * Move a leaf one position in `direction`, swapping with the neighbour it
3160
+ * shares the longest edge with. Returns false when `ref` is unresolvable or
3161
+ * nothing adjoins in that direction. Run `normalize()` afterwards.
3162
+ */
3163
+ function moveLeafStep(root, ref, direction) {
3164
+ const path = resolveLeafPath(root, ref);
3165
+ if (path === null) return false;
3166
+ const neighbours = getLeafNeighbours(root, ref);
3167
+ if (neighbours === null) return false;
3168
+ const list = direction === `up` ? neighbours.above : direction === `down` ? neighbours.below : direction === `left` ? neighbours.left : neighbours.right;
3169
+ if (list.length === 0) return false;
3170
+ return swapLeaves(root, path, list[0].path);
3171
+ }
3172
+ /**
3173
+ * Move a leaf to an edge of the overall layout, keeping the rest of the
3174
+ * tree intact as a single subtree and preserving the leaf's absolute size
3175
+ * along the target axis. Returns false when `ref` is unresolvable or the
3176
+ * layout holds a single leaf. Run `normalize()` afterwards (also safe to
3177
+ * call it twice — the host's commit does).
3178
+ */
3179
+ function moveLeafNodeToEdge(root, ref, edge) {
3180
+ const path = resolveLeafPath(root, ref);
3181
+ if (path === null || path.length === 0) return false;
3182
+ const self = computeLeafGeoms(root).find((g) => pathsEqual(g.path, path));
3183
+ if (!self) return false;
3184
+ const horizontal = edge === `left` || edge === `right`;
3185
+ const frac = Math.min(Math.max(horizontal ? self.rect.w : self.rect.h, .05), .95);
3186
+ if (!(frac > 0) || frac >= 1) return false;
3187
+ const parent = findParentOf(root, path);
3188
+ if (parent === null) return false;
3189
+ const [node] = parent.children.splice(path[path.length - 1], 1);
3190
+ parent.sizes.splice(path[path.length - 1], 1);
3191
+ if (!node) return false;
3192
+ normalize(root);
3193
+ const atStart = edge === `left` || edge === `top`;
3194
+ const direction = horizontal ? `row` : `column`;
3195
+ if (isStack(root)) {
3196
+ const rest = root;
3197
+ const remaining = {
3198
+ kind: `stack`,
3199
+ panels: [...rest.panels],
3200
+ active: rest.active
3201
+ };
3202
+ if (rest.name !== void 0) remaining.name = rest.name;
3203
+ if (rest.collapsed !== void 0) remaining.collapsed = rest.collapsed;
3204
+ if (rest.preCollapseSize !== void 0) remaining.preCollapseSize = rest.preCollapseSize;
3205
+ replaceNodeContents(root, {
3206
+ kind: `split`,
3207
+ direction,
3208
+ children: atStart ? [node, remaining] : [remaining, node],
3209
+ sizes: atStart ? [frac, 1 - frac] : [1 - frac, frac]
3210
+ });
3211
+ return true;
3212
+ }
3213
+ if (isSplit(root) && root.direction === direction) {
3214
+ const size = frac * (root.sizes.reduce((total, w) => total + w, 0) || root.children.length || 1) / (1 - frac);
3215
+ const at = atStart ? 0 : root.children.length;
3216
+ root.children.splice(at, 0, node);
3217
+ root.sizes.splice(at, 0, size);
3218
+ return true;
3219
+ }
3220
+ if (isSplit(root)) {
3221
+ const inner = {
3222
+ kind: `split`,
3223
+ direction: root.direction,
3224
+ children: root.children,
3225
+ sizes: root.sizes
3226
+ };
3227
+ replaceNodeContents(root, {
3228
+ kind: `split`,
3229
+ direction,
3230
+ children: atStart ? [node, inner] : [inner, node],
3231
+ sizes: atStart ? [frac, 1 - frac] : [1 - frac, frac]
3232
+ });
3233
+ return true;
3234
+ }
3235
+ return false;
3236
+ }
2919
3237
  function serializeLayout(root) {
2920
3238
  return {
2921
3239
  version: 1,
@@ -2929,6 +3247,8 @@ function serializeNode(node) {
2929
3247
  panels: [...node.panels],
2930
3248
  active: node.active
2931
3249
  };
3250
+ if (node.name !== void 0) out.name = node.name;
3251
+ if (node.hideLabels !== void 0) out.hideLabels = node.hideLabels;
2932
3252
  if (node.collapsed !== void 0) out.collapsed = node.collapsed;
2933
3253
  if (node.preCollapseSize !== void 0) out.preCollapseSize = node.preCollapseSize;
2934
3254
  return out;
@@ -2971,9 +3291,13 @@ let TabListItemElement = class TabListItemElement extends LitElement {
2971
3291
  this.registry = void 0;
2972
3292
  this._compact = false;
2973
3293
  this._hasToolbarContent = false;
3294
+ this._labelObserver = null;
2974
3295
  this._onToolbarSlotChange = () => {
2975
3296
  this._updateToolbarContent();
2976
3297
  };
3298
+ this._onLabelSlotChange = () => {
3299
+ this._syncLabelSizer();
3300
+ };
2977
3301
  }
2978
3302
  connectedCallback() {
2979
3303
  super.connectedCallback();
@@ -2982,6 +3306,13 @@ let TabListItemElement = class TabListItemElement extends LitElement {
2982
3306
  });
2983
3307
  this._resizeObserver.observe(this);
2984
3308
  (this.shadowRoot?.querySelector(`slot[name="toolbar"]`))?.addEventListener(`slotchange`, this._onToolbarSlotChange);
3309
+ (this.shadowRoot?.querySelector(`slot:not([name])`))?.addEventListener(`slotchange`, this._onLabelSlotChange);
3310
+ this._labelObserver = new MutationObserver(() => this._syncLabelSizer());
3311
+ this._labelObserver.observe(this, {
3312
+ childList: true,
3313
+ characterData: true,
3314
+ subtree: true
3315
+ });
2985
3316
  }
2986
3317
  disconnectedCallback() {
2987
3318
  super.disconnectedCallback();
@@ -2989,7 +3320,10 @@ let TabListItemElement = class TabListItemElement extends LitElement {
2989
3320
  this._resizeObserver = void 0;
2990
3321
  this._commandCleanup?.();
2991
3322
  this._commandCleanup = void 0;
3323
+ this._labelObserver?.disconnect();
3324
+ this._labelObserver = null;
2992
3325
  (this.shadowRoot?.querySelector(`slot[name="toolbar"]`))?.removeEventListener(`slotchange`, this._onToolbarSlotChange);
3326
+ (this.shadowRoot?.querySelector(`slot:not([name])`))?.removeEventListener(`slotchange`, this._onLabelSlotChange);
2993
3327
  }
2994
3328
  updated(changed) {
2995
3329
  super.updated(changed);
@@ -3012,12 +3346,25 @@ let TabListItemElement = class TabListItemElement extends LitElement {
3012
3346
  if (id === this.commandActive) this.click();
3013
3347
  });
3014
3348
  }
3349
+ /**
3350
+ * Mirror the default-slot text into `.label[data-label]` for the `::after`
3351
+ * width sizer. Slotted toolbar content is excluded.
3352
+ */
3353
+ _syncLabelSizer() {
3354
+ const slot = this.shadowRoot?.querySelector(`slot:not([name])`);
3355
+ const label = this.shadowRoot?.querySelector(`.label`);
3356
+ if (slot === null || slot === void 0 || label === null || label === void 0) return;
3357
+ const text = slot.assignedNodes().filter((node) => !(node instanceof HTMLElement && node.hasAttribute(`slot`))).map((node) => node.textContent ?? ``).join(``);
3358
+ if (text) label.setAttribute(`data-label`, text);
3359
+ else label.removeAttribute(`data-label`);
3360
+ }
3015
3361
  _updateToolbarContent() {
3016
3362
  const slot = this.shadowRoot?.querySelector(`slot[name="toolbar"]`);
3017
3363
  this._hasToolbarContent = (slot?.assignedElements().length ?? 0) > 0;
3018
3364
  }
3019
3365
  firstUpdated() {
3020
3366
  this._updateToolbarContent();
3367
+ this._syncLabelSizer();
3021
3368
  }
3022
3369
  render() {
3023
3370
  const showLabel = !this.hideLabels || !this.iconName;
@@ -3059,15 +3406,21 @@ let TabListItemElement = class TabListItemElement extends LitElement {
3059
3406
  css`
3060
3407
  :host {
3061
3408
  --selected-tab-color: var(--ixfx-selected-tab-color, var(--surface-2-text));
3409
+ --selected-tab-icon-color: var(--ixfx-selected-tab-icon-color, var(--accent));
3062
3410
  --selected-tab-border-width: var(--ixfx-selected-tab-border-width, 1px);
3063
3411
  --selected-tab-weight: var(--ixfx-selected-tab-weight, bold);
3412
+ --selected-tab-text-decoration: var(--ixfx-selected-tab-text-decoration, none);
3413
+ --selected-tab-text-decoration-color: var(--ixfx-selected-tab-text-decoration-color, var(--accent));
3414
+ --selected-tab-text-decoration-thickness: var(--ixfx-selected-tab-text-decoration-thickness, 0px);
3415
+ --selected-tab-text-decoration-offset: var(--ixfx-selected-tab-text-decoration-offset, 2px);
3064
3416
  --selected-background: var(--ixfx-tab-selected-background, var(--surface-5));
3065
3417
  --tab-padding: var(--ixfx-tab-padding, var(--space-s));
3066
3418
  display: inline-flex;
3067
3419
  min-width: 0;
3068
3420
  user-select: none;
3069
3421
  box-sizing: border-box;
3070
- font-size: var(--text-xs);
3422
+ font-size: var(--text-m);
3423
+ color: inherit;
3071
3424
  /* No explicit height: items stretch (align-self: stretch via the
3072
3425
  list's flex container) so every tab in a strip shares one height
3073
3426
  even when toolbars give some items taller content. */
@@ -3082,7 +3435,18 @@ let TabListItemElement = class TabListItemElement extends LitElement {
3082
3435
  font-weight: 500;
3083
3436
  border: var(--selected-tab-border-width) solid transparent;
3084
3437
  gap: var(--space-s);
3438
+ transition: background var(--transition, 0.15s);
3439
+ }
3085
3440
 
3441
+ /* Hover ("tickle") background. When --ixfx-tab-hover-background is
3442
+ unset each state keeps its resting background, so existing
3443
+ consumers see no change. */
3444
+ :host(:hover:not([selected])) > div {
3445
+ background: var(--ixfx-tab-hover-background, transparent);
3446
+ }
3447
+
3448
+ :host([selected]:hover) > div {
3449
+ background: var(--ixfx-tab-hover-background, var(--selected-background));
3086
3450
  }
3087
3451
 
3088
3452
  :host([selected]) > div {
@@ -3096,6 +3460,13 @@ let TabListItemElement = class TabListItemElement extends LitElement {
3096
3460
  border-radius:var(--radius-m);
3097
3461
  }
3098
3462
 
3463
+ :host([selected]) .label {
3464
+ text-decoration: var(--selected-tab-text-decoration);
3465
+ text-decoration-color: var(--selected-tab-text-decoration-color);
3466
+ text-decoration-thickness: var(--selected-tab-text-decoration-thickness);
3467
+ text-decoration-offset: var(--selected-tab-text-decoration-offset);
3468
+ }
3469
+
3099
3470
  .label {
3100
3471
  overflow: var(--allow-overflow, hidden);
3101
3472
  text-overflow: ellipsis;
@@ -3104,10 +3475,29 @@ let TabListItemElement = class TabListItemElement extends LitElement {
3104
3475
  flex: 1 1 auto;
3105
3476
  }
3106
3477
 
3478
+ /* Width sizer: an invisible copy of the label, always set at the
3479
+ selected weight, so activating a tab (bold) never shifts the strip.
3480
+ Text is mirrored from the default slot via the data-label attribute
3481
+ (see _syncLabelSizer). */
3482
+ .label::after {
3483
+ content: attr(data-label);
3484
+ display: block;
3485
+ height: 0;
3486
+ overflow: hidden;
3487
+ visibility: hidden;
3488
+ font-weight: var(--selected-tab-weight);
3489
+ }
3490
+
3107
3491
  ixfx-icon {
3108
3492
  flex-shrink: 0;
3109
3493
  }
3110
3494
 
3495
+ /* Leading icon (direct child — not close/toolbar icons) picks up the
3496
+ accent when selected, so icon-only tabs read as active. */
3497
+ :host([selected]) > div > ixfx-icon {
3498
+ color: var(--selected-tab-icon-color);
3499
+ }
3500
+
3111
3501
  .actions {
3112
3502
  display: flex;
3113
3503
  align-items: center;
@@ -3203,6 +3593,7 @@ let DockPanelElement = class DockPanelElement extends LitElement {
3203
3593
  this.iconName = ``;
3204
3594
  this.closable = false;
3205
3595
  this.collapsible = false;
3596
+ this.kind = `any`;
3206
3597
  this.accepts = ``;
3207
3598
  this.presentation = `solo`;
3208
3599
  this._collapsed = false;
@@ -3256,6 +3647,10 @@ let DockPanelElement = class DockPanelElement extends LitElement {
3256
3647
  }
3257
3648
  connectedCallback() {
3258
3649
  super.connectedCallback();
3650
+ if (this.hasAttribute(`title`)) {
3651
+ if (!this.hasAttribute(`label`)) this.title = this.getAttribute(`title`) ?? ``;
3652
+ this.removeAttribute(`title`);
3653
+ }
3259
3654
  this.addEventListener(`pointerdown`, this._onPointerDown);
3260
3655
  }
3261
3656
  disconnectedCallback() {
@@ -3347,13 +3742,20 @@ __decorate([property({
3347
3742
  attribute: `panel-id`,
3348
3743
  reflect: true
3349
3744
  })], DockPanelElement.prototype, "panelId", void 0);
3350
- __decorate([property({ type: String })], DockPanelElement.prototype, "title", void 0);
3745
+ __decorate([property({
3746
+ type: String,
3747
+ attribute: `label`
3748
+ })], DockPanelElement.prototype, "title", void 0);
3351
3749
  __decorate([property({
3352
3750
  type: String,
3353
3751
  attribute: `icon-name`
3354
3752
  })], DockPanelElement.prototype, "iconName", void 0);
3355
3753
  __decorate([property({ type: Boolean })], DockPanelElement.prototype, "closable", void 0);
3356
3754
  __decorate([property({ type: Boolean })], DockPanelElement.prototype, "collapsible", void 0);
3755
+ __decorate([property({
3756
+ type: String,
3757
+ reflect: true
3758
+ })], DockPanelElement.prototype, "kind", void 0);
3357
3759
  __decorate([property({
3358
3760
  type: String,
3359
3761
  attribute: `accepts`
@@ -3379,19 +3781,23 @@ let DockHostElement = class DockHostElement extends LitElement {
3379
3781
  super(..._args);
3380
3782
  this.layout = null;
3381
3783
  this.accepts = `tabs horizontal vertical`;
3784
+ this.isolated = false;
3382
3785
  this._model = createStack([]);
3383
3786
  this._pool = [];
3384
3787
  this._dropZone = null;
3385
3788
  this._dragActive = false;
3386
3789
  this._junctions = [];
3387
3790
  this._toolbarsOut = /* @__PURE__ */ new Map();
3791
+ this._leafContentRules = /* @__PURE__ */ new Map();
3388
3792
  this._tabDragPending = null;
3389
3793
  this._junctionsKey = ``;
3390
3794
  this._junctionsRaf = null;
3391
3795
  this._hostResizeObserver = null;
3796
+ this._toolbarSlots = /* @__PURE__ */ new Set();
3797
+ this._toolbarObserver = null;
3392
3798
  this._onDragStart = (event) => {
3393
3799
  const { panelId, pointerId, clientX, clientY } = event.detail;
3394
- startDrag(panelId, this, pointerId, clientX, clientY);
3800
+ startDrag(panelId, this, pointerId, clientX, clientY, this._findPanelEl(panelId)?.kind);
3395
3801
  };
3396
3802
  this._onPoolSlotChange = () => {
3397
3803
  this._syncPool();
@@ -3438,11 +3844,12 @@ let DockHostElement = class DockHostElement extends LitElement {
3438
3844
  const pathStr = this.shadowRoot?.getElementById(detail.id)?.getAttribute(`data-dock-path`);
3439
3845
  if (pathStr === null || pathStr === void 0) return;
3440
3846
  const path = parsePath(pathStr);
3441
- const paths = collectStackPaths(this._model).filter((p) => p.join(`-`) !== path.join(`-`));
3442
- const fallbackNode = paths.length > 0 ? findNode(this._model, paths[0]) : null;
3443
- const fallback = fallbackNode !== null && isStack(fallbackNode) ? fallbackNode : void 0;
3847
+ const closing = findNode(this._model, path);
3848
+ if (closing === null || !isStack(closing)) return;
3849
+ const moving = [...closing.panels];
3444
3850
  this._returnAllToolbars();
3445
- extractStack(this._model, path, fallback);
3851
+ extractStack(this._model, path);
3852
+ for (const panelId of moving) addPanelToStack(this._firstStackAllowing(this._panelKind(panelId)), panelId);
3446
3853
  this._commit();
3447
3854
  };
3448
3855
  this._onTabChange = (event, path) => {
@@ -3507,11 +3914,21 @@ let DockHostElement = class DockHostElement extends LitElement {
3507
3914
  this.addEventListener(`dock-panel-drag-start`, this._onDragStart);
3508
3915
  this._hostResizeObserver = new ResizeObserver(() => this._scheduleJunctions());
3509
3916
  this._hostResizeObserver.observe(this);
3917
+ this._scanToolbarSlots();
3918
+ this._toolbarObserver = new MutationObserver(() => this._scanToolbarSlots());
3919
+ this._toolbarObserver.observe(this, {
3920
+ childList: true,
3921
+ subtree: true,
3922
+ attributes: true,
3923
+ attributeFilter: [`slot`]
3924
+ });
3510
3925
  }
3511
3926
  disconnectedCallback() {
3512
3927
  super.disconnectedCallback();
3513
3928
  unregisterDockHost(this);
3514
3929
  this.removeEventListener(`dock-panel-drag-start`, this._onDragStart);
3930
+ this._toolbarObserver?.disconnect();
3931
+ this._toolbarObserver = null;
3515
3932
  this._hostResizeObserver?.disconnect();
3516
3933
  this._hostResizeObserver = null;
3517
3934
  if (this._junctionsRaf !== null) {
@@ -3564,6 +3981,19 @@ let DockHostElement = class DockHostElement extends LitElement {
3564
3981
  _scanPanels() {
3565
3982
  return Array.from(this.children).filter((el) => el instanceof HTMLElement && el.tagName === `IXFX-DOCK-PANEL` && Boolean(el.panelId));
3566
3983
  }
3984
+ /** Refresh the set of provided `toolbar-<name>` slots; re-render on change. */
3985
+ _scanToolbarSlots() {
3986
+ const found = /* @__PURE__ */ new Set();
3987
+ for (const child of this.children) {
3988
+ if (!(child instanceof HTMLElement)) continue;
3989
+ const slot = child.getAttribute(`slot`) ?? ``;
3990
+ if (slot.startsWith(`toolbar-`) && slot.length > `toolbar-`.length) found.add(slot);
3991
+ }
3992
+ if (found.size !== this._toolbarSlots.size || [...found].some((s) => !this._toolbarSlots.has(s))) {
3993
+ this._toolbarSlots = found;
3994
+ this.requestUpdate();
3995
+ }
3996
+ }
3567
3997
  _findPanelEl(panelId) {
3568
3998
  return this._pool.find((p) => p.panelId === panelId) ?? this._scanPanels().find((p) => p.panelId === panelId);
3569
3999
  }
@@ -3577,7 +4007,33 @@ let DockHostElement = class DockHostElement extends LitElement {
3577
4007
  return fresh;
3578
4008
  }
3579
4009
  _appendPanelToFirstStack(panelId) {
3580
- addPanelToStack(this._model.kind === `stack` ? this._model : this._firstStack(), panelId);
4010
+ const kind = this._panelKind(panelId);
4011
+ if (this._model.kind === `stack` && this._stackAllowsKind(this._model, kind)) {
4012
+ addPanelToStack(this._model, panelId);
4013
+ return;
4014
+ }
4015
+ addPanelToStack(this._firstStackAllowing(kind), panelId);
4016
+ }
4017
+ /** Normalized content kind of a panel (`'any'` when unknown/unset). */
4018
+ _panelKind(panelId) {
4019
+ return normalizeKind(this._findPanelEl(panelId)?.kind);
4020
+ }
4021
+ /** Whether `kind` may live in `stack` under the current leaf rules. */
4022
+ _stackAllowsKind(stack, kind) {
4023
+ if (stack.name === void 0) return true;
4024
+ const rules = this._leafContentRules.get(stack.name);
4025
+ if (rules === void 0) return true;
4026
+ if (rules.deny.has(kind)) return false;
4027
+ return rules.allow === null || rules.allow.has(kind);
4028
+ }
4029
+ /** First stack (tree order) whose rules admit `kind`, else the first stack. */
4030
+ _firstStackAllowing(kind) {
4031
+ for (const path of collectStackPaths(this._model)) {
4032
+ const node = findNode(this._model, path);
4033
+ if (node !== null && isStack(node) && this._stackAllowsKind(node, kind)) return node;
4034
+ }
4035
+ console.warn(`[ixfx-dock-host] no leaf admits kind "${kind}", using first stack`);
4036
+ return this._firstStack();
3581
4037
  }
3582
4038
  _syncPool() {
3583
4039
  const current = this._scanPanels();
@@ -3619,7 +4075,34 @@ let DockHostElement = class DockHostElement extends LitElement {
3619
4075
  this.requestUpdate();
3620
4076
  this._emitChange();
3621
4077
  }
4078
+ /**
4079
+ * Dock a panel. Returns false (without touching the DOM) when the
4080
+ * explicitly targeted container denies the panel's `kind`.
4081
+ */
3622
4082
  addPanel(panel, target) {
4083
+ const kind = panel instanceof HTMLElement ? normalizeKind(panel.kind) : normalizeKind(panel.kind);
4084
+ const op = target?.op ?? `tabs`;
4085
+ let stackPath = null;
4086
+ let splitTarget = null;
4087
+ if (target?.container !== void 0) {
4088
+ const namedPath = findStackPathByName(this._model, target.container);
4089
+ if (namedPath === null) console.warn(`[ixfx-dock-host] addPanel: unknown container "${target.container}", appending to first admitting stack`);
4090
+ else if (op === `tabs` && !this._stackAllowsKindAt(namedPath, kind)) {
4091
+ console.warn(`[ixfx-dock-host] addPanel: container "${target.container}" does not admit kind "${kind}"`);
4092
+ return false;
4093
+ } else stackPath = namedPath;
4094
+ } else if (target?.stack) {
4095
+ const found = findStackOf(this._model, target.stack);
4096
+ if (found === null) console.warn(`[ixfx-dock-host] addPanel: unknown stack panel "${target.stack}", appending to first admitting stack`);
4097
+ else if (op === `tabs` && !this._stackAllowsKindAt(found, kind)) {
4098
+ console.warn(`[ixfx-dock-host] addPanel: target stack does not admit kind "${kind}"`);
4099
+ return false;
4100
+ } else stackPath = found;
4101
+ }
4102
+ if (stackPath !== null && op !== `tabs`) {
4103
+ splitTarget = stackPath;
4104
+ stackPath = null;
4105
+ }
3623
4106
  let el;
3624
4107
  if (panel instanceof HTMLElement) el = panel;
3625
4108
  else {
@@ -3629,19 +4112,16 @@ let DockHostElement = class DockHostElement extends LitElement {
3629
4112
  if (panel.iconName !== void 0) el.iconName = panel.iconName;
3630
4113
  if (panel.closable !== void 0) el.closable = panel.closable;
3631
4114
  if (panel.collapsible !== void 0) el.collapsible = panel.collapsible;
4115
+ if (panel.kind !== void 0) el.kind = panel.kind;
3632
4116
  }
3633
4117
  if (el.parentElement !== this) this.appendChild(el);
3634
4118
  if (!this._pool.some((p) => p.panelId === el.panelId)) this._pool = [...this._pool, el];
3635
4119
  this._returnAllToolbars();
3636
- const op = target?.op ?? `tabs`;
3637
- if (target?.stack) {
3638
- const stackPath = findStackOf(this._model, target.stack);
3639
- if (stackPath !== null) {
3640
- if (op === `tabs`) {
3641
- const stack = findNode(this._model, stackPath);
3642
- if (stack !== null && isStack(stack)) addPanelToStack(stack, el.panelId, target.index);
3643
- } else insertBeside(this._model, stackPath, opToDirection(op), createStack([el.panelId]), `after`);
3644
- }
4120
+ if (collectPanelIds(this._model).includes(el.panelId)) this._removeFromModel(el.panelId);
4121
+ if (splitTarget !== null) insertBeside(this._model, splitTarget, opToDirection(op), createStack([el.panelId]), `after`);
4122
+ else if (stackPath !== null) {
4123
+ const stack = findNode(this._model, stackPath);
4124
+ if (stack !== null && isStack(stack)) addPanelToStack(stack, el.panelId, target?.index);
3645
4125
  } else this._appendPanelToFirstStack(el.panelId);
3646
4126
  normalize(this._model);
3647
4127
  this.requestUpdate();
@@ -3651,6 +4131,12 @@ let DockHostElement = class DockHostElement extends LitElement {
3651
4131
  composed: true
3652
4132
  }));
3653
4133
  this._emitChange();
4134
+ return true;
4135
+ }
4136
+ /** Whether the stack at `stackPath` admits `kind` under the leaf rules. */
4137
+ _stackAllowsKindAt(stackPath, kind) {
4138
+ const stack = findNode(this._model, stackPath);
4139
+ return stack !== null && isStack(stack) && this._stackAllowsKind(stack, kind);
3654
4140
  }
3655
4141
  removePanel(panelId) {
3656
4142
  if (!collectPanelIds(this._model).includes(panelId)) return false;
@@ -3686,6 +4172,123 @@ let DockHostElement = class DockHostElement extends LitElement {
3686
4172
  getPanelIds() {
3687
4173
  return collectPanelIds(this._model);
3688
4174
  }
4175
+ /** Names of every named stack leaf, in tree order. */
4176
+ getContainerNames() {
4177
+ return collectContainerNames(this._model);
4178
+ }
4179
+ /**
4180
+ * Snapshot of a named container (`{ name, panels, active }`), or null when
4181
+ * no stack carries that name. First match wins when duplicated.
4182
+ */
4183
+ getContainer(name) {
4184
+ const path = findStackPathByName(this._model, name);
4185
+ if (path === null) return null;
4186
+ const node = findNode(this._model, path);
4187
+ if (node === null || !isStack(node) || node.name === void 0) return null;
4188
+ return {
4189
+ name: node.name,
4190
+ panels: [...node.panels],
4191
+ active: node.active
4192
+ };
4193
+ }
4194
+ /** Panel-ids in a named container, in tab order (`[]` when unknown/empty). */
4195
+ getContainerPanels(name) {
4196
+ return this.getContainer(name)?.panels ?? [];
4197
+ }
4198
+ /** Panel elements in a named container, in tab order — for iterating children. */
4199
+ getContainerElements(name) {
4200
+ return this.getContainerPanels(name).map((id) => this._findPanelEl(id)).filter((el) => el !== void 0);
4201
+ }
4202
+ /**
4203
+ * Restrict a named container to only the given panel kinds — e.g.
4204
+ * `setLeafExclusiveContents('editor', ['document'])`. `tabs` drops,
4205
+ * `addPanel`, and re-homing into the leaf then refuse other kinds, while
4206
+ * edge splits beside the leaf stay allowed (they create a new leaf).
4207
+ * Pass `[]` to seal the leaf (nothing admitted). Warns when no stack
4208
+ * currently carries `name`, but the rule is still stored and applies once
4209
+ * one appears. Rules are host policy: kept across `loadLayout()`, never
4210
+ * serialized.
4211
+ */
4212
+ setLeafExclusiveContents(name, kinds) {
4213
+ const key = name.trim();
4214
+ if (!key) {
4215
+ console.warn(`[ixfx-dock-host] setLeafExclusiveContents: empty container name`);
4216
+ return;
4217
+ }
4218
+ const allow = new Set(kinds.map((k) => k.trim()).filter((k) => k.length > 0));
4219
+ const entry = this._leafContentRules.get(key) ?? {
4220
+ allow: null,
4221
+ deny: /* @__PURE__ */ new Set()
4222
+ };
4223
+ entry.allow = allow;
4224
+ this._leafContentRules.set(key, entry);
4225
+ if (findStackPathByName(this._model, key) === null) console.warn(`[ixfx-dock-host] setLeafExclusiveContents: no container named "${key}" yet; rule stored`);
4226
+ }
4227
+ /**
4228
+ * Forbid the given panel kinds in a named container — e.g.
4229
+ * `setLeafDenyContents('footer', ['files', 'document'])`. Everything else
4230
+ * stays admitted. Pass `[]` to lift the denial. See
4231
+ * {@link setLeafExclusiveContents} for scope/ordering notes.
4232
+ */
4233
+ setLeafDenyContents(name, kinds) {
4234
+ const key = name.trim();
4235
+ if (!key) {
4236
+ console.warn(`[ixfx-dock-host] setLeafDenyContents: empty container name`);
4237
+ return;
4238
+ }
4239
+ const deny = new Set(kinds.map((k) => k.trim()).filter((k) => k.length > 0));
4240
+ const entry = this._leafContentRules.get(key) ?? {
4241
+ allow: null,
4242
+ deny: /* @__PURE__ */ new Set()
4243
+ };
4244
+ entry.deny = deny;
4245
+ if (entry.allow === null && entry.deny.size === 0) this._leafContentRules.delete(key);
4246
+ else this._leafContentRules.set(key, entry);
4247
+ if (findStackPathByName(this._model, key) === null) console.warn(`[ixfx-dock-host] setLeafDenyContents: no container named "${key}" yet; rule stored`);
4248
+ }
4249
+ /** Remove every content rule for a named container. */
4250
+ clearLeafContentRestrictions(name) {
4251
+ this._leafContentRules.delete(name.trim());
4252
+ }
4253
+ /** Copy of a container's rules, or null when unrestricted. */
4254
+ getLeafContentRestrictions(name) {
4255
+ const rules = this._leafContentRules.get(name.trim());
4256
+ if (rules === void 0) return null;
4257
+ return {
4258
+ allow: rules.allow === null ? null : [...rules.allow],
4259
+ deny: [...rules.deny]
4260
+ };
4261
+ }
4262
+ /**
4263
+ * Geometric neighbours of a leaf (`name` or numeric `path`) — what sits
4264
+ * above / below / left / right of it on screen. Each entry is
4265
+ * `{ name, path }` (`name` is null for unnamed leaves), ordered
4266
+ * most-overlapping first. Returns null when the leaf is unknown.
4267
+ */
4268
+ getNeighbours(leaf) {
4269
+ return getLeafNeighbours(this._model, leaf);
4270
+ }
4271
+ /**
4272
+ * Move a leaf one position in `direction`, swapping with the neighbour it
4273
+ * shares the longest edge with (both keep the remaining structure intact).
4274
+ * Returns false when the leaf is unknown or nothing adjoins that way.
4275
+ */
4276
+ moveLeaf(leaf, direction) {
4277
+ if (!moveLeafStep(this._model, leaf, direction)) return false;
4278
+ this._commit();
4279
+ return true;
4280
+ }
4281
+ /**
4282
+ * Move a leaf to an edge of the overall layout (e.g. toggle a sidebar from
4283
+ * `left` to `right`). The rest of the layout stays intact as a single
4284
+ * subtree and the leaf keeps its absolute size along the target axis.
4285
+ * Returns false when the leaf is unknown or the layout holds one leaf.
4286
+ */
4287
+ moveLeafToEdge(leaf, edge) {
4288
+ if (!moveLeafNodeToEdge(this._model, leaf, edge)) return false;
4289
+ this._commit();
4290
+ return true;
4291
+ }
3689
4292
  /**
3690
4293
  * Capture a structured-clone-safe {@link DockPanelSnapshot} of a panel
3691
4294
  * (identity, chrome attributes, and a best-effort `innerHTML`). Returns
@@ -3700,6 +4303,7 @@ let DockHostElement = class DockHostElement extends LitElement {
3700
4303
  iconName: el.iconName,
3701
4304
  closable: el.closable,
3702
4305
  collapsible: el.collapsible,
4306
+ kind: normalizeKind(el.kind),
3703
4307
  accepts: el.accepts,
3704
4308
  contentHTML: el.innerHTML
3705
4309
  };
@@ -3723,10 +4327,11 @@ let DockHostElement = class DockHostElement extends LitElement {
3723
4327
  * `accepts` / `canAccept`. Pure query: no model mutation, no overlay.
3724
4328
  *
3725
4329
  * `panelId` is passed to `canAccept`; use any stable id for an external
3726
- * panel (defaults to `"__external__"`).
4330
+ * panel (defaults to `"__external__"`). `kind` covers leaf content rules
4331
+ * for panels unknown to this host (defaults to `'any'`).
3727
4332
  */
3728
- queryDropTarget(clientX, clientY, panelId = `__external__`) {
3729
- const zone = this.computeDropZone(clientX, clientY, panelId);
4333
+ queryDropTarget(clientX, clientY, panelId = `__external__`, kind) {
4334
+ const zone = this.computeDropZone(clientX, clientY, panelId, kind);
3730
4335
  return zone === null ? null : zoneToTarget(zone);
3731
4336
  }
3732
4337
  /**
@@ -3735,9 +4340,9 @@ let DockHostElement = class DockHostElement extends LitElement {
3735
4340
  * one. Call on every pointer update from the outer coordinator, then
3736
4341
  * {@link endExternalDrop} when the drag leaves or ends.
3737
4342
  */
3738
- previewExternalDrop(clientX, clientY, panelId = `__external__`) {
4343
+ previewExternalDrop(clientX, clientY, panelId = `__external__`, kind) {
3739
4344
  this.setDragActive(true);
3740
- const zone = this.computeDropZone(clientX, clientY, panelId);
4345
+ const zone = this.computeDropZone(clientX, clientY, panelId, kind);
3741
4346
  this.setDropZone(zone);
3742
4347
  return zone === null ? null : zoneToTarget(zone);
3743
4348
  }
@@ -3750,8 +4355,9 @@ let DockHostElement = class DockHostElement extends LitElement {
3750
4355
  * Recreate a panel from a {@link DockPanelSnapshot} (or a bare
3751
4356
  * {@link DockPanelInit}) and dock it. With `at` viewport coordinates the
3752
4357
  * panel lands where a drop there would put it (respecting `accepts` /
3753
- * `canAccept`); without them, or when the point misses the host, it goes
3754
- * into the first stack. Returns the created element, or null when a panel
4358
+ * `canAccept` and leaf content rules); without them, or when the point
4359
+ * misses the host, it goes into the first stack admitting its kind.
4360
+ * Returns the created element, or null when a panel
3755
4361
  * with that id is already docked here.
3756
4362
  */
3757
4363
  acceptExternalPanel(snapshot, at) {
@@ -3762,6 +4368,7 @@ let DockHostElement = class DockHostElement extends LitElement {
3762
4368
  if (snapshot.iconName !== void 0) el.iconName = snapshot.iconName;
3763
4369
  if (snapshot.closable !== void 0) el.closable = snapshot.closable;
3764
4370
  if (snapshot.collapsible !== void 0) el.collapsible = snapshot.collapsible;
4371
+ if (`kind` in snapshot && snapshot.kind !== void 0) el.kind = snapshot.kind;
3765
4372
  if (`accepts` in snapshot && snapshot.accepts) el.setAttribute(`accepts`, snapshot.accepts);
3766
4373
  if (`contentHTML` in snapshot && snapshot.contentHTML) el.innerHTML = snapshot.contentHTML;
3767
4374
  this._returnAllToolbars();
@@ -3787,9 +4394,10 @@ let DockHostElement = class DockHostElement extends LitElement {
3787
4394
  * a zone with rects relative to the host, or null when the pointer is
3788
4395
  * outside the host or the candidate zone is disallowed.
3789
4396
  */
3790
- computeDropZone(clientX, clientY, panelId) {
4397
+ computeDropZone(clientX, clientY, panelId, kind) {
3791
4398
  const hostRect = this.getBoundingClientRect();
3792
4399
  if (clientX < hostRect.left || clientX > hostRect.right || clientY < hostRect.top || clientY > hostRect.bottom) return null;
4400
+ const resolvedKind = kind?.trim() ? kind.trim() : this._panelKind(panelId);
3793
4401
  const rel = (r) => ({
3794
4402
  x: r.left - hostRect.left,
3795
4403
  y: r.top - hostRect.top,
@@ -3817,7 +4425,7 @@ let DockHostElement = class DockHostElement extends LitElement {
3817
4425
  rect: rel(leafRect),
3818
4426
  caretRect: this._stripCaretRect(strip, gapIndex, hostRect)
3819
4427
  };
3820
- return this._zoneAllowed(panelId, zone) ? zone : null;
4428
+ return this._zoneAllowed(panelId, zone, resolvedKind) ? zone : null;
3821
4429
  }
3822
4430
  }
3823
4431
  const relX = (clientX - leafRect.left) / Math.max(leafRect.width, 1);
@@ -3831,7 +4439,7 @@ let DockHostElement = class DockHostElement extends LitElement {
3831
4439
  gapIndex: stack.panels.length,
3832
4440
  rect: rel(leafRect)
3833
4441
  };
3834
- return this._zoneAllowed(panelId, zone) ? zone : null;
4442
+ return this._zoneAllowed(panelId, zone, resolvedKind) ? zone : null;
3835
4443
  }
3836
4444
  const r = rel(leafRect);
3837
4445
  let operation;
@@ -3877,7 +4485,7 @@ let DockHostElement = class DockHostElement extends LitElement {
3877
4485
  side,
3878
4486
  rect: zoneRect
3879
4487
  };
3880
- return this._zoneAllowed(panelId, zone) ? zone : null;
4488
+ return this._zoneAllowed(panelId, zone, resolvedKind) ? zone : null;
3881
4489
  }
3882
4490
  const fromLeft = clientX - hostRect.left;
3883
4491
  const fromRight = hostRect.right - clientX;
@@ -3933,15 +4541,18 @@ let DockHostElement = class DockHostElement extends LitElement {
3933
4541
  side,
3934
4542
  rect: zoneRect
3935
4543
  };
3936
- return this._zoneAllowed(panelId, zone) ? zone : null;
4544
+ return this._zoneAllowed(panelId, zone, resolvedKind) ? zone : null;
3937
4545
  }
3938
- _zoneAllowed(panelId, zone) {
4546
+ _zoneAllowed(panelId, zone, kind) {
3939
4547
  if (!tokenAllows(this.accepts, operationToToken(zone.operation))) return false;
3940
4548
  const stack = findNode(this._model, zone.targetPath);
3941
4549
  if (stack !== null && isStack(stack) && stack.panels.length === 1) {
3942
4550
  const target = this._findPanelEl(stack.panels[0]);
3943
4551
  if (target && !panelAllows(target.accepts, operationToToken(zone.operation))) return false;
3944
4552
  }
4553
+ if (zone.operation === `tabs` && stack !== null && isStack(stack)) {
4554
+ if (!this._stackAllowsKind(stack, kind?.trim() ? kind.trim() : this._panelKind(panelId))) return false;
4555
+ }
3945
4556
  if (this.canAccept && !this.canAccept(panelId, zone.operation)) return false;
3946
4557
  return true;
3947
4558
  }
@@ -3956,22 +4567,32 @@ let DockHostElement = class DockHostElement extends LitElement {
3956
4567
  this._appendPanelToFirstStack(panelId);
3957
4568
  normalize(this._model);
3958
4569
  }
3959
- this._applyZone(panelId, zone);
4570
+ this._applyZone(panelId, zone, sourceHost === void 0 || sourceHost === this);
3960
4571
  this._commit();
3961
4572
  }
3962
4573
  /**
3963
4574
  * Move `panelId` (already present somewhere in the model) to the position
3964
4575
  * described by `zone`. Shared by in-page drops and external panel adoption.
3965
4576
  */
3966
- _applyZone(panelId, zone) {
4577
+ _applyZone(panelId, zone, transferName = false) {
3967
4578
  const sourcePath = findStackOf(this._model, panelId);
4579
+ let vacated = null;
3968
4580
  if (sourcePath !== null) {
3969
4581
  const source = findNode(this._model, sourcePath);
3970
- if (source !== null && isStack(source)) removePanelFromStack(source, panelId);
4582
+ if (source !== null && isStack(source)) {
4583
+ removePanelFromStack(source, panelId);
4584
+ if (source.panels.length === 0) vacated = source;
4585
+ }
3971
4586
  }
3972
4587
  if (zone.operation === `tabs`) {
3973
4588
  const target = findNode(this._model, zone.targetPath);
3974
- if (target !== null && isStack(target)) addPanelToStack(target, panelId, zone.gapIndex);
4589
+ if (target !== null && isStack(target)) {
4590
+ addPanelToStack(target, panelId, zone.gapIndex);
4591
+ if (transferName && vacated !== null && vacated !== target && vacated.name !== void 0 && target.name === void 0) {
4592
+ target.name = vacated.name;
4593
+ delete vacated.name;
4594
+ }
4595
+ }
3975
4596
  } else insertBeside(this._model, zone.targetPath, opToDirection(zone.operation), createStack([panelId]), zone.side);
3976
4597
  }
3977
4598
  /**
@@ -4107,16 +4728,6 @@ let DockHostElement = class DockHostElement extends LitElement {
4107
4728
  };
4108
4729
  window.addEventListener(`click`, swallow, true);
4109
4730
  }
4110
- _splitStack(path, op) {
4111
- const stack = findNode(this._model, path);
4112
- if (stack === null || !isStack(stack) || stack.panels.length === 0) return;
4113
- const activeId = stack.active;
4114
- this._returnAllToolbars();
4115
- removePanelFromStack(stack, activeId);
4116
- normalize(this._model);
4117
- insertBeside(this._model, path, opToDirection(op), createStack([activeId]), `after`);
4118
- this._commit();
4119
- }
4120
4731
  _removeFromModel(panelId) {
4121
4732
  const stackPath = findStackOf(this._model, panelId);
4122
4733
  if (stackPath === null) return;
@@ -4247,6 +4858,7 @@ let DockHostElement = class DockHostElement extends LitElement {
4247
4858
  id=${`dock-leaf-${pathStr}`}
4248
4859
  class="leaf"
4249
4860
  data-dock-path=${pathStr}
4861
+ data-dock-name=${stack.name ?? nothing}
4250
4862
  size=${size === void 0 ? nothing : String(size)}
4251
4863
  min=${collapsible ? `1px` : nothing}
4252
4864
  when-small=${collapsible ? `collapse` : nothing}
@@ -4258,26 +4870,19 @@ let DockHostElement = class DockHostElement extends LitElement {
4258
4870
  <slot name="empty"><span>Drop panels here</span></slot>
4259
4871
  </div>
4260
4872
  `);
4873
+ const toolbarSlot = stack.name !== void 0 && this._toolbarSlots.has(`toolbar-${stack.name}`) ? html`<slot name="toolbar-${stack.name}" slot="toolbar"></slot>` : nothing;
4261
4874
  return leafAttrs(html`
4262
4875
  <ixfx-tab-list
4263
4876
  class="strip"
4264
4877
  orientation="horizontal"
4878
+ scrollable
4879
+ shrink
4880
+ ?hide-labels=${stack.hideLabels === true}
4265
4881
  @change=${(e) => this._onTabChange(e, path)}
4266
4882
  @pointerdown=${this._onStripPointerDown}
4267
4883
  >
4268
4884
  ${repeat(stack.panels, (id) => id, (id) => this._renderTabItem(stack, id))}
4269
- <div slot="toolbar" class="group-toolbar">
4270
- <button
4271
- class="chrome-btn"
4272
- title="Split horizontally (top/bottom)"
4273
- @click=${() => this._splitStack(path, `split-horizontal`)}
4274
- >↕</button>
4275
- <button
4276
- class="chrome-btn"
4277
- title="Split vertically (left/right)"
4278
- @click=${() => this._splitStack(path, `split-vertical`)}
4279
- >↔</button>
4280
- </div>
4885
+ ${toolbarSlot}
4281
4886
  </ixfx-tab-list>
4282
4887
  <div class="leaf-body">
4283
4888
  <slot name="dock-panel-${stack.active}"></slot>
@@ -4357,9 +4962,14 @@ let DockHostElement = class DockHostElement extends LitElement {
4357
4962
  .strip {
4358
4963
  flex-shrink: 0;
4359
4964
  border: 0;
4360
- /* The leaf background shows through selected tabs; the accent
4361
- underline indicator carries the selection. */
4965
+ /* The leaf background shows through selected tabs; a text underline
4966
+ (not the border indicator) carries the selection. */
4362
4967
  --ixfx-tab-selected-background: transparent;
4968
+ --ixfx-selected-tab-border-width: 0;
4969
+ --ixfx-selected-tab-text-decoration: underline;
4970
+ --ixfx-selected-tab-text-decoration-thickness: 2px;
4971
+ --ixfx-selected-tab-text-decoration-offset: 4px;
4972
+ --ixfx-tab-hover-background: var(--surface-4);
4363
4973
  }
4364
4974
 
4365
4975
  /* Uniform tab height: a panel toolbar button would otherwise inflate
@@ -4368,6 +4978,11 @@ let DockHostElement = class DockHostElement extends LitElement {
4368
4978
  height: var(--dock-tab-height, 30px);
4369
4979
  }
4370
4980
 
4981
+ /* Breathing room between strip toolbars and the leaf edge. */
4982
+ .strip::part(list-toolbar) {
4983
+ padding-right: var(--space-xs);
4984
+ }
4985
+
4371
4986
  .leaf-body {
4372
4987
  flex: 1;
4373
4988
  min-height: 0;
@@ -4389,38 +5004,6 @@ let DockHostElement = class DockHostElement extends LitElement {
4389
5004
  margin: var(--space-s);
4390
5005
  }
4391
5006
 
4392
- .group-toolbar {
4393
- display: flex;
4394
- gap: 2px;
4395
- padding: 0 var(--space-xs);
4396
- }
4397
-
4398
- .chrome-btn {
4399
- display: inline-flex;
4400
- align-items: center;
4401
- justify-content: center;
4402
- width: 1.6em;
4403
- height: 1.6em;
4404
- padding: 0;
4405
- background: none;
4406
- border: none;
4407
- border-radius: var(--radius-s);
4408
- color: inherit;
4409
- cursor: pointer;
4410
- opacity: var(--prominence-idle-opacity);
4411
- transition: background var(--transition), opacity var(--transition);
4412
- }
4413
-
4414
- .chrome-btn:hover {
4415
- background: var(--surface-5);
4416
- opacity: var(--prominence-tickled-opacity);
4417
- }
4418
-
4419
- .chrome-btn:focus-visible {
4420
- outline: 2px solid var(--accent);
4421
- outline-offset: 1px;
4422
- }
4423
-
4424
5007
  .overlay {
4425
5008
  position: absolute;
4426
5009
  inset: 0;
@@ -4490,6 +5073,10 @@ __decorate([property({
4490
5073
  type: String,
4491
5074
  attribute: `accepts`
4492
5075
  })], DockHostElement.prototype, "accepts", void 0);
5076
+ __decorate([property({
5077
+ type: Boolean,
5078
+ attribute: `isolated`
5079
+ })], DockHostElement.prototype, "isolated", void 0);
4493
5080
  __decorate([property({ attribute: false })], DockHostElement.prototype, "canAccept", void 0);
4494
5081
  __decorate([state()], DockHostElement.prototype, "_model", void 0);
4495
5082
  __decorate([state()], DockHostElement.prototype, "_pool", void 0);
@@ -4537,6 +5124,10 @@ function zoneToTarget(zone) {
4537
5124
  function cloneNode(node) {
4538
5125
  return JSON.parse(JSON.stringify(node));
4539
5126
  }
5127
+ /** Panel content kind, normalized (`'any'` when unset/blank). */
5128
+ function normalizeKind(kind) {
5129
+ return (kind?.trim() ?? ``) || `any`;
5130
+ }
4540
5131
  function parsePath(pathStr) {
4541
5132
  return pathStr.length === 0 ? [] : pathStr.split(`-`).filter((s) => s.length > 0).map(Number);
4542
5133
  }
@@ -7900,6 +8491,6 @@ function init() {
7900
8491
  console.log(`Init`);
7901
8492
  }
7902
8493
  //#endregion
7903
- export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectDataResult, collectPanelIds, collectStackPaths, computeColour, createAcCompletionProvider, createElementIncrSearch, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, getActiveDragPanelId, getIcon, hasIcon, iconBus, init, insertBeside, isDragging, isSplit, isStack, monitorIndeterminate, normalise, normalize, parseColourScaleAttr, parseLayout, pointerOverAnyHost, readBaseColour, registerIcon, removePanelFromStack, resolveCssVars, resolveGrid, resolveTracks, serializeLayout, setActive, svgToDataUri, timelineStyles, toSnapshot, walk, wrapRoot };
8494
+ export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createElementIncrSearch, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isSplit, isStack, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, parseColourScaleAttr, parseLayout, pointerOverAnyHost, readBaseColour, registerFontIcon, registerIcon, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, timelineStyles, toSnapshot, walk, wrapRoot };
7904
8495
 
7905
8496
  //# sourceMappingURL=index.js.map