@ixfx/components 0.10.0 → 0.10.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 (110) hide show
  1. package/bundle/index.d.ts +233 -21
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2101 -150
  4. package/bundle/index.js.map +1 -1
  5. package/bundle/style.css +38 -85
  6. package/dist/ac-text.js +1 -1
  7. package/dist/ac-token.js +2 -2
  8. package/dist/{colour-picker-R73i0QTP.js → colour-picker-WDrPuqoO.js} +2 -2
  9. package/dist/{colour-picker-R73i0QTP.js.map → colour-picker-WDrPuqoO.js.map} +1 -1
  10. package/dist/colour-picker.js +1 -1
  11. package/dist/crumbs.d.ts +1 -1
  12. package/dist/crumbs.js +2 -2
  13. package/dist/{data-grid-C5hk_pzK.d.ts → data-grid-pv6eaJCZ.d.ts} +4 -3
  14. package/dist/data-grid-pv6eaJCZ.d.ts.map +1 -0
  15. package/dist/data-grid.d.ts +2 -2
  16. package/dist/data-grid.js +28 -10
  17. package/dist/data-grid.js.map +1 -1
  18. package/dist/defaults-kVl1t2tT.js +59 -0
  19. package/dist/defaults-kVl1t2tT.js.map +1 -0
  20. package/dist/{detail-list-CxPpv89T.d.ts → detail-list-BA3RVVCB.d.ts} +4 -4
  21. package/dist/{detail-list-CxPpv89T.d.ts.map → detail-list-BA3RVVCB.d.ts.map} +1 -1
  22. package/dist/detail-list.d.ts +3 -3
  23. package/dist/detail-list.js +4 -4
  24. package/dist/{grid-list-DSnYOGh7.js → grid-list-CIc4jo-x.js} +34 -7
  25. package/dist/grid-list-CIc4jo-x.js.map +1 -0
  26. package/dist/{grid-list-BGvoWbZz.d.ts → grid-list-DWXJoRnK.d.ts} +4 -4
  27. package/dist/{grid-list-BGvoWbZz.d.ts.map → grid-list-DWXJoRnK.d.ts.map} +1 -1
  28. package/dist/grid-list.d.ts +2 -2
  29. package/dist/grid-list.js +1 -1
  30. package/dist/handle-DAXs7kbJ.js +656 -0
  31. package/dist/handle-DAXs7kbJ.js.map +1 -0
  32. package/dist/{icon-BCJJYEpm.js → icon-1gpwtPx8.js} +10 -1
  33. package/dist/icon-1gpwtPx8.js.map +1 -0
  34. package/dist/icon-CDGprSdE.d.ts.map +1 -1
  35. package/dist/icons.d.ts +1 -1
  36. package/dist/icons.js +2 -2
  37. package/dist/{incr-search-C05WjbZh.js → incr-search-D91M4Ozq.js} +5 -4
  38. package/dist/incr-search-D91M4Ozq.js.map +1 -0
  39. package/dist/incr-search.d.ts +1 -1
  40. package/dist/incr-search.js +1 -1
  41. package/dist/index-9AhaHIT2.d.ts.map +1 -1
  42. package/dist/index-CYndvTWH.d.ts +38 -0
  43. package/dist/index-CYndvTWH.d.ts.map +1 -0
  44. package/dist/{index--5BSdbnL.d.ts → index-CsaQwNkz.d.ts} +3 -2
  45. package/dist/index-CsaQwNkz.d.ts.map +1 -0
  46. package/dist/index.d.ts +207 -11
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +1295 -40
  49. package/dist/index.js.map +1 -1
  50. package/dist/{list-native-drag-controller-D97TYjn1.js → list-native-drag-controller-B9xO0XWn.js} +6 -4
  51. package/dist/list-native-drag-controller-B9xO0XWn.js.map +1 -0
  52. package/dist/{list-native-drag-controller-DWiXQZou.d.ts → list-native-drag-controller-BAuWAtyQ.d.ts} +2 -2
  53. package/dist/{list-native-drag-controller-DWiXQZou.d.ts.map → list-native-drag-controller-BAuWAtyQ.d.ts.map} +1 -1
  54. package/dist/{menu-C8a-84S5.js → menu-B2Qv3Oiz.js} +6 -5
  55. package/dist/menu-B2Qv3Oiz.js.map +1 -0
  56. package/dist/{menu-item-DzizkMH-.js → menu-item-Woc8jJc1.js} +4 -4
  57. package/dist/menu-item-Woc8jJc1.js.map +1 -0
  58. package/dist/menu.js +2 -2
  59. package/dist/miller.d.ts +2 -2
  60. package/dist/miller.js +1 -1
  61. package/dist/narrowed-text.js +1 -1
  62. package/dist/{scroll-fade-CAs1EabA.js → scroll-fade-BrNUIYnm.js} +16 -3
  63. package/dist/scroll-fade-BrNUIYnm.js.map +1 -0
  64. package/dist/select-horiz.js +1 -1
  65. package/dist/{split-layout-3yBOEVZW.js → split-layout-BJEU1W0d.js} +2 -2
  66. package/dist/{split-layout-3yBOEVZW.js.map → split-layout-BJEU1W0d.js.map} +1 -1
  67. package/dist/split-layout.js +1 -1
  68. package/dist/style.css +38 -85
  69. package/dist/tabs.js +1 -1
  70. package/dist/{timeline-CFlBFFGZ.js → timeline-BP1Ux2EV.js} +2 -2
  71. package/dist/{timeline-CFlBFFGZ.js.map → timeline-BP1Ux2EV.js.map} +1 -1
  72. package/dist/timeline.js +1 -1
  73. package/dist/titlebar.js +1 -1
  74. package/dist/{tree-C25xYUa6.js → tree-Bm2TzTfe.js} +234 -15
  75. package/dist/tree-Bm2TzTfe.js.map +1 -0
  76. package/dist/{tree-component-mw8x-n2Y.d.ts → tree-component-DGmKBbpy.d.ts} +14 -1
  77. package/dist/tree-component-DGmKBbpy.d.ts.map +1 -0
  78. package/dist/tree.d.ts +15 -3
  79. package/dist/tree.d.ts.map +1 -1
  80. package/dist/tree.js +1 -1
  81. package/dist/{vertical-list--tFanQwM.d.ts → vertical-list-BWLqy3PJ.d.ts} +3 -3
  82. package/dist/{vertical-list--tFanQwM.d.ts.map → vertical-list-BWLqy3PJ.d.ts.map} +1 -1
  83. package/dist/vertical-list.d.ts +2 -2
  84. package/dist/vertical-list.js +2 -2
  85. package/docs-user/README.md +1 -0
  86. package/docs-user/icons.md +24 -10
  87. package/docs-user/index.json +8 -3
  88. package/docs-user/llms.txt +2 -1
  89. package/docs-user/multicol-list.md +128 -0
  90. package/docs-user/tree.md +12 -0
  91. package/docs-user/vertical-list.md +65 -9
  92. package/llms.txt +2 -1
  93. package/package.json +1 -1
  94. package/dist/data-grid-C5hk_pzK.d.ts.map +0 -1
  95. package/dist/defaults-BJwDsMH4.js +0 -86
  96. package/dist/defaults-BJwDsMH4.js.map +0 -1
  97. package/dist/grid-list-DSnYOGh7.js.map +0 -1
  98. package/dist/handle-BPOxBOKf.js +0 -219
  99. package/dist/handle-BPOxBOKf.js.map +0 -1
  100. package/dist/icon-BCJJYEpm.js.map +0 -1
  101. package/dist/incr-search-C05WjbZh.js.map +0 -1
  102. package/dist/index--5BSdbnL.d.ts.map +0 -1
  103. package/dist/index-CS9yM8L-.d.ts +0 -47
  104. package/dist/index-CS9yM8L-.d.ts.map +0 -1
  105. package/dist/list-native-drag-controller-D97TYjn1.js.map +0 -1
  106. package/dist/menu-C8a-84S5.js.map +0 -1
  107. package/dist/menu-item-DzizkMH-.js.map +0 -1
  108. package/dist/scroll-fade-CAs1EabA.js.map +0 -1
  109. package/dist/tree-C25xYUa6.js.map +0 -1
  110. package/dist/tree-component-mw8x-n2Y.d.ts.map +0 -1
package/bundle/index.js CHANGED
@@ -1545,6 +1545,15 @@ let IxfxIconElement = class IxfxIconElement extends i {
1545
1545
  if (this.rotate?.length > 0) this.style.setProperty(`--rotate`, this.rotate);
1546
1546
  else this.style.removeProperty(`--rotate`);
1547
1547
  }
1548
+ if (changed.has(`size`)) {
1549
+ if (this.size !== void 0) {
1550
+ this.style.width = `${this.size}px`;
1551
+ this.style.height = `${this.size}px`;
1552
+ } else {
1553
+ this.style.removeProperty(`width`);
1554
+ this.style.removeProperty(`height`);
1555
+ }
1556
+ }
1548
1557
  }
1549
1558
  static {
1550
1559
  this.styles = [themeFallbacks, i$3`
@@ -3019,10 +3028,10 @@ var ListSearchController = class {
3019
3028
  />
3020
3029
  ${hasQuery ? b`<span class="list-search-count">${this.#matches.length === 0 ? `0` : this.#matchIndex + 1} / ${this.#matches.length}</span>` : A}
3021
3030
  ${this.#mode === `navigate` && this.#matches.length > 1 ? b`
3022
- <button class="list-search-icon-btn" title="Previous match (↑)" @click=${() => this.prevMatch()}>‹</button>
3023
- <button class="list-search-icon-btn" title="Next match (↓)" @click=${() => this.nextMatch()}>›</button>
3031
+ <button class="list-search-icon-btn" title="Previous match (↑)" @click=${() => this.prevMatch()}><ixfx-icon name="chevron-left" size="14"></ixfx-icon></button>
3032
+ <button class="list-search-icon-btn" title="Next match (↓)" @click=${() => this.nextMatch()}><ixfx-icon name="chevron-right" size="14"></ixfx-icon></button>
3024
3033
  ` : A}
3025
- <button class="list-search-icon-btn" title="Close (Esc)" @click=${() => this.close()}>✕</button>
3034
+ <button class="list-search-icon-btn" title="Close (Esc)" @click=${() => this.close()}><ixfx-icon name="close" size="14"></ixfx-icon></button>
3026
3035
  </div>
3027
3036
  ${others.length > 0 || showSelectMatches ? b`
3028
3037
  <div class="list-search-switch">
@@ -3926,11 +3935,20 @@ AcTokenElement = __decorate([safeCustomElement(`ixfx-ac-token`)], AcTokenElement
3926
3935
  * - **flat** — a solid rounded capsule (bar) or dot (`spot`), filled with
3927
3936
  * `--ixfx-handle-color`.
3928
3937
  * - **dotted** — marks in the [Lucide *grip*](https://lucide.dev/icons/grip)
3929
- * arrangement: three in a row (`horizontal`), three in a column (`vertical`) or a
3930
- * 3×3 block (`spot`). Tinted by `--ixfx-handle-color`. Each mark's corner
3931
- * rounding is `--ixfx-handle-radius` (→ the theme `--radius-l`), so a flat,
3932
- * squared-off theme renders them as **squares** instead of circles.
3933
- * - **dimpled** — that same layout, but each bar mark is pressed in: a lighter
3938
+ * arrangement: one or more marks along the long axis (`horizontal`: a row,
3939
+ * `vertical`: a column), repeated in one or more rows/columns across the
3940
+ * bar's cross axis (or a 3×3 block for `spot`, unaffected by the row/column
3941
+ * / mark-count behaviour below). Tinted by `--ixfx-handle-color`. Each
3942
+ * mark's corner rounding is `--ixfx-handle-radius` (→ the theme
3943
+ * `--radius-l`), so a flat, squared-off theme renders them as **squares**
3944
+ * instead of circles. For `horizontal` / `vertical`, `--ixfx-handle-length`
3945
+ * decides how many marks (1-7) fit along the long axis — spaced by
3946
+ * `--ixfx-handle-dot-gap` — the same way `--ixfx-handle-thickness` decides
3947
+ * how many rows (or columns) of marks (1-7) fit across the cross axis —
3948
+ * spaced by `--ixfx-handle-dot-pitch`. Both the bar's rendered length and
3949
+ * its thickness snap to the nearest whole multiple of the relevant spacing
3950
+ * so marks never clip at the edges.
3951
+ * - **dimpled** — that same layout, but each mark is pressed in: a lighter
3934
3952
  * copy peeking out above-left and a darker copy below-right of the base mark.
3935
3953
  * - **grated** — fine 45° knurling: each ridge is a lit edge, the base colour,
3936
3954
  * then a shaded edge. `spot` is the same hatch on a square crop.
@@ -3961,14 +3979,26 @@ AcTokenElement = __decorate([safeCustomElement(`ixfx-ac-token`)], AcTokenElement
3961
3979
  * ## CSS variables (defaults shown; override on the element or any ancestor)
3962
3980
  * - `--ixfx-handle-color` `var(--surface-muted-text)` — `flat` / `dotted` fill
3963
3981
  * - `--ixfx-handle-color-active` `var(--accent)` — `flat` / `dotted` hover / focus
3964
- * - `--ixfx-handle-thickness` `6px` — cross-axis size of a bar
3965
- * - `--ixfx-handle-length` `44px` — long-axis size of a bar (`100%` to fill)
3982
+ * - `--ixfx-handle-thickness` `6px` — cross-axis size of a bar (dotted /
3983
+ * dimpled `horizontal` / `vertical` bars snap this to the nearest whole multiple
3984
+ * of `--ixfx-handle-dot-pitch` so their mark rows / columns fit exactly)
3985
+ * - `--ixfx-handle-length` `44px` — long-axis size of a bar (`100%` to
3986
+ * fill). For dotted / dimpled `horizontal` / `vertical` bars this also decides
3987
+ * how many marks (1-7) fit along the long axis — spaced by
3988
+ * `--ixfx-handle-dot-gap` — the same way `--ixfx-handle-thickness` decides row /
3989
+ * column count across the cross axis, and the bar's rendered length likewise
3990
+ * snaps to the nearest whole multiple of `--ixfx-handle-dot-gap` so marks
3991
+ * never clip at the edges.
3966
3992
  * - `--ixfx-handle-spot-size` `14px` — width = height of a `spot`
3967
3993
  * - `--ixfx-handle-radius` `var(--radius-l)` — `flat` capsule / dotted & dimpled mark rounding
3968
3994
  * - `--ixfx-handle-opacity` `0.55` — rest opacity
3969
3995
  * - `--ixfx-handle-opacity-active` `1`
3970
3996
  * - `--ixfx-handle-dot-size` `3px` — mark size (dotted / dimpled)
3971
- * - `--ixfx-handle-dot-gap` `length/4` (bar) · `spot/3` — mark centre-to-centre step
3997
+ * - `--ixfx-handle-dot-gap` `length/4` (bar) · `spot/3` — mark centre-to-centre
3998
+ * step along the long axis (dotted / dimpled `horizontal` / `vertical` bars fit up
3999
+ * to 7 marks, driven by `--ixfx-handle-length`; `spot` is a fixed 3×3 block)
4000
+ * - `--ixfx-handle-dot-pitch` `dot-size * 2` — row/column centre-to-centre step across
4001
+ * the cross axis (dotted / dimpled `horizontal` / `vertical` only; up to 7 rows/columns)
3972
4002
  * - `--ixfx-handle-tile` `5px` — knurl period (grated)
3973
4003
  * - `--ixfx-handle-highlight` `color-mix(currentColor 35%, #fff)` — lit deboss edge
3974
4004
  * - `--ixfx-handle-shadow` `color-mix(currentColor 60%, #000)` — shaded deboss edge
@@ -3989,6 +4019,28 @@ const handleStyles = i$3`
3989
4019
  /* Centre-to-centre step of the dotted / dimpled marks (a length). Defaults to
3990
4020
  a share of the long axis; a bar shows ~3 marks. */
3991
4021
  --_handle-gap: var(--ixfx-handle-dot-gap, calc(var(--_handle-length) / 4));
4022
+ /* Centre-to-centre step between rows (horizontal) / columns (vertical) of
4023
+ marks across the bar's cross axis. */
4024
+ --_handle-pitch: var(--ixfx-handle-dot-pitch, calc(var(--_handle-dot) * 2));
4025
+ /* How many whole pitch-steps fit across the thickness, rounded to the
4026
+ nearest step and clamped to 1-7 rows/columns. */
4027
+ --_handle-rows: clamp(1, round(nearest, calc(var(--_handle-thickness) / var(--_handle-pitch)), 1), 7);
4028
+ /* An even row/column count has no centre step, so the whole grid (the
4029
+ ::before mark and its box-shadow copies, spaced at whole --_handle-pitch
4030
+ steps from it) is nudged by half a pitch to stay centred and symmetric. */
4031
+ --_handle-row-shift: calc((1 - mod(var(--_handle-rows), 2)) * var(--_handle-pitch) / 2);
4032
+ /* The bar's cross-axis size snapped to exactly --_handle-rows steps of
4033
+ --_handle-pitch, so mark rows/columns fit without clipping. */
4034
+ --_handle-thickness-snapped: calc(var(--_handle-rows) * var(--_handle-pitch));
4035
+ /* How many whole gap-steps fit across the long axis, rounded to the
4036
+ nearest step and clamped to 1-7 marks. */
4037
+ --_handle-cols: clamp(1, round(nearest, calc(var(--_handle-length) / var(--_handle-gap)), 1), 7);
4038
+ /* An even mark count has no centre step, so the whole grid is nudged by
4039
+ half a gap along the long axis to stay centred and symmetric. */
4040
+ --_handle-col-shift: calc((1 - mod(var(--_handle-cols), 2)) * var(--_handle-gap) / 2);
4041
+ /* The bar's long-axis size snapped to exactly --_handle-cols steps of
4042
+ --_handle-gap, so marks fit without clipping. */
4043
+ --_handle-length-snapped: calc(var(--_handle-cols) * var(--_handle-gap));
3992
4044
  /* How far a dimpled mark's highlight / shadow copy is offset. */
3993
4045
  --_dimple-off: calc(var(--_handle-dot) * 0.15);
3994
4046
  /* The mark colour lightened / darkened for the two deboss edges. */
@@ -4038,14 +4090,32 @@ const handleStyles = i$3`
4038
4090
  border-radius: 50%;
4039
4091
  }
4040
4092
 
4041
- /* ── dotted / dimpled: 3 marks in a row / column (or 3×3 for spot), drawn as
4042
- one ::before mark whose border-radius follows the theme — round normally,
4043
- square in a flat / squared-off theme (--radius-l: 0). box-shadow paints
4044
- the rest of the marks. ─────────────────────────────────────────────── */
4093
+ /* ── dotted / dimpled: 3 marks in a row / column (or 3×3 for spot), repeated
4094
+ in --_handle-rows rows/columns across the cross axis for horizontal /
4095
+ vertical, drawn as one ::before mark whose border-radius follows the
4096
+ theme — round normally, square in a flat / squared-off theme
4097
+ (--radius-l: 0). box-shadow paints the rest of the marks. The extra
4098
+ rows/columns beyond --_handle-rows are still emitted (a fixed 7-row
4099
+ grid, CSS can't loop) but land outside the snapped, overflow: hidden
4100
+ bar, so they're clipped cleanly rather than shown or partially cut. ── */
4045
4101
  .ixfx-handle[data-handle='dotted'],
4046
4102
  .ixfx-handle[data-handle='dimpled'] {
4047
4103
  position: relative;
4048
4104
  }
4105
+ .ixfx-handle[data-handle='dotted']:not([data-shape='spot']),
4106
+ .ixfx-handle[data-handle='dimpled']:not([data-shape='spot']) {
4107
+ overflow: hidden;
4108
+ }
4109
+ .ixfx-handle[data-handle='dotted']:not([data-shape='vertical']):not([data-shape='spot']),
4110
+ .ixfx-handle[data-handle='dimpled']:not([data-shape='vertical']):not([data-shape='spot']) {
4111
+ width: var(--_handle-length-snapped);
4112
+ height: var(--_handle-thickness-snapped);
4113
+ }
4114
+ .ixfx-handle[data-handle='dotted'][data-shape='vertical'],
4115
+ .ixfx-handle[data-handle='dimpled'][data-shape='vertical'] {
4116
+ width: var(--_handle-thickness-snapped);
4117
+ height: var(--_handle-length-snapped);
4118
+ }
4049
4119
  .ixfx-handle[data-handle='dotted']::before,
4050
4120
  .ixfx-handle[data-handle='dimpled']::before {
4051
4121
  content: '';
@@ -4059,16 +4129,112 @@ const handleStyles = i$3`
4059
4129
  background: currentColor;
4060
4130
  }
4061
4131
 
4062
- /* dotted — the two other marks are plain copies along the axis. */
4063
- .ixfx-handle[data-handle='dotted']::before {
4132
+ /* dotted — the other marks are plain copies: 2 more along the long axis,
4133
+ repeated across up to 7 rows/columns on the cross axis (clipped to
4134
+ --_handle-rows by the snapped, overflow: hidden bar — see above). */
4135
+ .ixfx-handle[data-handle='dotted']:not([data-shape='vertical']):not([data-shape='spot'])::before {
4136
+ transform: translate(calc(-50% + var(--_handle-col-shift)), calc(-50% + var(--_handle-row-shift)));
4064
4137
  box-shadow:
4138
+ calc(-3 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4139
+ calc(-2 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4140
+ calc(-1 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4141
+ 0 calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4142
+ calc(var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4143
+ calc(2 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4144
+ calc(3 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4145
+ calc(-3 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4146
+ calc(-2 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4147
+ calc(-1 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4148
+ 0 calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4149
+ calc(var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4150
+ calc(2 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4151
+ calc(3 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4152
+ calc(-3 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4153
+ calc(-2 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4154
+ calc(-1 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4155
+ 0 calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4156
+ calc(var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4157
+ calc(2 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4158
+ calc(3 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4159
+ calc(-3 * var(--_handle-gap)) 0 0 0 currentColor,
4160
+ calc(-2 * var(--_handle-gap)) 0 0 0 currentColor,
4065
4161
  calc(-1 * var(--_handle-gap)) 0 0 0 currentColor,
4066
- var(--_handle-gap) 0 0 0 currentColor;
4162
+ calc(var(--_handle-gap)) 0 0 0 currentColor,
4163
+ calc(2 * var(--_handle-gap)) 0 0 0 currentColor,
4164
+ calc(3 * var(--_handle-gap)) 0 0 0 currentColor,
4165
+ calc(-3 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4166
+ calc(-2 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4167
+ calc(-1 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4168
+ 0 calc(var(--_handle-pitch)) 0 0 currentColor,
4169
+ calc(var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4170
+ calc(2 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4171
+ calc(3 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4172
+ calc(-3 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4173
+ calc(-2 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4174
+ calc(-1 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4175
+ 0 calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4176
+ calc(var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4177
+ calc(2 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4178
+ calc(3 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4179
+ calc(-3 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4180
+ calc(-2 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4181
+ calc(-1 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4182
+ 0 calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4183
+ calc(var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4184
+ calc(2 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4185
+ calc(3 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor;
4067
4186
  }
4068
4187
  .ixfx-handle[data-handle='dotted'][data-shape='vertical']::before {
4188
+ transform: translate(calc(-50% + var(--_handle-row-shift)), calc(-50% + var(--_handle-col-shift)));
4069
4189
  box-shadow:
4190
+ calc(-3 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4191
+ calc(-3 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4192
+ calc(-3 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4193
+ calc(-3 * var(--_handle-pitch)) 0 0 0 currentColor,
4194
+ calc(-3 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4195
+ calc(-3 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4196
+ calc(-3 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4197
+ calc(-2 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4198
+ calc(-2 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4199
+ calc(-2 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4200
+ calc(-2 * var(--_handle-pitch)) 0 0 0 currentColor,
4201
+ calc(-2 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4202
+ calc(-2 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4203
+ calc(-2 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4204
+ calc(-1 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4205
+ calc(-1 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4206
+ calc(-1 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4207
+ calc(-1 * var(--_handle-pitch)) 0 0 0 currentColor,
4208
+ calc(-1 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4209
+ calc(-1 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4210
+ calc(-1 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4211
+ 0 calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4212
+ 0 calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4070
4213
  0 calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4071
- 0 var(--_handle-gap) 0 0 currentColor;
4214
+ 0 calc(var(--_handle-gap)) 0 0 currentColor,
4215
+ 0 calc(2 * var(--_handle-gap)) 0 0 currentColor,
4216
+ 0 calc(3 * var(--_handle-gap)) 0 0 currentColor,
4217
+ calc(var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4218
+ calc(var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4219
+ calc(var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4220
+ calc(var(--_handle-pitch)) 0 0 0 currentColor,
4221
+ calc(var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4222
+ calc(var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4223
+ calc(var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4224
+ calc(2 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4225
+ calc(2 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4226
+ calc(2 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4227
+ calc(2 * var(--_handle-pitch)) 0 0 0 currentColor,
4228
+ calc(2 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4229
+ calc(2 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4230
+ calc(2 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4231
+ calc(3 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4232
+ calc(3 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4233
+ calc(3 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4234
+ calc(3 * var(--_handle-pitch)) 0 0 0 currentColor,
4235
+ calc(3 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4236
+ calc(3 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4237
+ calc(3 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor;
4072
4238
  }
4073
4239
  .ixfx-handle[data-handle='dotted'][data-shape='spot']::before,
4074
4240
  .ixfx-handle[data-handle='dimpled'][data-shape='spot']::before {
@@ -4083,29 +4249,309 @@ const handleStyles = i$3`
4083
4249
  var(--_handle-gap) var(--_handle-gap) 0 0 currentColor;
4084
4250
  }
4085
4251
 
4086
- /* dimpled — each bar mark also gets a lighter copy up-left and a darker copy
4087
- down-right (spot stays plain — the 3D is imperceptible at that size). */
4088
- .ixfx-handle[data-handle='dimpled']:not([data-shape='spot'])::before {
4252
+ /* dimpled — each mark also gets a lighter copy up-left and a darker copy
4253
+ down-right, repeated across up to 7 rows/columns on the cross axis
4254
+ (clipped to --_handle-rows — see above). Spot stays plain — the 3D is
4255
+ imperceptible at that size. */
4256
+ .ixfx-handle[data-handle='dimpled']:not([data-shape='vertical']):not([data-shape='spot'])::before {
4257
+ transform: translate(calc(-50% + var(--_handle-col-shift)), calc(-50% + var(--_handle-row-shift)));
4089
4258
  box-shadow:
4090
4259
  calc(-1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4091
4260
  var(--_dimple-off) var(--_dimple-off) 0 0 var(--_handle-lo),
4261
+ calc(-3 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4262
+ calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4263
+ calc(-3 * var(--_handle-gap) + var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4264
+ calc(-2 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4265
+ calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4266
+ calc(-2 * var(--_handle-gap) + var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4267
+ calc(-1 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4268
+ calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4269
+ calc(-1 * var(--_handle-gap) + var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4270
+ 0 calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4271
+ calc(-1 * var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4272
+ var(--_dimple-off) calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4273
+ calc(var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4274
+ calc(var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4275
+ calc(var(--_handle-gap) + var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4276
+ calc(2 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4277
+ calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4278
+ calc(2 * var(--_handle-gap) + var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4279
+ calc(3 * var(--_handle-gap)) calc(-3 * var(--_handle-pitch)) 0 0 currentColor,
4280
+ calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4281
+ calc(3 * var(--_handle-gap) + var(--_dimple-off)) calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4282
+ calc(-3 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4283
+ calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4284
+ calc(-3 * var(--_handle-gap) + var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4285
+ calc(-2 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4286
+ calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4287
+ calc(-2 * var(--_handle-gap) + var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4288
+ calc(-1 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4289
+ calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4290
+ calc(-1 * var(--_handle-gap) + var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4291
+ 0 calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4292
+ calc(-1 * var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4293
+ var(--_dimple-off) calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4294
+ calc(var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4295
+ calc(var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4296
+ calc(var(--_handle-gap) + var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4297
+ calc(2 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4298
+ calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4299
+ calc(2 * var(--_handle-gap) + var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4300
+ calc(3 * var(--_handle-gap)) calc(-2 * var(--_handle-pitch)) 0 0 currentColor,
4301
+ calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4302
+ calc(3 * var(--_handle-gap) + var(--_dimple-off)) calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4303
+ calc(-3 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4304
+ calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4305
+ calc(-3 * var(--_handle-gap) + var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4306
+ calc(-2 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4307
+ calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4308
+ calc(-2 * var(--_handle-gap) + var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4309
+ calc(-1 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4310
+ calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4311
+ calc(-1 * var(--_handle-gap) + var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4312
+ 0 calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4313
+ calc(-1 * var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4314
+ var(--_dimple-off) calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4315
+ calc(var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4316
+ calc(var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4317
+ calc(var(--_handle-gap) + var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4318
+ calc(2 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4319
+ calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4320
+ calc(2 * var(--_handle-gap) + var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4321
+ calc(3 * var(--_handle-gap)) calc(-1 * var(--_handle-pitch)) 0 0 currentColor,
4322
+ calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4323
+ calc(3 * var(--_handle-gap) + var(--_dimple-off)) calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4324
+ calc(-3 * var(--_handle-gap)) 0 0 0 currentColor,
4325
+ calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4326
+ calc(-3 * var(--_handle-gap) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4327
+ calc(-2 * var(--_handle-gap)) 0 0 0 currentColor,
4328
+ calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4329
+ calc(-2 * var(--_handle-gap) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4092
4330
  calc(-1 * var(--_handle-gap)) 0 0 0 currentColor,
4093
- calc(-1 * var(--_handle-gap) - var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4331
+ calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4094
4332
  calc(-1 * var(--_handle-gap) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4095
- var(--_handle-gap) 0 0 0 currentColor,
4096
- calc(var(--_handle-gap) - var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4097
- calc(var(--_handle-gap) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo);
4333
+ calc(var(--_handle-gap)) 0 0 0 currentColor,
4334
+ calc(var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4335
+ calc(var(--_handle-gap) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4336
+ calc(2 * var(--_handle-gap)) 0 0 0 currentColor,
4337
+ calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4338
+ calc(2 * var(--_handle-gap) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4339
+ calc(3 * var(--_handle-gap)) 0 0 0 currentColor,
4340
+ calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4341
+ calc(3 * var(--_handle-gap) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4342
+ calc(-3 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4343
+ calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4344
+ calc(-3 * var(--_handle-gap) + var(--_dimple-off)) calc(var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4345
+ calc(-2 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4346
+ calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4347
+ calc(-2 * var(--_handle-gap) + var(--_dimple-off)) calc(var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4348
+ calc(-1 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4349
+ calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4350
+ calc(-1 * var(--_handle-gap) + var(--_dimple-off)) calc(var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4351
+ 0 calc(var(--_handle-pitch)) 0 0 currentColor,
4352
+ calc(-1 * var(--_dimple-off)) calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4353
+ var(--_dimple-off) calc(var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4354
+ calc(var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4355
+ calc(var(--_handle-gap) + -1 * var(--_dimple-off)) calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4356
+ calc(var(--_handle-gap) + var(--_dimple-off)) calc(var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4357
+ calc(2 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4358
+ calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4359
+ calc(2 * var(--_handle-gap) + var(--_dimple-off)) calc(var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4360
+ calc(3 * var(--_handle-gap)) calc(var(--_handle-pitch)) 0 0 currentColor,
4361
+ calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4362
+ calc(3 * var(--_handle-gap) + var(--_dimple-off)) calc(var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4363
+ calc(-3 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4364
+ calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4365
+ calc(-3 * var(--_handle-gap) + var(--_dimple-off)) calc(2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4366
+ calc(-2 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4367
+ calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4368
+ calc(-2 * var(--_handle-gap) + var(--_dimple-off)) calc(2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4369
+ calc(-1 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4370
+ calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4371
+ calc(-1 * var(--_handle-gap) + var(--_dimple-off)) calc(2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4372
+ 0 calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4373
+ calc(-1 * var(--_dimple-off)) calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4374
+ var(--_dimple-off) calc(2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4375
+ calc(var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4376
+ calc(var(--_handle-gap) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4377
+ calc(var(--_handle-gap) + var(--_dimple-off)) calc(2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4378
+ calc(2 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4379
+ calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4380
+ calc(2 * var(--_handle-gap) + var(--_dimple-off)) calc(2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4381
+ calc(3 * var(--_handle-gap)) calc(2 * var(--_handle-pitch)) 0 0 currentColor,
4382
+ calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4383
+ calc(3 * var(--_handle-gap) + var(--_dimple-off)) calc(2 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4384
+ calc(-3 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4385
+ calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4386
+ calc(-3 * var(--_handle-gap) + var(--_dimple-off)) calc(3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4387
+ calc(-2 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4388
+ calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4389
+ calc(-2 * var(--_handle-gap) + var(--_dimple-off)) calc(3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4390
+ calc(-1 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4391
+ calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4392
+ calc(-1 * var(--_handle-gap) + var(--_dimple-off)) calc(3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4393
+ 0 calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4394
+ calc(-1 * var(--_dimple-off)) calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4395
+ var(--_dimple-off) calc(3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4396
+ calc(var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4397
+ calc(var(--_handle-gap) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4398
+ calc(var(--_handle-gap) + var(--_dimple-off)) calc(3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4399
+ calc(2 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4400
+ calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4401
+ calc(2 * var(--_handle-gap) + var(--_dimple-off)) calc(3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4402
+ calc(3 * var(--_handle-gap)) calc(3 * var(--_handle-pitch)) 0 0 currentColor,
4403
+ calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4404
+ calc(3 * var(--_handle-gap) + var(--_dimple-off)) calc(3 * var(--_handle-pitch) + var(--_dimple-off)) 0 0 var(--_handle-lo);
4098
4405
  }
4099
4406
  .ixfx-handle[data-handle='dimpled'][data-shape='vertical']::before {
4407
+ transform: translate(calc(-50% + var(--_handle-row-shift)), calc(-50% + var(--_handle-col-shift)));
4100
4408
  box-shadow:
4101
4409
  calc(-1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4102
4410
  var(--_dimple-off) var(--_dimple-off) 0 0 var(--_handle-lo),
4411
+ calc(-3 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4412
+ calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4413
+ calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) calc(-3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4414
+ calc(-3 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4415
+ calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4416
+ calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) calc(-2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4417
+ calc(-3 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4418
+ calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4419
+ calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) calc(-1 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4420
+ calc(-3 * var(--_handle-pitch)) 0 0 0 currentColor,
4421
+ calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4422
+ calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4423
+ calc(-3 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4424
+ calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4425
+ calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) calc(var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4426
+ calc(-3 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4427
+ calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4428
+ calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) calc(2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4429
+ calc(-3 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4430
+ calc(-3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4431
+ calc(-3 * var(--_handle-pitch) + var(--_dimple-off)) calc(3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4432
+ calc(-2 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4433
+ calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4434
+ calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) calc(-3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4435
+ calc(-2 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4436
+ calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4437
+ calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) calc(-2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4438
+ calc(-2 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4439
+ calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4440
+ calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) calc(-1 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4441
+ calc(-2 * var(--_handle-pitch)) 0 0 0 currentColor,
4442
+ calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4443
+ calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4444
+ calc(-2 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4445
+ calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4446
+ calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) calc(var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4447
+ calc(-2 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4448
+ calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4449
+ calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) calc(2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4450
+ calc(-2 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4451
+ calc(-2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4452
+ calc(-2 * var(--_handle-pitch) + var(--_dimple-off)) calc(3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4453
+ calc(-1 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4454
+ calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4455
+ calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) calc(-3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4456
+ calc(-1 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4457
+ calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4458
+ calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) calc(-2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4459
+ calc(-1 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4460
+ calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4461
+ calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) calc(-1 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4462
+ calc(-1 * var(--_handle-pitch)) 0 0 0 currentColor,
4463
+ calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4464
+ calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4465
+ calc(-1 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4466
+ calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4467
+ calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) calc(var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4468
+ calc(-1 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4469
+ calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4470
+ calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) calc(2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4471
+ calc(-1 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4472
+ calc(-1 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4473
+ calc(-1 * var(--_handle-pitch) + var(--_dimple-off)) calc(3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4474
+ 0 calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4475
+ calc(-1 * var(--_dimple-off)) calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4476
+ var(--_dimple-off) calc(-3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4477
+ 0 calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4478
+ calc(-1 * var(--_dimple-off)) calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4479
+ var(--_dimple-off) calc(-2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4103
4480
  0 calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4104
- calc(-1 * var(--_dimple-off)) calc(-1 * var(--_handle-gap) - var(--_dimple-off)) 0 0 var(--_handle-hi),
4481
+ calc(-1 * var(--_dimple-off)) calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4105
4482
  var(--_dimple-off) calc(-1 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4106
- 0 var(--_handle-gap) 0 0 currentColor,
4107
- calc(-1 * var(--_dimple-off)) calc(var(--_handle-gap) - var(--_dimple-off)) 0 0 var(--_handle-hi),
4108
- var(--_dimple-off) calc(var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo);
4483
+ 0 calc(var(--_handle-gap)) 0 0 currentColor,
4484
+ calc(-1 * var(--_dimple-off)) calc(var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4485
+ var(--_dimple-off) calc(var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4486
+ 0 calc(2 * var(--_handle-gap)) 0 0 currentColor,
4487
+ calc(-1 * var(--_dimple-off)) calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4488
+ var(--_dimple-off) calc(2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4489
+ 0 calc(3 * var(--_handle-gap)) 0 0 currentColor,
4490
+ calc(-1 * var(--_dimple-off)) calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4491
+ var(--_dimple-off) calc(3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4492
+ calc(var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4493
+ calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4494
+ calc(var(--_handle-pitch) + var(--_dimple-off)) calc(-3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4495
+ calc(var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4496
+ calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4497
+ calc(var(--_handle-pitch) + var(--_dimple-off)) calc(-2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4498
+ calc(var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4499
+ calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4500
+ calc(var(--_handle-pitch) + var(--_dimple-off)) calc(-1 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4501
+ calc(var(--_handle-pitch)) 0 0 0 currentColor,
4502
+ calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4503
+ calc(var(--_handle-pitch) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4504
+ calc(var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4505
+ calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4506
+ calc(var(--_handle-pitch) + var(--_dimple-off)) calc(var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4507
+ calc(var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4508
+ calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4509
+ calc(var(--_handle-pitch) + var(--_dimple-off)) calc(2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4510
+ calc(var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4511
+ calc(var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4512
+ calc(var(--_handle-pitch) + var(--_dimple-off)) calc(3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4513
+ calc(2 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4514
+ calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4515
+ calc(2 * var(--_handle-pitch) + var(--_dimple-off)) calc(-3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4516
+ calc(2 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4517
+ calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4518
+ calc(2 * var(--_handle-pitch) + var(--_dimple-off)) calc(-2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4519
+ calc(2 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4520
+ calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4521
+ calc(2 * var(--_handle-pitch) + var(--_dimple-off)) calc(-1 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4522
+ calc(2 * var(--_handle-pitch)) 0 0 0 currentColor,
4523
+ calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4524
+ calc(2 * var(--_handle-pitch) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4525
+ calc(2 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4526
+ calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4527
+ calc(2 * var(--_handle-pitch) + var(--_dimple-off)) calc(var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4528
+ calc(2 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4529
+ calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4530
+ calc(2 * var(--_handle-pitch) + var(--_dimple-off)) calc(2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4531
+ calc(2 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4532
+ calc(2 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4533
+ calc(2 * var(--_handle-pitch) + var(--_dimple-off)) calc(3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4534
+ calc(3 * var(--_handle-pitch)) calc(-3 * var(--_handle-gap)) 0 0 currentColor,
4535
+ calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4536
+ calc(3 * var(--_handle-pitch) + var(--_dimple-off)) calc(-3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4537
+ calc(3 * var(--_handle-pitch)) calc(-2 * var(--_handle-gap)) 0 0 currentColor,
4538
+ calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4539
+ calc(3 * var(--_handle-pitch) + var(--_dimple-off)) calc(-2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4540
+ calc(3 * var(--_handle-pitch)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4541
+ calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4542
+ calc(3 * var(--_handle-pitch) + var(--_dimple-off)) calc(-1 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4543
+ calc(3 * var(--_handle-pitch)) 0 0 0 currentColor,
4544
+ calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4545
+ calc(3 * var(--_handle-pitch) + var(--_dimple-off)) var(--_dimple-off) 0 0 var(--_handle-lo),
4546
+ calc(3 * var(--_handle-pitch)) calc(var(--_handle-gap)) 0 0 currentColor,
4547
+ calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4548
+ calc(3 * var(--_handle-pitch) + var(--_dimple-off)) calc(var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4549
+ calc(3 * var(--_handle-pitch)) calc(2 * var(--_handle-gap)) 0 0 currentColor,
4550
+ calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(2 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4551
+ calc(3 * var(--_handle-pitch) + var(--_dimple-off)) calc(2 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo),
4552
+ calc(3 * var(--_handle-pitch)) calc(3 * var(--_handle-gap)) 0 0 currentColor,
4553
+ calc(3 * var(--_handle-pitch) + -1 * var(--_dimple-off)) calc(3 * var(--_handle-gap) + -1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4554
+ calc(3 * var(--_handle-pitch) + var(--_dimple-off)) calc(3 * var(--_handle-gap) + var(--_dimple-off)) 0 0 var(--_handle-lo);
4109
4555
  }
4110
4556
 
4111
4557
  /* ── grated: fine 45° knurling — each ridge is a lit edge, the base colour,
@@ -5021,71 +5467,41 @@ __decorate([state()], BottomDrawerElement.prototype, "_dragHeight", void 0);
5021
5467
  BottomDrawerElement = __decorate([safeCustomElement(`ixfx-bottom-drawer`)], BottomDrawerElement);
5022
5468
  //#endregion
5023
5469
  //#region src/icons/defaults.ts
5470
+ /** Chevron pointing down (Lucide `chevron-down`). Used for split-button dropdown toggle. */
5471
+ const ICON_CHEVRON_DOWN = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>`;
5024
5472
  /**
5025
- * Chevron pointing down. Used for split-button dropdown toggle.
5026
- * fill="currentColor" so it inherits CSS color.
5027
- */
5028
- const ICON_CHEVRON_DOWN = `<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"/></svg>`;
5029
- /**
5030
- * Caret pointing right. Used for tree-basic expand indicator as a CSS mask.
5031
- * fill="black" is correct for CSS mask-image use (shape only, color comes from background-color).
5032
- */
5033
- const ICON_CARET_RIGHT = `<svg viewBox="0 0 24 24" fill="black" xmlns="http://www.w3.org/2000/svg"><path d="M10 7l6 5-6 5V7z"/></svg>`;
5034
- /**
5035
- * Checkmark. Useful for menu-check-item or confirmation states.
5036
- * fill="currentColor" so it inherits CSS color.
5037
- */
5038
- const ICON_CHECK = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`;
5039
- /**
5040
- * Close / X icon. Useful for dialogs, notifications, dismissible elements.
5041
- * fill="currentColor" so it inherits CSS color.
5042
- */
5043
- const ICON_CLOSE = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>`;
5044
- /**
5045
- * Plus / add icon. Pairs with `close` for add/remove actions.
5046
- * fill="currentColor" so it inherits CSS color.
5047
- */
5048
- const ICON_ADD = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>`;
5049
- /**
5050
- * Error / warning icon. Useful for error notifications.
5051
- * fill="currentColor" so it inherits CSS color.
5052
- */
5053
- const ICON_ERROR = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>`;
5054
- /**
5055
- * Info icon. Useful for info notifications.
5056
- * fill="currentColor" so it inherits CSS color.
5057
- */
5058
- const ICON_INFO = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>`;
5059
- /**
5060
- * Warning icon. Useful for warning notifications.
5061
- * fill="currentColor" so it inherits CSS color.
5062
- */
5063
- const ICON_WARNING = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>`;
5064
- /**
5065
- * Chevron pointing right. Used for split-button switcher toggle.
5066
- * fill="currentColor" so it inherits CSS color.
5067
- */
5068
- const ICON_CHEVRON_RIGHT = `<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"/></svg>`;
5069
- /**
5070
- * Folder with up arrow. Angled line style for navigating up one level.
5071
- * fill="currentColor" so it inherits CSS color.
5072
- */
5073
- const ICON_FOLDER_UP = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 4L4 12h5v8h6v-8h5L12 4z"/></svg>`;
5074
- /**
5075
- * Folder. Useful for file explorers and sidebars.
5076
- * fill="currentColor" so it inherits CSS color.
5077
- */
5078
- const ICON_FOLDER = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 1.99-.9 1.99-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>`;
5079
- /**
5080
- * Document / file page. Useful for file lists and document tabs.
5081
- * fill="currentColor" so it inherits CSS color.
5473
+ * Chevron pointing right (Lucide `chevron-right`). Used for tree-basic expand
5474
+ * indicator as a CSS mask — shape only, color comes from background-color.
5082
5475
  */
5083
- const ICON_FILE = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>`;
5084
- /**
5085
- * Magnifying glass. Useful for search panels and inputs.
5086
- * fill="currentColor" so it inherits CSS color.
5087
- */
5088
- const ICON_SEARCH = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>`;
5476
+ const ICON_CARET_RIGHT = `<svg viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="m9 18 6-6-6-6"/></svg>`;
5477
+ /** Checkmark (Lucide `check`). Useful for menu-check-item or confirmation states. */
5478
+ const ICON_CHECK = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>`;
5479
+ /** Close / X icon (Lucide `x`). Useful for dialogs, notifications, dismissible elements. */
5480
+ const ICON_CLOSE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`;
5481
+ /** Plus / add icon (Lucide `plus`). Pairs with `close` for add/remove actions. */
5482
+ const ICON_ADD = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg>`;
5483
+ /** Error icon (Lucide `circle-alert`). Useful for error notifications. */
5484
+ const ICON_ERROR = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>`;
5485
+ /** Info icon (Lucide `info`). Useful for info notifications. */
5486
+ const ICON_INFO = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>`;
5487
+ /** Warning icon (Lucide `triangle-alert`). Useful for warning notifications. */
5488
+ const ICON_WARNING = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`;
5489
+ /** Chevron pointing right (Lucide `chevron-right`). Used for split-button switcher toggle. */
5490
+ const ICON_CHEVRON_RIGHT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>`;
5491
+ /** Folder with up arrow (Lucide `folder-up`). Angled line style for navigating up one level. */
5492
+ const ICON_FOLDER_UP = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 9V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-1"/><path d="M12 10v6"/><path d="m9 13 3-3 3 3"/></svg>`;
5493
+ /** Folder (Lucide `folder`). Useful for file explorers and sidebars. */
5494
+ const ICON_FOLDER = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>`;
5495
+ /** Document / file page (Lucide `file`). Useful for file lists and document tabs. */
5496
+ const ICON_FILE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>`;
5497
+ /** Magnifying glass (Lucide `search`). Useful for search panels and inputs. */
5498
+ const ICON_SEARCH = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>`;
5499
+ /** Chevron pointing up (Lucide `chevron-up`). Sort/collapse direction indicators. */
5500
+ const ICON_CHEVRON_UP = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m18 15-6-6-6 6"/></svg>`;
5501
+ /** Chevron pointing left (Lucide `chevron-left`). Previous/back affordances. */
5502
+ const ICON_CHEVRON_LEFT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg>`;
5503
+ /** Vertical ellipsis (Lucide `ellipsis-vertical`). Default glyph for an unlabelled menu trigger. */
5504
+ const ICON_ELLIPSIS_VERTICAL = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>`;
5089
5505
  function registerDefaults() {
5090
5506
  registerIcon(`chevron-down`, ICON_CHEVRON_DOWN);
5091
5507
  registerIcon(`chevron-right`, ICON_CHEVRON_RIGHT);
@@ -5100,6 +5516,9 @@ function registerDefaults() {
5100
5516
  registerIcon(`folder-up`, ICON_FOLDER_UP);
5101
5517
  registerIcon(`info`, ICON_INFO);
5102
5518
  registerIcon(`warning`, ICON_WARNING);
5519
+ registerIcon(`chevron-up`, ICON_CHEVRON_UP);
5520
+ registerIcon(`chevron-left`, ICON_CHEVRON_LEFT);
5521
+ registerIcon(`ellipsis-vertical`, ICON_ELLIPSIS_VERTICAL);
5103
5522
  }
5104
5523
  //#endregion
5105
5524
  //#region src/icons/index.ts
@@ -5707,14 +6126,12 @@ function fontOf(style) {
5707
6126
  } catch {}
5708
6127
  return `12px system-ui, sans-serif`;
5709
6128
  }
5710
- /** Caption for a drag entry — element labels win, then string items, then common object fields, then the key. */
6129
+ /** Caption for a drag entry — explicit element labels win, then data values, then generic element text, then the key. */
5711
6130
  function captionForEntry(entry) {
5712
6131
  const el = entry.element ?? (entry.item instanceof Element ? entry.item : void 0);
5713
6132
  if (el) {
5714
6133
  const labelled = el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.getAttribute(`data-search-label`) ?? el.querySelector(`.cell`)?.textContent?.trim();
5715
6134
  if (labelled) return labelled.slice(0, 80);
5716
- const text = el.textContent?.trim();
5717
- if (text) return text.slice(0, 80);
5718
6135
  }
5719
6136
  const item = entry.item;
5720
6137
  if (typeof item === `string` && item.length > 0) return item.slice(0, 80);
@@ -5730,6 +6147,10 @@ function captionForEntry(entry) {
5730
6147
  if (typeof value === `string` && value.length > 0) return value.slice(0, 80);
5731
6148
  }
5732
6149
  }
6150
+ if (el) {
6151
+ const text = el.textContent?.trim();
6152
+ if (text) return text.slice(0, 80);
6153
+ }
5733
6154
  return entry.key;
5734
6155
  }
5735
6156
  function glyphFor(caption) {
@@ -7373,7 +7794,7 @@ let MenuTrigger = class MenuTrigger extends i {
7373
7794
  constructor(..._args) {
7374
7795
  super(..._args);
7375
7796
  this.mode = `trigger`;
7376
- this.label = `⋮`;
7797
+ this.label = ``;
7377
7798
  this.iconName = ``;
7378
7799
  this.placement = `bottom-start`;
7379
7800
  this.tabStop = true;
@@ -7660,7 +8081,7 @@ let MenuTrigger = class MenuTrigger extends i {
7660
8081
  @click=${this._handleTriggerClick}
7661
8082
  @contextmenu=${this._handleContextMenu}
7662
8083
  >
7663
- ${showButton ? b`<button class="trigger-btn" part="base" tabindex=${this.tabStop ? `0` : `-1`}>${this.iconName ? b`<ixfx-icon name=${this.iconName}></ixfx-icon>` : this.label}</button>` : null}
8084
+ ${showButton ? b`<button class="trigger-btn" part="base" tabindex=${this.tabStop ? `0` : `-1`}>${this.iconName ? b`<ixfx-icon name=${this.iconName}></ixfx-icon>` : this.label || b`<ixfx-icon name="ellipsis-vertical"></ixfx-icon>`}</button>` : null}
7664
8085
  <slot name="trigger" @slotchange=${this._handleTriggerSlotChange}></slot>
7665
8086
  </div>
7666
8087
  <div
@@ -10202,8 +10623,21 @@ var TreeDataModel = class {
10202
10623
  * Apply the returned `.scroll-fade-x` / `.scroll-fade-y` class to
10203
10624
  * whichever element actually has `overflow` scrolling on that axis.
10204
10625
  *
10626
+ * NOTE: apply only one axis class per element. Each class sets
10627
+ * `mask-image`, so putting both on the same element makes the second
10628
+ * overwrite the first. An element that scrolls on both axes at once (e.g.
10629
+ * a grid viewport with `overflow: auto`) should use the vertical fade on
10630
+ * the viewport itself and handle the horizontal axis separately (e.g. a
10631
+ * synced header strip with its own `scroll-fade-x`).
10632
+ *
10205
10633
  * CSS variables:
10206
10634
  * - `--scroll-fade-width` (default `30px`): how far the fade extends from each edge.
10635
+ * - `--scroll-fade-strength` (default `1`): how completely content fades out
10636
+ * at the edge, `0`–`1`. `1` fades to fully transparent (current behaviour);
10637
+ * `0` disables the fade entirely. Dense layouts (e.g. a data grid) typically
10638
+ * want something like `0.7`–`0.8` so the edge row stays partly readable.
10639
+ * Deliberately *not* `@property`-registered, so it inherits normally and a
10640
+ * consumer override on the host reaches the shadow-DOM scroller.
10207
10641
  *
10208
10642
  * ```ts
10209
10643
  * import { scrollFadeStyles } from '../styles/scroll-fade.js';
@@ -10246,9 +10680,9 @@ function scrollFadeStyles(axis) {
10246
10680
  animation: ${r$2(keyframesName)};
10247
10681
  animation-timeline: ${r$2(timeline)};
10248
10682
  mask-image: linear-gradient(${r$2(gradientDirection)},
10249
- transparent,
10683
+ rgb(0 0 0 / calc(1 - var(--scroll-fade-strength, 1))),
10250
10684
  black var(${r$2(start)}, 0px) calc(100% - var(${r$2(end)}, 0px)),
10251
- transparent
10685
+ rgb(0 0 0 / calc(1 - var(--scroll-fade-strength, 1)))
10252
10686
  );
10253
10687
  }
10254
10688
  `;
@@ -13321,7 +13755,8 @@ let MenuCheckItem = class MenuCheckItem extends i {
13321
13755
  .check {
13322
13756
  width: 1em;
13323
13757
  margin-left: var(--space-xs);
13324
- font-size: var(--text-m);
13758
+ display: inline-flex;
13759
+ align-items: center;
13325
13760
  color: var(--accent);
13326
13761
  }
13327
13762
 
@@ -13364,7 +13799,7 @@ let MenuCheckItem = class MenuCheckItem extends i {
13364
13799
  role="menuitemcheckbox"
13365
13800
  aria-checked="${this.checked}"
13366
13801
  >
13367
- <span class="check">${this.checked ? `✓` : ``}</span>
13802
+ <span class="check">${this.checked ? b`<ixfx-icon name="check" size="14"></ixfx-icon>` : ``}</span>
13368
13803
  <span class="icon-slot">${resolvedIcon ? b`<ixfx-icon name="${resolvedIcon}"></ixfx-icon>` : null}</span>
13369
13804
  <span class="label">${this._resolvedLabel()}</span>
13370
13805
  ${shortcut ? b`<span class="shortcut">${shortcut}</span>` : null}
@@ -14487,6 +14922,8 @@ let DataGridElement = class DataGridElement extends i {
14487
14922
  tickledItemStyles,
14488
14923
  listItemFadeKeyframes,
14489
14924
  listSearchStyles,
14925
+ scrollFadeStyles(`x`),
14926
+ scrollFadeStyles(`y`),
14490
14927
  i$3`
14491
14928
  :host {
14492
14929
  --data-grid-row-height: 28px;
@@ -14503,6 +14940,11 @@ let DataGridElement = class DataGridElement extends i {
14503
14940
  autoSizeColumn(). Read back in JS (_columnResizeAnimationMs) to know
14504
14941
  when the animation has finished, so keep it a plain time value. */
14505
14942
  --data-grid-column-resize-duration: 180ms;
14943
+ /* Dense 28px rows read badly under the global 30px full-strength fade,
14944
+ so default to a shorter, shallower edge hint. Consumers can override
14945
+ --scroll-fade-width / --scroll-fade-strength per instance. */
14946
+ --scroll-fade-width: 20px;
14947
+ --scroll-fade-strength: 0.8;
14506
14948
  position: relative;
14507
14949
  display: block;
14508
14950
  overflow: hidden;
@@ -14522,6 +14964,9 @@ let DataGridElement = class DataGridElement extends i {
14522
14964
  }
14523
14965
  .header-scroll {
14524
14966
  display: flex; flex: 1; overflow: hidden;
14967
+ /* Horizontal edge fade comes from the shared .scroll-fade-x class
14968
+ (see ../styles/scroll-fade.ts). Its scrollLeft is synced from the
14969
+ viewport in _onScroll, which drives its own scroll-timeline. */
14525
14970
  }
14526
14971
  .header-cell {
14527
14972
  display: flex; align-items: center; gap: var(--space-xs);
@@ -14540,7 +14985,7 @@ let DataGridElement = class DataGridElement extends i {
14540
14985
  .header-cell.width-animating, .cell.width-animating { transition: none; }
14541
14986
  }
14542
14987
  .header-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
14543
- .sort-icon { font-size: 10px; opacity: 0.7; }
14988
+ .sort-icon { display: inline-flex; align-items: center; opacity: 0.7; }
14544
14989
  .resize-handle {
14545
14990
  position: absolute; right: -4px; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 1;
14546
14991
  display: flex; align-items: stretch; justify-content: center;
@@ -14551,10 +14996,16 @@ let DataGridElement = class DataGridElement extends i {
14551
14996
  }
14552
14997
  .resize-handle:hover::before { background: var(--split-gutter-hover-color, var(--surface-6)); }
14553
14998
  .resize-handle[data-active]::before { background: var(--split-gutter-active-color, var(--accent)); }
14554
- .reorder-handle { cursor: grab; opacity: 0.5; display: inline-flex; align-items: center; }
14999
+ .reorder-handle { cursor: grab; opacity: 0; display: inline-flex; align-items: center; transition: opacity var(--transition, 0.15s ease-out); }
15000
+ .header-cell:hover .reorder-handle, .header-cell:focus-within .reorder-handle, .header-cell[drag-source] .reorder-handle { opacity: var(--ixfx-handle-opacity, 0.5); }
14555
15001
  .reorder-handle .ixfx-handle { --ixfx-handle-length: 14px; --ixfx-handle-thickness: var(--data-grid-reorder-handle-width, 10px); --ixfx-handle-dot-size: 3px; }
14556
15002
  .flyout-trigger { flex-shrink: 0; width: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; border-left: var(--border-subtle); background: var(--data-grid-header-bg); }
14557
15003
  .viewport { flex: 1; overflow: auto; position: relative; }
15004
+ /* Vertical edge fade from the shared .scroll-fade-y class
15005
+ (see ../styles/scroll-fade.ts). The mask lives on the viewport —
15006
+ below the header bar — so the top fade never covers the column
15007
+ headers. Reach/strength tune via --scroll-fade-width /
15008
+ --scroll-fade-strength (defaults set on :host above). */
14558
15009
  .spacer { position: relative; }
14559
15010
  .row.item {
14560
15011
  position: absolute; left: 0; right: 0; display: flex; align-items: stretch;
@@ -15596,7 +16047,7 @@ let DataGridElement = class DataGridElement extends i {
15596
16047
  return b`
15597
16048
  <div class="grid">
15598
16049
  <div class="header-bar">
15599
- <div class="header-scroll">
16050
+ <div class="header-scroll scroll-fade-x">
15600
16051
  ${showCheckbox ? b`<div class="header-cell checkbox-col"></div>` : A}
15601
16052
  ${repeat(this._visibleColumns, (c) => c.key, (col, idx) => b`
15602
16053
  <div class="header-cell ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
@@ -15606,7 +16057,7 @@ let DataGridElement = class DataGridElement extends i {
15606
16057
  @pointerdown=${(e) => this._beginHeaderDrag(e, col, idx)}
15607
16058
  ><span class="ixfx-handle" data-handle="dotted" data-shape="vertical" part="reorder-handle"></span></span>` : A}
15608
16059
  <span class="header-label">${col.label}</span>
15609
- ${this.sortState?.key === col.key ? b`<span class="sort-icon">${this.sortState.direction === `asc` ? `▲` : `▼`}</span>` : A}
16060
+ ${this.sortState?.key === col.key ? b`<span class="sort-icon"><ixfx-icon name=${this.sortState.direction === `asc` ? `chevron-up` : `chevron-down`} size="12"></ixfx-icon></span>` : A}
15610
16061
  ${col.resizable !== false ? b`<span class="resize-handle" ?data-active=${this._resizingColKey === col.key} @pointerdown=${(e) => this._onResizePointerDown(e, col)} @click=${(e) => e.stopPropagation()} @dblclick=${(e) => {
15611
16062
  e.preventDefault();
15612
16063
  e.stopPropagation();
@@ -15663,14 +16114,14 @@ let DataGridElement = class DataGridElement extends i {
15663
16114
  </ixfx-menu-container>
15664
16115
  </ixfx-menu-trigger>`}
15665
16116
  </div>
15666
- <div class="viewport" @scroll=${this._onScroll}>
16117
+ <div class="viewport scroll-fade-y" @scroll=${this._onScroll}>
15667
16118
  <div class="spacer" style="height:${this._flat.length * rh}px; width:${this._colWidthSum}px">
15668
16119
  ${repeat(visibleFlat, (f) => f.key, (entry, i) => {
15669
16120
  const absIdx = startRow + i;
15670
16121
  if (entry.type === `group`) {
15671
16122
  const gKey = entry.key.replace(/^__group__/, ``);
15672
16123
  const collapsed = this._collapsedGroups.has(gKey);
15673
- return b`<div class="group-row ${collapsed ? `collapsed` : ``}" style="top:${absIdx * rh}px; height:${rh}px" @click=${() => this._toggleGroup(gKey)}><span class="caret">▼</span><span>${entry.label}</span><span class="count">(${entry.count})</span><span class="divider"></span></div>`;
16124
+ return b`<div class="group-row ${collapsed ? `collapsed` : ``}" style="top:${absIdx * rh}px; height:${rh}px" @click=${() => this._toggleGroup(gKey)}><span class="caret"><ixfx-icon name="chevron-down" size="12"></ixfx-icon></span><span>${entry.label}</span><span class="count">(${entry.count})</span><span class="divider"></span></div>`;
15674
16125
  }
15675
16126
  const row = entry.row;
15676
16127
  const absIdx2 = entry.idx;
@@ -23331,6 +23782,16 @@ let GridListElement = class GridListElement extends i {
23331
23782
  });
23332
23783
  this.#scheduleSnap();
23333
23784
  };
23785
+ this.#onWheel = (e) => {
23786
+ if (this.orientation !== `row`) return;
23787
+ if (e.ctrlKey || e.metaKey) return;
23788
+ if (e.deltaY === 0) return;
23789
+ const vp = this.#viewportRef.value ?? e.currentTarget;
23790
+ if (!vp) return;
23791
+ if (vp.scrollWidth <= vp.clientWidth + 1) return;
23792
+ vp.scrollLeft += e.deltaX + e.deltaY;
23793
+ e.preventDefault();
23794
+ };
23334
23795
  this._onKeyDown = (e) => {
23335
23796
  if ((e.ctrlKey || e.metaKey) && e.key === `f`) return;
23336
23797
  if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
@@ -23784,6 +24245,10 @@ let GridListElement = class GridListElement extends i {
23784
24245
  #postDragSuppressTimer;
23785
24246
  #nativeDrag;
23786
24247
  /** Origin rule + display-ordered entry resolution for native drag-out (both modes). */
24248
+ #renderedCellForKey(key) {
24249
+ const selector = `.cell[data-key="${CSS.escape(key)}"]`;
24250
+ return this.renderRoot.querySelector(selector) ?? void 0;
24251
+ }
23787
24252
  #prepareNativeDrag(originKey) {
23788
24253
  const index = this.#source.indexOfKey(originKey);
23789
24254
  if (index < 0 || !this.#source.isSelectable(index)) return void 0;
@@ -23794,14 +24259,15 @@ let GridListElement = class GridListElement extends i {
23794
24259
  const entryIndex = this.#source.indexOfKey(key);
23795
24260
  if (entryIndex < 0) continue;
23796
24261
  const data = this.#source.resolveKeyToData(key);
23797
- const element = this.#source.resolveKeyToElement(key);
23798
- const item = data ?? element;
24262
+ const sourceElement = this.#source.resolveKeyToElement(key);
24263
+ const renderedElement = sourceElement ?? (this.#source instanceof DataGridListSource ? this.#renderedCellForKey(key) : void 0);
24264
+ const item = data ?? sourceElement;
23799
24265
  if (item === void 0) continue;
23800
24266
  selected.push({
23801
24267
  key,
23802
24268
  index: entryIndex,
23803
24269
  item,
23804
- ...element ? { element } : {}
24270
+ ...renderedElement ? { element: renderedElement } : {}
23805
24271
  });
23806
24272
  }
23807
24273
  const origin = selected.find((entry) => entry.key === originKey);
@@ -24508,6 +24974,7 @@ let GridListElement = class GridListElement extends i {
24508
24974
  ${ref(this.#viewportRef)}
24509
24975
  data-input-mode=${this.tickled.inputMode}
24510
24976
  @scroll=${this.#onScroll}
24977
+ @wheel=${this.#onWheel}
24511
24978
  >
24512
24979
  <div class="spacer" style=${spacerStyle}>
24513
24980
  ${this.#source instanceof DataGridListSource ? this.#renderDataCells(win) : this.#renderElementCells(win)}
@@ -24645,6 +25112,17 @@ let GridListElement = class GridListElement extends i {
24645
25112
  `;
24646
25113
  }
24647
25114
  #onScroll;
25115
+ /**
25116
+ * Translate vertical wheel deltas into horizontal scroll when the list runs
25117
+ * in `row` orientation. Without this the default browser behaviour needs
25118
+ * Shift+wheel or a physical horizontal wheel — neither of which most users
25119
+ * reach for on a single-row strip.
25120
+ *
25121
+ * Only kicks in for a plain vertical wheel over a viewport that can actually
25122
+ * scroll horizontally; native horizontal deltas (trackpad, Shift+wheel) are
25123
+ * left to the browser when there is no vertical component.
25124
+ */
25125
+ #onWheel;
24648
25126
  #cancelScrollRaf() {
24649
25127
  if (this.#scrollRaf !== 0) {
24650
25128
  cancelAnimationFrame(this.#scrollRaf);
@@ -25547,6 +26025,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
25547
26025
  this.itemDnd = `none`;
25548
26026
  this.dndExpandHoverMs = 500;
25549
26027
  this.reorderable = true;
26028
+ this.stickyHeaders = true;
25550
26029
  this.applyInternalDrop = true;
25551
26030
  this._expandedKeys = /* @__PURE__ */ new Set();
25552
26031
  this.tickled = new TickledController(this, {
@@ -25579,6 +26058,18 @@ let TreeBaseElement = class TreeBaseElement extends i {
25579
26058
  this.#onContainerScroll = () => {
25580
26059
  this.#rowsCache = void 0;
25581
26060
  };
26061
+ this.#onStickyScroll = () => {
26062
+ if (this.#stickyRaf !== 0) return;
26063
+ this.#stickyRaf = requestAnimationFrame(() => {
26064
+ this.#stickyRaf = 0;
26065
+ this.#updateStickyHeaders();
26066
+ });
26067
+ };
26068
+ this._stickyKeys = [];
26069
+ this.#stickyEntries = /* @__PURE__ */ new Map();
26070
+ this.#stickyDirty = true;
26071
+ this.#stickyRaf = 0;
26072
+ this.#stickyScrollBound = false;
25582
26073
  this.#onKeyDown = (event) => {
25583
26074
  if (this.search.isOpen) return;
25584
26075
  const nodes = this.#getAllNodes();
@@ -25682,7 +26173,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
25682
26173
  }
25683
26174
  };
25684
26175
  this.#onDblClick = (e) => {
25685
- const li = e.target.closest(`li`);
26176
+ const li = e.target.closest(`ul.tree-list li`);
25686
26177
  if (!li) return;
25687
26178
  const key = li.dataset.key;
25688
26179
  if (!key) return;
@@ -25744,6 +26235,157 @@ let TreeBaseElement = class TreeBaseElement extends i {
25744
26235
  /** Per-drag cache of measured rows; invalidated on scroll / update. */
25745
26236
  #rowsCache;
25746
26237
  #onContainerScroll;
26238
+ /** Bind the sticky scroll listener once the container exists; unbind on opt-out. */
26239
+ #syncStickyScroll() {
26240
+ const container = this.#container();
26241
+ if (this.stickyHeaders && container) {
26242
+ if (!this.#stickyScrollBound || this.#stickyScrollContainer !== container) {
26243
+ this.#stickyScrollContainer?.removeEventListener(`scroll`, this.#onStickyScroll);
26244
+ container.addEventListener(`scroll`, this.#onStickyScroll, { passive: true });
26245
+ this.#stickyScrollContainer = container;
26246
+ this.#stickyScrollBound = true;
26247
+ }
26248
+ } else if (this.#stickyScrollBound) {
26249
+ this.#stickyScrollContainer?.removeEventListener(`scroll`, this.#onStickyScroll);
26250
+ this.#stickyScrollContainer = void 0;
26251
+ this.#stickyScrollBound = false;
26252
+ }
26253
+ }
26254
+ #onStickyScroll;
26255
+ /** Real (non-sticky) row element for `key`, or `null`. */
26256
+ #stickyRowEl(key) {
26257
+ return this.renderRoot.querySelector(`ul.tree-list li[data-key="${CSS.escape(key)}"]`);
26258
+ }
26259
+ /**
26260
+ * Recompute which ancestors stick to the top: the ancestors of the first
26261
+ * row whose bottom clears the container top, minus any ancestor that is
26262
+ * itself still visible below the overlay. Synchronous — reads current
26263
+ * geometry, renders nothing, expands nothing.
26264
+ */
26265
+ #updateStickyHeaders() {
26266
+ this.#stickyDirty = false;
26267
+ const empty = /* @__PURE__ */ new Map();
26268
+ if (!this.stickyHeaders || !this.root) {
26269
+ this.#setStickyKeys([], empty);
26270
+ return;
26271
+ }
26272
+ const container = this.#container();
26273
+ if (!container) {
26274
+ this.#setStickyKeys([], empty);
26275
+ return;
26276
+ }
26277
+ const nodes = this.#getAllNodes();
26278
+ if (nodes.length === 0) {
26279
+ this.#setStickyKeys([], empty);
26280
+ return;
26281
+ }
26282
+ const parent = /* @__PURE__ */ new Map();
26283
+ const walk = (n) => {
26284
+ for (const child of n.children ?? []) {
26285
+ parent.set(child.item.key, n.item.key);
26286
+ walk(child);
26287
+ }
26288
+ };
26289
+ walk(this.root);
26290
+ const containerTop = container.getBoundingClientRect().top;
26291
+ const visibleThreshold = containerTop + (this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0);
26292
+ let topKey;
26293
+ for (const { node } of nodes) {
26294
+ const li = this.#stickyRowEl(node.item.key);
26295
+ if (!li || li.classList.contains(`leaving`)) continue;
26296
+ if (li.getBoundingClientRect().bottom > containerTop) {
26297
+ topKey = node.item.key;
26298
+ break;
26299
+ }
26300
+ }
26301
+ if (topKey === void 0) {
26302
+ this.#setStickyKeys([], empty);
26303
+ return;
26304
+ }
26305
+ const rootKey = this.root.item.key;
26306
+ const chain = [];
26307
+ let cursor = parent.get(topKey);
26308
+ while (cursor !== void 0 && cursor !== rootKey) {
26309
+ chain.unshift(cursor);
26310
+ cursor = parent.get(cursor);
26311
+ }
26312
+ const byKey = new Map(nodes.map((entry) => [entry.node.item.key, entry]));
26313
+ const entries = /* @__PURE__ */ new Map();
26314
+ const stuck = [];
26315
+ for (const key of chain) {
26316
+ const li = this.#stickyRowEl(key);
26317
+ if (!li || li.getBoundingClientRect().bottom > visibleThreshold) continue;
26318
+ const entry = byKey.get(key);
26319
+ if (!entry) continue;
26320
+ stuck.push(key);
26321
+ entries.set(key, entry);
26322
+ }
26323
+ this.#setStickyKeys(stuck, entries);
26324
+ }
26325
+ #setStickyKeys(keys, entries) {
26326
+ const prev = this._stickyKeys;
26327
+ if (prev.length === keys.length && prev.every((key, i) => key === keys[i])) return;
26328
+ this.#stickyEntries = entries;
26329
+ this._stickyKeys = keys;
26330
+ }
26331
+ #renderStickyHeaders() {
26332
+ if (!this.stickyHeaders || this._stickyKeys.length === 0) return A;
26333
+ return b`
26334
+ <div class="sticky-headers" part="sticky-headers">
26335
+ ${this._stickyKeys.map((key) => {
26336
+ const entry = this.#stickyEntries.get(key);
26337
+ if (!entry) return A;
26338
+ return this.#renderStickyHeader(entry.node, entry.depth);
26339
+ })}
26340
+ </div>
26341
+ `;
26342
+ }
26343
+ #renderStickyHeader(node, depth) {
26344
+ const selected = this._isSelected(node);
26345
+ const isBranch = node.item.isLeaf !== true;
26346
+ return b`
26347
+ <div
26348
+ class="item ${isBranch ? `branch` : `leaf`} sticky-header${selected ? ` selected` : ``}"
26349
+ part="sticky-header"
26350
+ role="button"
26351
+ tabindex="0"
26352
+ aria-label="Go to ${node.item.label}"
26353
+ data-key=${node.item.key}
26354
+ style=${`--depth: ${depth}`}
26355
+ @click=${(e) => this.#onStickyClick(e, node)}
26356
+ @keydown=${(e) => {
26357
+ if (e.key === `Enter` || e.key === ` `) {
26358
+ e.preventDefault();
26359
+ this.#activateSticky(node);
26360
+ }
26361
+ }}
26362
+ >${this.renderNodeContent(node, depth)}</div>
26363
+ `;
26364
+ }
26365
+ #onStickyClick(event, node) {
26366
+ if (this.#drop?.isDragging || this.#nativeDrag?.isDragging) return;
26367
+ if (node.item.isLeaf !== true && event.target instanceof Element && event.target.closest(`.tree-caret`)) {
26368
+ const depth = this.#stickyEntries.get(node.item.key)?.depth ?? 0;
26369
+ this.#toggleExpand(node, depth);
26370
+ this.#stickyDirty = true;
26371
+ this.requestUpdate();
26372
+ return;
26373
+ }
26374
+ this.#activateSticky(node);
26375
+ }
26376
+ #activateSticky(node) {
26377
+ if (this.#canSelect(node)) this.select(node);
26378
+ this.#scrollKeyIntoView(node.item.key);
26379
+ this.#stickyDirty = true;
26380
+ this.requestUpdate();
26381
+ }
26382
+ /** Visible `{ node, depth }` entries for `_stickyKeys` — depths drive the indent. */
26383
+ #stickyEntries;
26384
+ /** Set when expansion / data / filter changes may have altered the sticky set. Consumed in `updated()`. */
26385
+ #stickyDirty;
26386
+ #stickyRaf;
26387
+ #stickyScrollContainer;
26388
+ #stickyScrollBound;
25747
26389
  get nativeDrag() {
25748
26390
  return this.#nativeDrag;
25749
26391
  }
@@ -25767,6 +26409,8 @@ let TreeBaseElement = class TreeBaseElement extends i {
25767
26409
  willUpdate(changedProps) {
25768
26410
  super.willUpdate(changedProps);
25769
26411
  this.#diffVisibleNodes();
26412
+ if (changedProps.has(`root`) || changedProps.has(`model`) || changedProps.has(`filterPredicate`) || changedProps.has(`stickyHeaders`)) this.#stickyDirty = true;
26413
+ if (this.#stickyDirty) this.#updateStickyHeaders();
25770
26414
  }
25771
26415
  #diffVisibleNodes() {
25772
26416
  const nodes = this.root ? this.#getAllNodes() : [];
@@ -25866,6 +26510,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
25866
26510
  }
25867
26511
  updated(changedProps) {
25868
26512
  this.#rowsCache = void 0;
26513
+ this.#syncStickyScroll();
25869
26514
  if (changedProps.has(`root`) && this.root) {
25870
26515
  this.#abortPending();
25871
26516
  this._expandedKeys.clear();
@@ -25889,6 +26534,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
25889
26534
  } else if (event.type === `invalidate`) {
25890
26535
  if (this._expandedKeys.has(event.key)) {
25891
26536
  this._expandedKeys.delete(event.key);
26537
+ this.#stickyDirty = true;
25892
26538
  this.requestUpdate();
25893
26539
  }
25894
26540
  }
@@ -25931,7 +26577,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
25931
26577
  isEnabled: () => this.itemDnd !== `none` && this.#anyRowDraggable(),
25932
26578
  resolveEventToKey: (target) => {
25933
26579
  if (!(target instanceof Element)) return void 0;
25934
- return target.closest(`li[data-key]`)?.getAttribute(`data-key`) ?? void 0;
26580
+ return target.closest(`ul.tree-list li[data-key]`)?.getAttribute(`data-key`) ?? void 0;
25935
26581
  },
25936
26582
  prepareSelection: (originKey) => {
25937
26583
  const allNodes = this.#getAllNodes();
@@ -25946,7 +26592,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
25946
26592
  key: n.node.item.key,
25947
26593
  index: i,
25948
26594
  item: n.node,
25949
- element: this.shadowRoot?.querySelector(`li[data-key="${CSS.escape(n.node.item.key)}"]`) ?? void 0
26595
+ element: this.shadowRoot?.querySelector(`ul.tree-list li[data-key="${CSS.escape(n.node.item.key)}"]`) ?? void 0
25950
26596
  }));
25951
26597
  const origin = selected.find((e) => e.key === originKey) ?? {
25952
26598
  key: originKey,
@@ -26101,7 +26747,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
26101
26747
  if (this.#rowsCache) return this.#rowsCache;
26102
26748
  const rows = [];
26103
26749
  for (const { node, depth } of this.#getAllNodes()) {
26104
- const li = this.shadowRoot?.querySelector(`li[data-key="${CSS.escape(node.item.key)}"]`);
26750
+ const li = this.shadowRoot?.querySelector(`ul.tree-list li[data-key="${CSS.escape(node.item.key)}"]`);
26105
26751
  if (li) rows.push({
26106
26752
  key: node.item.key,
26107
26753
  node,
@@ -26223,6 +26869,15 @@ let TreeBaseElement = class TreeBaseElement extends i {
26223
26869
  this.#postDragSuppressTimer = void 0;
26224
26870
  }
26225
26871
  this.#container()?.removeEventListener(`scroll`, this.#onContainerScroll);
26872
+ if (this.#stickyScrollBound) {
26873
+ this.#stickyScrollContainer?.removeEventListener(`scroll`, this.#onStickyScroll);
26874
+ this.#stickyScrollContainer = void 0;
26875
+ this.#stickyScrollBound = false;
26876
+ }
26877
+ if (this.#stickyRaf !== 0) {
26878
+ cancelAnimationFrame(this.#stickyRaf);
26879
+ this.#stickyRaf = 0;
26880
+ }
26226
26881
  treeDragUnregister(this.#dragSourceId);
26227
26882
  if (this.#ownedRegistry) {
26228
26883
  this.#ownedRegistry.unregister(`select-all`);
@@ -26311,6 +26966,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
26311
26966
  }
26312
26967
  currentNode = found;
26313
26968
  }
26969
+ this.#stickyDirty = true;
26314
26970
  this.requestUpdate();
26315
26971
  this.#scrollKeyIntoView(keys.at(-1));
26316
26972
  return true;
@@ -26331,6 +26987,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
26331
26987
  this._expandedKeys.add(path[i]);
26332
26988
  }
26333
26989
  this.tickled.setTickled(key);
26990
+ this.#stickyDirty = true;
26334
26991
  this.requestUpdate();
26335
26992
  this.updateComplete.then(() => this.#scrollKeyIntoView(key));
26336
26993
  return true;
@@ -26566,7 +27223,9 @@ let TreeBaseElement = class TreeBaseElement extends i {
26566
27223
  this.requestUpdate();
26567
27224
  }
26568
27225
  #toggleExpand(node, depth) {
26569
- if (this._expandedKeys.has(node.item.key)) {
27226
+ const isExpanded = this._expandedKeys.has(node.item.key);
27227
+ this.#stickyDirty = true;
27228
+ if (isExpanded) {
26570
27229
  this._expandedKeys.delete(node.item.key);
26571
27230
  this.dispatchEvent(new CustomEvent(`collapse`, {
26572
27231
  detail: {
@@ -26659,6 +27318,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
26659
27318
  node.children = children;
26660
27319
  this.#applyExclusivity(node.item.key);
26661
27320
  this._expandedKeys.add(node.item.key);
27321
+ this.#stickyDirty = true;
26662
27322
  this.#pendingNode = void 0;
26663
27323
  this.#pendingController = void 0;
26664
27324
  this.dispatchEvent(new CustomEvent(`expand`, {
@@ -26699,14 +27359,16 @@ let TreeBaseElement = class TreeBaseElement extends i {
26699
27359
  }
26700
27360
  #scrollKeyIntoView(key) {
26701
27361
  requestAnimationFrame(() => {
26702
- const el = this.shadowRoot?.querySelector(`[data-key="${CSS.escape(key)}"]`);
26703
- if (!el) return;
27362
+ const container = this.#container();
27363
+ const el = this.shadowRoot?.querySelector(`ul.tree-list [data-key="${CSS.escape(key)}"]`);
27364
+ if (!el || !container) return;
26704
27365
  const elRect = el.getBoundingClientRect();
26705
- const hostRect = this.getBoundingClientRect();
26706
- const relativeTop = elRect.top - hostRect.top;
27366
+ const containerRect = container.getBoundingClientRect();
27367
+ const relativeTop = elRect.top - containerRect.top;
26707
27368
  const relativeBottom = relativeTop + elRect.height;
26708
- if (relativeTop < 0) this.scrollTop += relativeTop;
26709
- else if (relativeBottom > this.clientHeight) this.scrollTop += relativeBottom - this.clientHeight;
27369
+ const overlayHeight = this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0;
27370
+ if (relativeTop < overlayHeight) container.scrollTop += relativeTop - overlayHeight;
27371
+ else if (relativeBottom > container.clientHeight) container.scrollTop += relativeBottom - container.clientHeight;
26710
27372
  });
26711
27373
  }
26712
27374
  #abortPending() {
@@ -26795,7 +27457,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
26795
27457
  #scrollTickledIntoView() {
26796
27458
  const tickledKey = this.tickled.tickledId;
26797
27459
  if (tickledKey === void 0) return;
26798
- (this.shadowRoot?.querySelector(`[data-key="${tickledKey}"]`))?.scrollIntoView({
27460
+ (this.shadowRoot?.querySelector(`ul.tree-list [data-key="${CSS.escape(tickledKey)}"]`))?.scrollIntoView({
26799
27461
  block: `nearest`,
26800
27462
  behavior: `smooth`
26801
27463
  });
@@ -26815,6 +27477,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
26815
27477
  @pointerleave=${(e) => this.tickled.handlePointerLeave(e)}
26816
27478
  tabindex="0"
26817
27479
  >
27480
+ ${this.#renderStickyHeaders()}
26818
27481
  ${this.root ? this.#renderNodes(nodes) : A}
26819
27482
  </div>
26820
27483
  ${this.search.renderField()}
@@ -26913,6 +27576,9 @@ let TreeBaseElement = class TreeBaseElement extends i {
26913
27576
  --tree-drop-nub-size: 5px;
26914
27577
  --tree-drop-into-outline: 2px solid var(--accent);
26915
27578
  --tree-drop-into-bg: color-mix(in srgb, var(--accent) 14%, transparent);
27579
+ --tree-sticky-bg: var(--tree-bg);
27580
+ --tree-sticky-border: var(--border, transparent);
27581
+ --tree-sticky-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
26916
27582
 
26917
27583
  position: relative;
26918
27584
  display: block;
@@ -26939,6 +27605,28 @@ let TreeBaseElement = class TreeBaseElement extends i {
26939
27605
  padding: var(--space-xs);
26940
27606
  }
26941
27607
 
27608
+ /* VS Code-style sticky headers: ancestors of the top-visible row, pinned
27609
+ to the top of the scroll container. Rows reuse the item class for
27610
+ identical metrics; the overlay itself is opaque so rows slide under it. */
27611
+ .sticky-headers {
27612
+ position: sticky;
27613
+ top: 0;
27614
+ z-index: 5;
27615
+ background: var(--tree-sticky-bg);
27616
+ border-bottom: 1px solid var(--tree-sticky-border);
27617
+ box-shadow: var(--tree-sticky-shadow);
27618
+ }
27619
+
27620
+ .sticky-header {
27621
+ margin: 0;
27622
+ border-radius: 0;
27623
+ }
27624
+
27625
+ .sticky-header:focus-visible {
27626
+ outline: 2px solid var(--accent);
27627
+ outline-offset: -2px;
27628
+ }
27629
+
26942
27630
  .item {
26943
27631
  position: relative; /* anchor for the .drop-line before/after indicator */
26944
27632
  display: flex;
@@ -27112,6 +27800,14 @@ __decorate([property({
27112
27800
  toAttribute: (value) => value ? null : `false`
27113
27801
  }
27114
27802
  })], TreeBaseElement.prototype, "reorderable", void 0);
27803
+ __decorate([property({
27804
+ reflect: true,
27805
+ attribute: `sticky-headers`,
27806
+ converter: {
27807
+ fromAttribute: (value) => value !== `false`,
27808
+ toAttribute: (value) => value ? null : `false`
27809
+ }
27810
+ })], TreeBaseElement.prototype, "stickyHeaders", void 0);
27115
27811
  __decorate([property({ attribute: false })], TreeBaseElement.prototype, "dragPreviewRenderer", void 0);
27116
27812
  __decorate([property({ attribute: false })], TreeBaseElement.prototype, "dragPreviewSource", void 0);
27117
27813
  __decorate([property({ attribute: false })], TreeBaseElement.prototype, "canDrag", void 0);
@@ -27120,6 +27816,7 @@ __decorate([property({ attribute: false })], TreeBaseElement.prototype, "parseEx
27120
27816
  __decorate([property({ attribute: false })], TreeBaseElement.prototype, "applyInternalDrop", void 0);
27121
27817
  __decorate([state()], TreeBaseElement.prototype, "_expandedKeys", void 0);
27122
27818
  __decorate([state()], TreeBaseElement.prototype, "_focusedDepth", void 0);
27819
+ __decorate([state()], TreeBaseElement.prototype, "_stickyKeys", void 0);
27123
27820
  TreeBaseElement = __decorate([safeCustomElement(`ixfx-tree-base`)], TreeBaseElement);
27124
27821
  //#endregion
27125
27822
  //#region src/tree/tree-list.ts
@@ -36152,6 +36849,973 @@ var MillerTreeController = class {
36152
36849
  }
36153
36850
  };
36154
36851
  //#endregion
36852
+ //#region src/multicol-list/types.ts
36853
+ const ALL_KEY = `__all__`;
36854
+ //#endregion
36855
+ //#region src/multicol-list/column.ts
36856
+ /** Sorts by label, honouring direction. 'All' lives outside the list, so it is not special-cased. */
36857
+ function sortItems(items, direction) {
36858
+ const dir = direction === `desc` ? -1 : 1;
36859
+ return [...items].sort((a, b) => dir * a.label.localeCompare(b.label, void 0, {
36860
+ sensitivity: `base`,
36861
+ numeric: true
36862
+ }));
36863
+ }
36864
+ /**
36865
+ * Nearest-gap math for header drag-reorder (per data-grid's `_beginHeaderDrag`).
36866
+ *
36867
+ * `offsets` is a prefix-sum of column widths (length = count + 1). `x` is the
36868
+ * pointer position in content space (already adjusted for scroll). Returns the
36869
+ * final insertion index once the source column is removed (`insert`), plus the
36870
+ * un-normalised gap offset for the drop caret (`caretOffset`).
36871
+ */
36872
+ function computeDropGap(offsets, x, count, from) {
36873
+ if (count === 0) return {
36874
+ insert: 0,
36875
+ caretOffset: 0
36876
+ };
36877
+ let bestDist = Number.MAX_SAFE_INTEGER;
36878
+ let caretGap = from;
36879
+ for (let i = 0; i < count; i++) {
36880
+ if (i === from) continue;
36881
+ const left = offsets[i];
36882
+ const right = offsets[i + 1] ?? offsets[offsets.length - 1];
36883
+ const mid = (left + right) / 2;
36884
+ const dist = Math.abs(x - (x < mid ? left : right));
36885
+ if (dist < bestDist) {
36886
+ bestDist = dist;
36887
+ caretGap = x < mid ? i : i + 1;
36888
+ }
36889
+ }
36890
+ const insert = caretGap > from ? caretGap - 1 : caretGap;
36891
+ return {
36892
+ insert: Math.max(0, Math.min(count - 1, insert)),
36893
+ caretOffset: offsets[caretGap] ?? offsets[offsets.length - 1]
36894
+ };
36895
+ }
36896
+ var MulticolColumnController = class {
36897
+ #category;
36898
+ #categoryDefaults;
36899
+ #selectionMode;
36900
+ #dataProvider;
36901
+ #onSelectionChange;
36902
+ #onRequestData;
36903
+ #abortController = void 0;
36904
+ #host;
36905
+ #allRow;
36906
+ #listerEl;
36907
+ #selectedValues = [];
36908
+ #allSelected = true;
36909
+ #lastItems = [];
36910
+ #syncingSelection = false;
36911
+ #state;
36912
+ constructor(options) {
36913
+ this.id = options.id;
36914
+ this.#category = options.category;
36915
+ this.#categoryDefaults = options.categoryDefaults;
36916
+ this.#selectionMode = options.selectionMode;
36917
+ this.#dataProvider = options.dataProvider;
36918
+ this.#onSelectionChange = options.onSelectionChange;
36919
+ this.#onRequestData = options.onRequestData;
36920
+ this.#state = {
36921
+ id: options.id,
36922
+ categoryKey: options.category.key,
36923
+ sort: options.categoryDefaults?.sort ?? `asc`,
36924
+ grouped: options.categoryDefaults?.grouped ?? false,
36925
+ width: 220
36926
+ };
36927
+ }
36928
+ get state() {
36929
+ return this.getState();
36930
+ }
36931
+ /** Always derived from the live category so a swap is visible everywhere. */
36932
+ get categoryKey() {
36933
+ return this.#category.key;
36934
+ }
36935
+ getState() {
36936
+ return { ...this.#state };
36937
+ }
36938
+ updateState(partial) {
36939
+ const sortChanged = partial.sort !== void 0 && partial.sort !== this.#state.sort;
36940
+ const groupedChanged = partial.grouped !== void 0 && partial.grouped !== this.#state.grouped;
36941
+ this.#state = {
36942
+ ...this.#state,
36943
+ ...partial
36944
+ };
36945
+ if (sortChanged) this.#repush();
36946
+ if (groupedChanged) this.#syncGrouping();
36947
+ }
36948
+ setWidth(width) {
36949
+ this.#state = {
36950
+ ...this.#state,
36951
+ width: Math.max(80, Math.min(480, Math.round(width)))
36952
+ };
36953
+ }
36954
+ setSort(sort) {
36955
+ if (sort === this.#state.sort) return;
36956
+ this.#state = {
36957
+ ...this.#state,
36958
+ sort
36959
+ };
36960
+ this.#repush();
36961
+ }
36962
+ setGrouped(grouped) {
36963
+ if (grouped === this.#state.grouped) return;
36964
+ this.#state = {
36965
+ ...this.#state,
36966
+ grouped
36967
+ };
36968
+ this.#syncGrouping();
36969
+ }
36970
+ setSelectionMode(mode) {
36971
+ if (mode === this.#selectionMode) return;
36972
+ this.#selectionMode = mode;
36973
+ for (const sub of this.#subComponents()) sub.selectionMode = mode;
36974
+ }
36975
+ setCategory(category) {
36976
+ this.#category = category;
36977
+ this.#categoryDefaults = category.defaults;
36978
+ this.#state = {
36979
+ ...this.#state,
36980
+ categoryKey: category.key,
36981
+ sort: category.defaults?.sort ?? `asc`,
36982
+ grouped: category.defaults?.grouped ?? false
36983
+ };
36984
+ this.clearSelection();
36985
+ this.#syncGrouping();
36986
+ }
36987
+ get listerHost() {
36988
+ return this.#host;
36989
+ }
36990
+ get isMounted() {
36991
+ return this.#host !== void 0;
36992
+ }
36993
+ setDataProvider(provider) {
36994
+ this.#dataProvider = provider;
36995
+ }
36996
+ mount(host) {
36997
+ if (this.#host) return;
36998
+ this.#host = host;
36999
+ const allRow = document.createElement(`div`);
37000
+ allRow.className = `all-row`;
37001
+ allRow.dataset.key = ALL_KEY;
37002
+ allRow.setAttribute(`role`, `button`);
37003
+ allRow.setAttribute(`tabindex`, `0`);
37004
+ const allLabel = document.createElement(`span`);
37005
+ allLabel.className = `all-label`;
37006
+ allRow.appendChild(allLabel);
37007
+ const applyAll = () => {
37008
+ if (this.#allSelected) return;
37009
+ this.#setAllSelection();
37010
+ };
37011
+ allRow.addEventListener(`click`, applyAll);
37012
+ allRow.addEventListener(`keydown`, (e) => {
37013
+ if (e.key === `Enter` || e.key === ` `) {
37014
+ e.preventDefault();
37015
+ applyAll();
37016
+ }
37017
+ });
37018
+ this.#allRow = allRow;
37019
+ const lister = document.createElement(`ixfx-grouped-item-lister`);
37020
+ lister.crossGroupSelection = true;
37021
+ lister.factory = () => createVerticalListAdapter(renderItem, {
37022
+ selectionMode: this.#selectionMode,
37023
+ interactionMode: `standard`
37024
+ });
37025
+ lister.addEventListener(`select`, this.#handleSelect);
37026
+ this.#listerEl = lister;
37027
+ this.#syncGrouping();
37028
+ this.#syncAllRow();
37029
+ host.append(allRow, lister);
37030
+ if (this.#lastItems.length > 0) {
37031
+ this.#pushItems();
37032
+ this.#restoreAfterSync();
37033
+ }
37034
+ }
37035
+ unmount() {
37036
+ this.#abortPending();
37037
+ this.#allRow?.remove();
37038
+ this.#listerEl?.remove();
37039
+ this.#allRow = void 0;
37040
+ this.#listerEl = void 0;
37041
+ this.#host = void 0;
37042
+ }
37043
+ requestData(query) {
37044
+ if (!this.#dataProvider) return;
37045
+ this.#abortPending();
37046
+ const controller = new AbortController();
37047
+ this.#abortController = controller;
37048
+ const { signal } = controller;
37049
+ this.#onRequestData(this.id, query, signal);
37050
+ Promise.resolve().then(async () => {
37051
+ const items = await collectDataResult(this.#dataProvider(query, signal), signal);
37052
+ if (signal.aborted) return;
37053
+ this.#applyItems(items);
37054
+ }).catch(() => {});
37055
+ }
37056
+ #applyItems(items) {
37057
+ this.#lastItems = items;
37058
+ const valid = this.#selectedValues.filter((v) => items.some((i) => i.key === v));
37059
+ const changed = valid.length !== this.#selectedValues.length;
37060
+ this.#selectedValues = valid;
37061
+ if (valid.length === 0) this.#allSelected = true;
37062
+ if (!this.#listerEl) {
37063
+ if (changed) this.#onSelectionChange(this.id, valid);
37064
+ return;
37065
+ }
37066
+ this.#pushItems();
37067
+ this.#restoreAfterSync();
37068
+ if (changed) this.#onSelectionChange(this.id, valid);
37069
+ }
37070
+ #pushItems() {
37071
+ if (!this.#listerEl) return;
37072
+ const sorted = sortItems(this.#lastItems, this.#state.sort);
37073
+ this.#listerEl.provider = () => sorted;
37074
+ }
37075
+ #repush() {
37076
+ if (!this.#listerEl || this.#lastItems.length === 0) return;
37077
+ this.#pushItems();
37078
+ this.#restoreAfterSync();
37079
+ }
37080
+ /** Grouping config from category defaults; grouping without groupBy is a no-op. */
37081
+ #syncGrouping() {
37082
+ if (!this.#listerEl) return;
37083
+ const groupBy = this.#categoryDefaults?.groupBy;
37084
+ this.#listerEl.groupBy = groupBy;
37085
+ this.#listerEl.grouped = this.#state.grouped && groupBy !== void 0;
37086
+ if (this.#lastItems.length > 0) this.#restoreAfterSync();
37087
+ }
37088
+ #subComponents() {
37089
+ if (!this.#listerEl) return [];
37090
+ return this.#listerEl.querySelectorAll(`:scope > .ungrouped-host > *, :scope > ixfx-panel > *`);
37091
+ }
37092
+ #handleSelect = () => {
37093
+ if (this.#syncingSelection) return;
37094
+ const values = [...this.#listerEl?.selectedItems ?? []].map((i) => i.key).filter((k) => k !== ALL_KEY);
37095
+ if (values.length === this.#selectedValues.length && values.every((v) => this.#selectedValues.includes(v))) {
37096
+ this.#syncAllRow();
37097
+ return;
37098
+ }
37099
+ this.#selectedValues = values;
37100
+ this.#allSelected = values.length === 0;
37101
+ this.#syncAllRow();
37102
+ this.#onSelectionChange(this.id, values);
37103
+ };
37104
+ #setAllSelection() {
37105
+ const previous = this.#selectedValues;
37106
+ this.#syncingSelection = true;
37107
+ try {
37108
+ this.#selectedValues = [];
37109
+ this.#allSelected = true;
37110
+ for (const sub of this.#subComponents()) sub.clearSelection();
37111
+ } finally {
37112
+ this.#syncingSelection = false;
37113
+ }
37114
+ this.#syncAllRow();
37115
+ if (previous.length > 0) this.#onSelectionChange(this.id, []);
37116
+ }
37117
+ clearSelection() {
37118
+ this.#setAllSelection();
37119
+ }
37120
+ get selectedValues() {
37121
+ return this.#selectedValues;
37122
+ }
37123
+ #restoreAfterSync() {
37124
+ if (!this.#listerEl) return;
37125
+ this.#listerEl.updateComplete.then(() => {
37126
+ if (!this.#listerEl || !this.#host) return;
37127
+ this.#syncingSelection = true;
37128
+ try {
37129
+ const wanted = this.#selectedValues;
37130
+ for (const list of this.#listerEl.querySelectorAll(`ixfx-vertical-list`)) list.selectKeys(wanted);
37131
+ const selected = [...this.#listerEl.selectedItems].map((i) => i.key).filter((k) => k !== ALL_KEY);
37132
+ this.#selectedValues = selected;
37133
+ this.#allSelected = selected.length === 0;
37134
+ } finally {
37135
+ this.#syncingSelection = false;
37136
+ }
37137
+ this.#syncAllRow();
37138
+ });
37139
+ }
37140
+ #syncAllRow() {
37141
+ if (!this.#allRow) return;
37142
+ const label = this.#categoryDefaults?.allLabel ?? `All`;
37143
+ const labelEl = this.#allRow.querySelector(`.all-label`);
37144
+ if (labelEl) labelEl.textContent = label;
37145
+ this.#allRow.toggleAttribute(`data-selected`, this.#allSelected);
37146
+ }
37147
+ #abortPending() {
37148
+ if (this.#abortController) {
37149
+ this.#abortController.abort();
37150
+ this.#abortController = void 0;
37151
+ }
37152
+ }
37153
+ };
37154
+ function renderItem(item) {
37155
+ const li = document.createElement(`li`);
37156
+ li.dataset.key = item.key;
37157
+ const labelSpan = document.createElement(`span`);
37158
+ labelSpan.className = `item-label`;
37159
+ labelSpan.textContent = item.label;
37160
+ li.appendChild(labelSpan);
37161
+ if (item.count !== void 0) {
37162
+ const countSpan = document.createElement(`span`);
37163
+ countSpan.className = `count`;
37164
+ countSpan.textContent = String(item.count);
37165
+ li.appendChild(countSpan);
37166
+ }
37167
+ return li;
37168
+ }
37169
+ //#endregion
37170
+ //#region src/multicol-list/multicol-list.ts
37171
+ registerIcon(`menu`, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16"/><path d="M4 18h16"/><path d="M4 6h16"/></svg>`);
37172
+ const DEFAULT_COLUMN_WIDTH = 220;
37173
+ const MIN_COLUMN_WIDTH = 80;
37174
+ const MAX_COLUMN_WIDTH = 480;
37175
+ const DRAG_THRESHOLD = 5;
37176
+ let MulticolListElement = class MulticolListElement extends i {
37177
+ constructor(..._args) {
37178
+ super(..._args);
37179
+ this.categories = [];
37180
+ this.selectionMode = `multiple`;
37181
+ this.minColumns = 1;
37182
+ this.maxColumns = 5;
37183
+ this._columns = [];
37184
+ this._colOffsets = [0];
37185
+ this._resizingColumnId = void 0;
37186
+ this._draggingColumnId = void 0;
37187
+ this._dropCaretX = void 0;
37188
+ this._idCounter = 0;
37189
+ this._autoSeeded = false;
37190
+ this._drag = {
37191
+ pointerId: -1,
37192
+ from: -1,
37193
+ startX: 0,
37194
+ startY: 0,
37195
+ active: false,
37196
+ ghost: null,
37197
+ insert: -1
37198
+ };
37199
+ }
37200
+ static {
37201
+ this.styles = [
37202
+ themeFallbacks,
37203
+ handleStyles,
37204
+ i$3`
37205
+ :host {
37206
+ --multicol-list-bg: var(--surface-3);
37207
+ --multicol-list-header-bg: var(--surface-2);
37208
+ --multicol-list-header-text: var(--surface-3-text);
37209
+ --multicol-list-border: var(--border);
37210
+ --multicol-list-min-column-width: ${MIN_COLUMN_WIDTH}px;
37211
+ --multicol-list-max-column-width: ${MAX_COLUMN_WIDTH}px;
37212
+
37213
+ display: block;
37214
+ overflow: hidden;
37215
+ background: var(--multicol-list-bg);
37216
+ color: var(--surface-3-text);
37217
+ font-family: var(--font-family);
37218
+ }
37219
+ #container {
37220
+ display: flex;
37221
+ flex-direction: row;
37222
+ align-items: stretch;
37223
+ overflow-x: auto;
37224
+ overflow-y: hidden;
37225
+ position: relative;
37226
+ height: 100%;
37227
+ }
37228
+ .column {
37229
+ flex-shrink: 0;
37230
+ display: flex;
37231
+ flex-direction: column;
37232
+ border-right: var(--multicol-list-border);
37233
+ min-width: var(--multicol-list-min-column-width);
37234
+ max-width: var(--multicol-list-max-column-width);
37235
+ background: var(--surface-3);
37236
+ }
37237
+ .column-header {
37238
+ display: flex;
37239
+ align-items: center;
37240
+ gap: var(--space-xs);
37241
+ height: 32px;
37242
+ background: var(--multicol-list-header-bg);
37243
+ color: var(--multicol-list-header-text);
37244
+ border-bottom: var(--border);
37245
+ position: relative;
37246
+ user-select: none;
37247
+ min-width: var(--multicol-list-min-column-width);
37248
+ box-sizing: border-box;
37249
+ }
37250
+ .column-header[data-dragging] {
37251
+ opacity: 0.4;
37252
+ }
37253
+ .reorder-handle {
37254
+ cursor: grab;
37255
+ opacity: 0;
37256
+ display: inline-flex;
37257
+ align-items: center;
37258
+ flex-shrink: 0;
37259
+ padding-left: var(--space-xs);
37260
+ touch-action: none;
37261
+ transition: opacity var(--transition, 0.15s ease-out);
37262
+ }
37263
+ .column-header:hover .reorder-handle, .column-header:focus-within .reorder-handle, .column-header[data-dragging] .reorder-handle { opacity: var(--ixfx-handle-opacity, 0.5); }
37264
+ .reorder-handle:active {
37265
+ cursor: grabbing;
37266
+ }
37267
+ .reorder-handle .ixfx-handle {
37268
+ --ixfx-handle-length: 14px;
37269
+ --ixfx-handle-thickness: var(--multicol-list-reorder-handle-width, 10px);
37270
+ --ixfx-handle-dot-size: 3px;
37271
+ }
37272
+ .column-label {
37273
+ flex: 1;
37274
+ overflow: hidden;
37275
+ text-overflow: ellipsis;
37276
+ white-space: nowrap;
37277
+ padding: 0 var(--space-s);
37278
+ font-size: var(--text-m);
37279
+ font-weight: 600;
37280
+ background: transparent;
37281
+ border: none;
37282
+ color: inherit;
37283
+ cursor: pointer;
37284
+ text-align: left;
37285
+ min-width: 0;
37286
+ }
37287
+ .column-label:hover {
37288
+ text-decoration: underline;
37289
+ }
37290
+ .burger-btn {
37291
+ flex-shrink: 0;
37292
+ width: 28px;
37293
+ height: 28px;
37294
+ display: flex;
37295
+ align-items: center;
37296
+ justify-content: center;
37297
+ background: transparent;
37298
+ border: none;
37299
+ cursor: pointer;
37300
+ color: var(--surface-3-text);
37301
+ opacity: 0;
37302
+ transition: opacity var(--transition);
37303
+ border-radius: var(--radius-s);
37304
+ }
37305
+ .column:hover .burger-btn,
37306
+ .column-header:focus-within .burger-btn {
37307
+ opacity: 1;
37308
+ }
37309
+ .burger-btn:hover {
37310
+ background: var(--surface-4);
37311
+ }
37312
+ .resize-handle {
37313
+ position: absolute;
37314
+ right: -4px;
37315
+ top: 0;
37316
+ bottom: 0;
37317
+ width: 8px;
37318
+ cursor: col-resize;
37319
+ z-index: 1;
37320
+ display: flex;
37321
+ align-items: stretch;
37322
+ justify-content: center;
37323
+ touch-action: none;
37324
+ }
37325
+ .resize-handle::before {
37326
+ content: '';
37327
+ width: 2px;
37328
+ margin: 2px 0;
37329
+ border-radius: 1px;
37330
+ background: transparent;
37331
+ transition: background-color var(--transition);
37332
+ }
37333
+ .resize-handle:hover::before,
37334
+ .resize-handle[data-active]::before {
37335
+ background: var(--accent);
37336
+ }
37337
+ .lister-host {
37338
+ flex: 1;
37339
+ overflow-y: auto;
37340
+ overflow-x: hidden;
37341
+ position: relative;
37342
+ display: flex;
37343
+ flex-direction: column;
37344
+ }
37345
+ .all-row {
37346
+ display: flex;
37347
+ align-items: center;
37348
+ padding: var(--space-xs) var(--space-s);
37349
+ cursor: pointer;
37350
+ user-select: none;
37351
+ min-height: 28px;
37352
+ box-sizing: border-box;
37353
+ outline: 1px solid transparent;
37354
+ background: var(--surface-3);
37355
+ }
37356
+ .all-row:hover,
37357
+ .all-row[data-selected] {
37358
+ background: var(--surface-5);
37359
+ }
37360
+ .all-row[data-selected] {
37361
+ outline-color: var(--accent);
37362
+ font-weight: 600;
37363
+ }
37364
+ ixfx-grouped-item-lister {
37365
+ display: block;
37366
+ }
37367
+ .drop-caret {
37368
+ position: absolute;
37369
+ top: 0;
37370
+ bottom: 0;
37371
+ width: 2px;
37372
+ background: var(--accent);
37373
+ border-radius: 1px;
37374
+ pointer-events: none;
37375
+ z-index: 10;
37376
+ }
37377
+ .item-label {
37378
+ flex: 1;
37379
+ }
37380
+ .count {
37381
+ opacity: 0.6;
37382
+ margin-left: var(--space-s);
37383
+ font-size: var(--text-s);
37384
+ }
37385
+ `
37386
+ ];
37387
+ }
37388
+ get columns() {
37389
+ return this._columns.map((c) => c.getState());
37390
+ }
37391
+ setColumns(states) {
37392
+ const existingMap = new Map(this._columns.map((c) => [c.id, c]));
37393
+ const newColumns = [];
37394
+ for (const colState of states) {
37395
+ const existing = existingMap.get(colState.id);
37396
+ if (existing) {
37397
+ if (existing.categoryKey !== colState.categoryKey) {
37398
+ const newCategory = this.categories.find((c) => c.key === colState.categoryKey);
37399
+ if (newCategory) existing.setCategory(newCategory);
37400
+ }
37401
+ existing.updateState(colState);
37402
+ newColumns.push(existing);
37403
+ existingMap.delete(colState.id);
37404
+ continue;
37405
+ }
37406
+ const category = this.categories.find((c) => c.key === colState.categoryKey);
37407
+ if (!category) continue;
37408
+ const controller = this.#createController(colState.id, category);
37409
+ controller.updateState(colState);
37410
+ newColumns.push(controller);
37411
+ }
37412
+ for (const col of existingMap.values()) col.unmount();
37413
+ this._columns = newColumns;
37414
+ this._updateColOffsets();
37415
+ this._emitColumnsChange(`add`);
37416
+ this._requeryAllColumns();
37417
+ this.requestUpdate();
37418
+ }
37419
+ addColumn(categoryKey, position) {
37420
+ if (this.maxColumns > 0 && this._columns.length >= this.maxColumns) return;
37421
+ const category = this.categories.find((c) => c.key === categoryKey);
37422
+ if (!category) return;
37423
+ const id = `col-${++this._idCounter}`;
37424
+ const controller = this.#createController(id, category);
37425
+ if (position !== void 0 && position >= 0 && position < this._columns.length) this._columns.splice(position, 0, controller);
37426
+ else this._columns.push(controller);
37427
+ this._updateColOffsets();
37428
+ this._emitColumnsChange(`add`);
37429
+ this._requeryAllColumns();
37430
+ this.requestUpdate();
37431
+ }
37432
+ removeColumn(columnId) {
37433
+ if (this._columns.length <= this.minColumns) return;
37434
+ const idx = this._columns.findIndex((c) => c.id === columnId);
37435
+ if (idx < 0) return;
37436
+ this._columns[idx].unmount();
37437
+ this._columns.splice(idx, 1);
37438
+ this._updateColOffsets();
37439
+ this._emitColumnsChange(`remove`);
37440
+ this._requeryAllColumns();
37441
+ this.requestUpdate();
37442
+ }
37443
+ getSelection() {
37444
+ return this._columns.map((c) => ({
37445
+ columnId: c.id,
37446
+ categoryKey: c.categoryKey,
37447
+ values: c.selectedValues
37448
+ }));
37449
+ }
37450
+ clearSelection() {
37451
+ for (const col of this._columns) col.clearSelection();
37452
+ this.requestUpdate();
37453
+ }
37454
+ render() {
37455
+ return b`
37456
+ <div id="container">
37457
+ ${repeat(this._columns, (col) => col.id, (col, idx) => this._renderColumn(col, idx))}
37458
+ ${this._dropCaretX !== void 0 ? b`<div class="drop-caret" style="left: ${this._dropCaretX}px"></div>` : A}
37459
+ </div>
37460
+ `;
37461
+ }
37462
+ updated(changedProperties) {
37463
+ if (!this._autoSeeded && this._columns.length === 0 && this.categories.length > 0) {
37464
+ this._autoSeeded = true;
37465
+ this.addColumn(this.categories[0].key);
37466
+ }
37467
+ if (changedProperties.has(`selectionMode`)) for (const col of this._columns) col.setSelectionMode(this.selectionMode);
37468
+ if (changedProperties.has(`dataProvider`)) for (const col of this._columns) col.setDataProvider(this.dataProvider);
37469
+ for (const col of this._columns) if (!col.isMounted) {
37470
+ const hostEl = this.shadowRoot?.querySelector(`[data-col-id="${col.id}"] .lister-host`);
37471
+ if (hostEl) col.mount(hostEl);
37472
+ }
37473
+ if (changedProperties.has(`categories`)) this._syncColumnsWithCategories();
37474
+ if (changedProperties.has(`categories`) || changedProperties.has(`dataProvider`)) this._requeryAllColumns();
37475
+ this._updateColOffsets();
37476
+ }
37477
+ _renderColumn(col, idx) {
37478
+ const width = col.getState().width ?? DEFAULT_COLUMN_WIDTH;
37479
+ return b`
37480
+ <div
37481
+ class="column"
37482
+ data-col-id="${col.id}"
37483
+ style="width: ${width}px"
37484
+ >
37485
+ <div
37486
+ class="column-header"
37487
+ ?data-dragging=${this._draggingColumnId === col.id}
37488
+ data-col-idx=${idx}
37489
+ >
37490
+ <span
37491
+ class="reorder-handle"
37492
+ data-tooltip="Drag to reorder"
37493
+ @pointerdown=${(e) => this._onHeaderPointerDown(e, col, idx)}
37494
+ ><span class="ixfx-handle" data-handle="dotted" data-shape="vertical" part="reorder-handle"></span></span>
37495
+ <button
37496
+ class="column-label"
37497
+ data-tooltip="Change category"
37498
+ @click=${(e) => {
37499
+ e.stopPropagation();
37500
+ this._openCategoryPicker(col, e.currentTarget);
37501
+ }}
37502
+ >${this._getCategoryLabel(col.categoryKey)}</button>
37503
+ <button
37504
+ class="burger-btn"
37505
+ data-tooltip="Column options"
37506
+ @click=${(e) => {
37507
+ e.stopPropagation();
37508
+ this._openBurgerMenu(col, e.currentTarget);
37509
+ }}
37510
+ >
37511
+ <ixfx-icon name="menu" size="14"></ixfx-icon>
37512
+ </button>
37513
+ <span
37514
+ class="resize-handle"
37515
+ ?data-active=${this._resizingColumnId === col.id}
37516
+ @pointerdown=${(e) => this._onResizePointerDown(e, col)}
37517
+ @click=${(e) => e.stopPropagation()}
37518
+ ></span>
37519
+ </div>
37520
+ <div class="lister-host"></div>
37521
+ </div>
37522
+ `;
37523
+ }
37524
+ _getCategoryLabel(key) {
37525
+ return this.categories.find((c) => c.key === key)?.label ?? key;
37526
+ }
37527
+ #createController(id, category) {
37528
+ return new MulticolColumnController({
37529
+ id,
37530
+ category,
37531
+ categoryDefaults: category.defaults,
37532
+ selectionMode: this.selectionMode,
37533
+ dataProvider: this.dataProvider,
37534
+ onSelectionChange: this._onColumnSelectionChange.bind(this),
37535
+ onRequestData: this._onColumnRequestData.bind(this)
37536
+ });
37537
+ }
37538
+ _onHeaderPointerDown(e, col, idx) {
37539
+ e.stopPropagation();
37540
+ this._drag = {
37541
+ pointerId: e.pointerId,
37542
+ from: idx,
37543
+ startX: e.clientX,
37544
+ startY: e.clientY,
37545
+ active: false,
37546
+ ghost: null,
37547
+ insert: idx
37548
+ };
37549
+ const onMove = (ev) => {
37550
+ if (ev.pointerId !== this._drag.pointerId) return;
37551
+ const dx = ev.clientX - this._drag.startX;
37552
+ const dy = ev.clientY - this._drag.startY;
37553
+ if (!this._drag.active) {
37554
+ if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
37555
+ this._drag.active = true;
37556
+ this._draggingColumnId = col.id;
37557
+ const headerEl = this.shadowRoot?.querySelector(`[data-col-id="${col.id}"] .column-header`);
37558
+ const content = document.createElement(`span`);
37559
+ content.textContent = this._getCategoryLabel(col.categoryKey);
37560
+ try {
37561
+ if (headerEl) {
37562
+ this._drag.ghost = createDragGhost({
37563
+ anchor: headerEl,
37564
+ content
37565
+ });
37566
+ this._drag.ghost.move(ev.clientX, ev.clientY);
37567
+ }
37568
+ } catch {}
37569
+ try {
37570
+ this._containerEl?.setPointerCapture(ev.pointerId);
37571
+ } catch {}
37572
+ }
37573
+ this._drag.ghost?.move(ev.clientX, ev.clientY);
37574
+ const containerEl = this._containerEl;
37575
+ if (!containerEl) return;
37576
+ const rect = containerEl.getBoundingClientRect();
37577
+ const x = ev.clientX - rect.left + containerEl.scrollLeft;
37578
+ const gap = computeDropGap(this._colOffsets, x, this._columns.length, this._drag.from);
37579
+ this._drag.insert = gap.insert;
37580
+ this._dropCaretX = gap.caretOffset;
37581
+ this.requestUpdate();
37582
+ };
37583
+ const finish = (ev) => {
37584
+ if (ev.pointerId !== this._drag.pointerId) return;
37585
+ const wasActive = this._drag.active;
37586
+ const from = this._drag.from;
37587
+ const insert = this._drag.insert;
37588
+ this._drag.ghost?.destroy();
37589
+ this._drag.ghost = null;
37590
+ this._draggingColumnId = void 0;
37591
+ this._dropCaretX = void 0;
37592
+ try {
37593
+ this._containerEl?.releasePointerCapture(ev.pointerId);
37594
+ } catch {}
37595
+ window.removeEventListener(`pointermove`, onMove);
37596
+ window.removeEventListener(`pointerup`, finish);
37597
+ window.removeEventListener(`pointercancel`, finish);
37598
+ if (wasActive && insert !== from) {
37599
+ const [moved] = this._columns.splice(from, 1);
37600
+ this._columns.splice(insert, 0, moved);
37601
+ this._updateColOffsets();
37602
+ this._emitColumnsChange(`reorder`);
37603
+ this.requestUpdate();
37604
+ }
37605
+ };
37606
+ window.addEventListener(`pointermove`, onMove);
37607
+ window.addEventListener(`pointerup`, finish);
37608
+ window.addEventListener(`pointercancel`, finish);
37609
+ }
37610
+ _onResizePointerDown(e, col) {
37611
+ e.preventDefault();
37612
+ e.stopPropagation();
37613
+ const startX = e.clientX;
37614
+ const startW = col.getState().width ?? DEFAULT_COLUMN_WIDTH;
37615
+ this._resizingColumnId = col.id;
37616
+ const applyWidth = (clientX) => {
37617
+ const w = Math.max(MIN_COLUMN_WIDTH, Math.min(MAX_COLUMN_WIDTH, startW + clientX - startX));
37618
+ col.setWidth(w);
37619
+ return w;
37620
+ };
37621
+ const onMove = (ev) => {
37622
+ if (ev.pointerId !== e.pointerId) return;
37623
+ applyWidth(ev.clientX);
37624
+ this._updateColOffsets();
37625
+ this.requestUpdate();
37626
+ };
37627
+ const finish = (ev) => {
37628
+ window.removeEventListener(`pointermove`, onMove);
37629
+ window.removeEventListener(`pointerup`, finish);
37630
+ window.removeEventListener(`pointercancel`, finish);
37631
+ applyWidth(ev.type === `pointercancel` ? startX : ev.clientX);
37632
+ this._resizingColumnId = void 0;
37633
+ this._updateColOffsets();
37634
+ this._emitColumnsChange(`resize`);
37635
+ this.requestUpdate();
37636
+ };
37637
+ window.addEventListener(`pointermove`, onMove);
37638
+ window.addEventListener(`pointerup`, finish);
37639
+ window.addEventListener(`pointercancel`, finish);
37640
+ }
37641
+ /** Floats a dropdown menu trigger over `anchorEl` and opens it immediately. */
37642
+ #openMenuAt(anchorEl, placement, buildMenu) {
37643
+ const rect = anchorEl.getBoundingClientRect();
37644
+ const trigger = document.createElement(`ixfx-menu-trigger`);
37645
+ trigger.mode = `dropdown`;
37646
+ trigger.tabStop = false;
37647
+ trigger.placement = placement;
37648
+ trigger.style.position = `fixed`;
37649
+ trigger.style.left = `${rect.left}px`;
37650
+ trigger.style.top = `${rect.bottom}px`;
37651
+ const container = document.createElement(`ixfx-menu-container`);
37652
+ container.setAttribute(`slot`, `menu`);
37653
+ buildMenu(container);
37654
+ trigger.appendChild(container);
37655
+ trigger.addEventListener(`menu-trigger-close`, () => trigger.remove(), { once: true });
37656
+ document.body.appendChild(trigger);
37657
+ trigger.updateComplete.then(() => trigger.openMenu());
37658
+ }
37659
+ #categoryToNode(cat) {
37660
+ return {
37661
+ item: {
37662
+ key: cat.key,
37663
+ label: cat.label
37664
+ },
37665
+ children: cat.children && cat.children.length > 0 ? cat.children.map((c) => this.#categoryToNode(c)) : void 0
37666
+ };
37667
+ }
37668
+ _openCategoryPicker(col, anchorEl) {
37669
+ this.#openMenuAt(anchorEl, `bottom-start`, (container) => {
37670
+ const items = menuFromTreeToElements(this.categories.map((c) => this.#categoryToNode(c)), { onCommand: (detail) => {
37671
+ if (detail.command === void 0) return;
37672
+ this._changeColumnCategory(col, detail.command);
37673
+ } });
37674
+ for (const el of items) container.appendChild(el);
37675
+ for (const item of container.querySelectorAll(`ixfx-menu-item`)) if (item.getAttribute(`command`) === col.categoryKey) item.setAttribute(`disabled`, ``);
37676
+ });
37677
+ }
37678
+ #menuItem(label, command, onCommand, iconName) {
37679
+ const el = document.createElement(`ixfx-menu-item`);
37680
+ el.setAttribute(`command`, command);
37681
+ el.setAttribute(`label`, label);
37682
+ if (iconName) el.setAttribute(`icon-name`, iconName);
37683
+ if (onCommand) el.addEventListener(`command`, () => onCommand());
37684
+ return el;
37685
+ }
37686
+ _openBurgerMenu(col, anchorEl) {
37687
+ this.#openMenuAt(anchorEl, `bottom-end`, (container) => {
37688
+ const removeItem = this.#menuItem(`Remove this column`, `remove`, () => this.removeColumn(col.id));
37689
+ removeItem.toggleAttribute(`disabled`, this._columns.length <= this.minColumns);
37690
+ container.appendChild(removeItem);
37691
+ const separator = document.createElement(`ixfx-menu-separator`);
37692
+ container.appendChild(separator);
37693
+ const atMax = this.maxColumns > 0 && this._columns.length >= this.maxColumns;
37694
+ const usedCats = new Set(this._columns.map((c) => c.categoryKey));
37695
+ const unused = this.categories.filter((c) => !usedCats.has(c.key));
37696
+ const canAdd = !atMax && unused.length > 0;
37697
+ const addSub = document.createElement(`ixfx-menu-sub-trigger`);
37698
+ addSub.setAttribute(`label`, `Add column`);
37699
+ const addContainer = document.createElement(`ixfx-menu-container`);
37700
+ addContainer.setAttribute(`slot`, `submenu`);
37701
+ const idx = this._columns.findIndex((c) => c.id === col.id);
37702
+ for (const [label, offset] of [[`Left`, -1], [`Right`, 1]]) {
37703
+ const item = this.#menuItem(label, `add-${label.toLowerCase()}`, () => {
37704
+ this.#insertColumn(unused[0].key, Math.max(0, idx + Math.max(0, offset)));
37705
+ });
37706
+ item.toggleAttribute(`disabled`, !canAdd);
37707
+ addContainer.appendChild(item);
37708
+ }
37709
+ addSub.appendChild(addContainer);
37710
+ container.appendChild(addSub);
37711
+ const sortSub = document.createElement(`ixfx-menu-sub-trigger`);
37712
+ sortSub.setAttribute(`label`, `Sort`);
37713
+ const sortContainer = document.createElement(`ixfx-menu-container`);
37714
+ sortContainer.setAttribute(`slot`, `submenu`);
37715
+ sortContainer.appendChild(this.#menuItem(`Ascending`, `sort-asc`, () => {
37716
+ col.setSort(`asc`);
37717
+ this._emitColumnsChange(`sort`);
37718
+ this.requestUpdate();
37719
+ }, col.getState().sort === `asc` ? `check` : void 0));
37720
+ sortContainer.appendChild(this.#menuItem(`Descending`, `sort-desc`, () => {
37721
+ col.setSort(`desc`);
37722
+ this._emitColumnsChange(`sort`);
37723
+ this.requestUpdate();
37724
+ }, col.getState().sort === `desc` ? `check` : void 0));
37725
+ sortSub.appendChild(sortContainer);
37726
+ container.appendChild(sortSub);
37727
+ const grouped = col.getState().grouped;
37728
+ container.appendChild(this.#menuItem(grouped ? `Ungroup` : `Group`, `toggle-group`, () => {
37729
+ col.setGrouped(!grouped);
37730
+ this._emitColumnsChange(`grouping`);
37731
+ this.requestUpdate();
37732
+ }, grouped ? `check` : void 0));
37733
+ });
37734
+ }
37735
+ #insertColumn(categoryKey, position) {
37736
+ this.addColumn(categoryKey, position);
37737
+ }
37738
+ _changeColumnCategory(col, categoryKey) {
37739
+ const category = this.categories.find((c) => c.key === categoryKey);
37740
+ if (!category) return;
37741
+ col.setCategory(category);
37742
+ this._emitColumnsChange(`category`);
37743
+ this._requeryAllColumns();
37744
+ this.requestUpdate();
37745
+ }
37746
+ _onColumnSelectionChange(columnId, _values) {
37747
+ if (!this._columns.find((c) => c.id === columnId)) return;
37748
+ for (const otherCol of this._columns) if (otherCol.id !== columnId) otherCol.requestData(this._buildQuery(otherCol));
37749
+ const detail = {
37750
+ columnId,
37751
+ columns: this.getSelection()
37752
+ };
37753
+ this.dispatchEvent(new CustomEvent(`multicol-select`, {
37754
+ detail,
37755
+ bubbles: true,
37756
+ composed: true
37757
+ }));
37758
+ this.requestUpdate();
37759
+ }
37760
+ _onColumnRequestData(_columnId, _query, _signal) {}
37761
+ _buildQuery(col) {
37762
+ const otherSelections = /* @__PURE__ */ new Map();
37763
+ for (const c of this._columns) if (c.id !== col.id) otherSelections.set(c.id, c.selectedValues);
37764
+ return {
37765
+ columnId: col.id,
37766
+ categoryKey: col.categoryKey,
37767
+ otherSelections
37768
+ };
37769
+ }
37770
+ _requeryAllColumns() {
37771
+ for (const col of this._columns) col.requestData(this._buildQuery(col));
37772
+ }
37773
+ _syncColumnsWithCategories() {
37774
+ let changed = false;
37775
+ for (const col of this._columns) {
37776
+ if (this.categories.some((c) => c.key === col.categoryKey)) continue;
37777
+ if (this.categories.length === 0) continue;
37778
+ col.setCategory(this.categories[0]);
37779
+ changed = true;
37780
+ }
37781
+ if (changed) this._emitColumnsChange(`category`);
37782
+ }
37783
+ _updateColOffsets() {
37784
+ const offsets = [0];
37785
+ let sum = 0;
37786
+ for (const col of this._columns) {
37787
+ sum += col.getState().width ?? DEFAULT_COLUMN_WIDTH;
37788
+ offsets.push(sum);
37789
+ }
37790
+ if (!(offsets.length === this._colOffsets.length && offsets.every((v, i) => v === this._colOffsets[i]))) this._colOffsets = offsets;
37791
+ }
37792
+ _emitColumnsChange(reason) {
37793
+ this.dispatchEvent(new CustomEvent(`multicol-columns-change`, {
37794
+ detail: {
37795
+ columns: this.columns,
37796
+ reason
37797
+ },
37798
+ bubbles: true,
37799
+ composed: true
37800
+ }));
37801
+ }
37802
+ };
37803
+ __decorate([property({ attribute: false })], MulticolListElement.prototype, "categories", void 0);
37804
+ __decorate([property({ attribute: false })], MulticolListElement.prototype, "dataProvider", void 0);
37805
+ __decorate([property({
37806
+ type: String,
37807
+ attribute: `selection-mode`
37808
+ })], MulticolListElement.prototype, "selectionMode", void 0);
37809
+ __decorate([property({ type: Number })], MulticolListElement.prototype, "minColumns", void 0);
37810
+ __decorate([property({ type: Number })], MulticolListElement.prototype, "maxColumns", void 0);
37811
+ __decorate([state()], MulticolListElement.prototype, "_columns", void 0);
37812
+ __decorate([state()], MulticolListElement.prototype, "_colOffsets", void 0);
37813
+ __decorate([state()], MulticolListElement.prototype, "_resizingColumnId", void 0);
37814
+ __decorate([state()], MulticolListElement.prototype, "_draggingColumnId", void 0);
37815
+ __decorate([state()], MulticolListElement.prototype, "_dropCaretX", void 0);
37816
+ __decorate([query(`#container`)], MulticolListElement.prototype, "_containerEl", void 0);
37817
+ MulticolListElement = __decorate([customElement(`ixfx-multicol-list`)], MulticolListElement);
37818
+ //#endregion
36155
37819
  //#region src/notifier/notification-pill.ts
36156
37820
  let NotificationPillElement = class NotificationPillElement extends i {
36157
37821
  constructor(..._args) {
@@ -45856,12 +47520,25 @@ function squarifyInto(items, box) {
45856
47520
  }
45857
47521
  return out;
45858
47522
  }
47523
+ /** Clamp a raw cascade offset fraction to a sane range. */
47524
+ function normaliseOffset(raw) {
47525
+ return typeof raw === `number` && Number.isFinite(raw) && raw > 0 ? Math.min(raw, 1) : 0;
47526
+ }
45859
47527
  /**
45860
47528
  * Subdivide `box` among `weights` (same length as the child list) using
45861
47529
  * `method` at `relativeDepth`. Returns one absolute box per weight, in order.
45862
47530
  * Zero-share children get degenerate (zero-area) boxes at the box origin.
47531
+ * Degenerate (zero/negative-extent) containers also yield zero-area boxes
47532
+ * rather than `NaN` — this matters for cascaded layouts where deep insets
47533
+ * can consume the whole box.
45863
47534
  */
45864
47535
  function subdivide(box, weights, method, relativeDepth) {
47536
+ if (box.width <= 0 || box.height <= 0) return weights.map(() => ({
47537
+ x: box.x,
47538
+ y: box.y,
47539
+ width: 0,
47540
+ height: 0
47541
+ }));
45865
47542
  const total = weights.reduce((a, b) => a + b, 0);
45866
47543
  const shares = total > 0 ? weights.map((w) => w / total) : weights.map(() => 1 / weights.length);
45867
47544
  const dice = method === `dice` || method === `slice-dice` && relativeDepth % 2 === 0;
@@ -45928,16 +47605,24 @@ function subdivide(box, weights, method, relativeDepth) {
45928
47605
  * @param options `valueAccessor` (default equal weights), `maxDepth` (nesting
45929
47606
  * levels, default unlimited), `zoomKey` (visual root, default true root;
45930
47607
  * unknown keys fall back to the true root), `tilingMethod` (default
45931
- * `'squarify'`).
47608
+ * `'squarify'`), `presentation` (default `'nested'`) plus `cascadeOffsetX/Y`
47609
+ * (per-level shift as a viewport fraction, cascaded only).
45932
47610
  */
45933
47611
  function computeTreemapLayout(root, options = {}) {
45934
47612
  if (!root?.children?.length) return [];
45935
- const { maxDepth = Number.POSITIVE_INFINITY, valueAccessor = () => 1, zoomKey, tilingMethod = `squarify` } = options;
47613
+ const { maxDepth = Number.POSITIVE_INFINITY, valueAccessor = () => 1, zoomKey, tilingMethod = `squarify`, hideLeaves = false, presentation = `nested`, cascadeOffsetX: rawOx, cascadeOffsetY: rawOy } = options;
45936
47614
  if (maxDepth <= 0) return [];
47615
+ const cascaded = presentation === `cascaded`;
47616
+ const ox = cascaded ? normaliseOffset(rawOx) : 0;
47617
+ const oy = cascaded ? normaliseOffset(rawOy) : 0;
47618
+ const visibleChildren = (node) => {
47619
+ const children = node.children ?? [];
47620
+ return hideLeaves ? children.filter((n) => n.item.isLeaf !== true) : children;
47621
+ };
45937
47622
  const zoomNode = zoomKey === void 0 ? root : findTreemapNode(root, zoomKey) ?? root;
45938
47623
  const zoomPath = findTreemapPath(root, zoomNode.item.key);
45939
47624
  const zoomDepth = zoomPath ? zoomPath.length - 2 : -1;
45940
- const siblings = zoomNode.children ?? [];
47625
+ const siblings = visibleChildren(zoomNode);
45941
47626
  if (siblings.length === 0) return [];
45942
47627
  const rects = [];
45943
47628
  const layoutLevel = (nodes, box, depth, relativeDepth, parentKey) => {
@@ -45946,7 +47631,7 @@ function computeTreemapLayout(root, options = {}) {
45946
47631
  const setSize = nodes.length;
45947
47632
  nodes.forEach((node, index) => {
45948
47633
  const childBox = boxes[index];
45949
- const children = node.children ?? [];
47634
+ const children = visibleChildren(node);
45950
47635
  rects.push({
45951
47636
  node,
45952
47637
  depth,
@@ -45960,7 +47645,15 @@ function computeTreemapLayout(root, options = {}) {
45960
47645
  setSize,
45961
47646
  hasChildren: children.length > 0 || node.children === void 0 && node.item.isLeaf !== true
45962
47647
  });
45963
- if (children.length > 0) layoutLevel(children, childBox, depth + 1, relativeDepth + 1, node.item.key);
47648
+ if (children.length > 0) {
47649
+ const contentBox = ox <= 0 && oy <= 0 ? childBox : {
47650
+ x: childBox.x + ox,
47651
+ y: childBox.y + oy,
47652
+ width: Math.max(0, childBox.width - ox),
47653
+ height: Math.max(0, childBox.height - oy)
47654
+ };
47655
+ layoutLevel(children, contentBox, depth + 1, relativeDepth + 1, node.item.key);
47656
+ }
45964
47657
  });
45965
47658
  };
45966
47659
  layoutLevel(siblings, {
@@ -45980,9 +47673,14 @@ let TreemapListElement = class TreemapListElement extends i {
45980
47673
  this.selectionFilter = `all`;
45981
47674
  this.interactionMode = `standard`;
45982
47675
  this.tilingMethod = `squarify`;
47676
+ this.presentation = `nested`;
47677
+ this.cascadeOffset = 6;
45983
47678
  this.maxDepth = 4;
47679
+ this.hideLeaves = false;
45984
47680
  this.minLabelArea = 1600;
47681
+ this.minTileWidth = 24;
45985
47682
  this.padding = 4;
47683
+ this.minChildArea = 400;
45986
47684
  this.valueAccessor = () => 1;
45987
47685
  this._selectedNodes = /* @__PURE__ */ new Set();
45988
47686
  this.disableKeyboardNavigation = false;
@@ -46016,6 +47714,7 @@ let TreemapListElement = class TreemapListElement extends i {
46016
47714
  });
46017
47715
  this.#pendingKeys = /* @__PURE__ */ new Set();
46018
47716
  this.#aborters = /* @__PURE__ */ new Map();
47717
+ this.#wheelCooldownUntil = 0;
46019
47718
  }
46020
47719
  get selectedNodes() {
46021
47720
  return this._selectedNodes;
@@ -46032,17 +47731,43 @@ let TreemapListElement = class TreemapListElement extends i {
46032
47731
  #ownedRegistry;
46033
47732
  #unregisterSearchCommands;
46034
47733
  #resizeObserver;
47734
+ #pendingFlip;
47735
+ /** `performance.now()` timestamp before which further Cmd+wheel zoom steps are ignored. */
47736
+ #wheelCooldownUntil;
46035
47737
  get #effectiveRoot() {
46036
47738
  return this.model?.root ?? this.root;
46037
47739
  }
46038
47740
  #rects() {
47741
+ return this.#rectsForZoom(this._zoomKey);
47742
+ }
47743
+ #rectsForZoom(zoomKey) {
46039
47744
  return computeTreemapLayout(this.#effectiveRoot, {
46040
47745
  valueAccessor: this.valueAccessor,
46041
47746
  maxDepth: this.maxDepth,
46042
- zoomKey: this._zoomKey,
46043
- tilingMethod: this.tilingMethod
47747
+ zoomKey,
47748
+ tilingMethod: this.tilingMethod,
47749
+ hideLeaves: this.hideLeaves,
47750
+ presentation: this.presentation,
47751
+ ...this.#cascadeFractions()
46044
47752
  });
46045
47753
  }
47754
+ /**
47755
+ * Pixel `cascadeOffset` as viewport fractions for the current host size.
47756
+ * Returns `{}` when nested, unmeasured, or non-positive — layout then falls
47757
+ * back to classic nesting for that pass (the `ResizeObserver` re-renders
47758
+ * once measured).
47759
+ */
47760
+ #cascadeFractions() {
47761
+ if (this.presentation !== `cascaded`) return {};
47762
+ if (!Number.isFinite(this.cascadeOffset) || this.cascadeOffset <= 0) return {};
47763
+ const w = this.clientWidth || 0;
47764
+ const h = this.clientHeight || 0;
47765
+ if (w <= 0 || h <= 0) return {};
47766
+ return {
47767
+ cascadeOffsetX: this.cascadeOffset / w,
47768
+ cascadeOffsetY: this.cascadeOffset / h
47769
+ };
47770
+ }
46046
47771
  connectedCallback() {
46047
47772
  super.connectedCallback();
46048
47773
  this.setAttribute(`data-input-mode`, `mouse`);
@@ -46116,8 +47841,14 @@ let TreemapListElement = class TreemapListElement extends i {
46116
47841
  if (changedProps.has(`searchModes`)) this.#syncSearchCommands();
46117
47842
  if (this._zoomKey !== void 0 && !findTreemapNode(this.#effectiveRoot, this._zoomKey)) this._zoomKey = void 0;
46118
47843
  this.#ensureVisibleChildren();
47844
+ if (this.#pendingFlip) {
47845
+ const flip = this.#pendingFlip;
47846
+ this.#pendingFlip = void 0;
47847
+ this.#applyFlip(flip);
47848
+ }
46119
47849
  }
46120
47850
  #resetViewState() {
47851
+ this.#pendingFlip = void 0;
46121
47852
  this.#clearSelectionInternal();
46122
47853
  this.tickled.clearTickled();
46123
47854
  this._zoomKey = void 0;
@@ -46230,6 +47961,7 @@ let TreemapListElement = class TreemapListElement extends i {
46230
47961
  */
46231
47962
  zoomTo(node) {
46232
47963
  if (this._zoomKey === node.item.key) return;
47964
+ this.#pendingFlip = this.#planFlip(node.item.key);
46233
47965
  this._zoomKey = node.item.key;
46234
47966
  this.tickled.clearTickled();
46235
47967
  const depth = findTreemapPath(this.#effectiveRoot, node.item.key)?.length;
@@ -46258,6 +47990,7 @@ let TreemapListElement = class TreemapListElement extends i {
46258
47990
  */
46259
47991
  zoomOut() {
46260
47992
  if (this._zoomKey === void 0) return;
47993
+ this.#pendingFlip = this.#planFlip(void 0);
46261
47994
  const previous = this.focusedNode;
46262
47995
  const path = this._zoomKey === void 0 ? void 0 : findTreemapPath(this.#effectiveRoot, this._zoomKey);
46263
47996
  const absoluteDepth = path === void 0 ? -1 : path.length - 2;
@@ -46281,9 +48014,66 @@ let TreemapListElement = class TreemapListElement extends i {
46281
48014
  }));
46282
48015
  this.requestUpdate();
46283
48016
  }
48017
+ /**
48018
+ * Work out (before `_zoomKey` changes) whether the upcoming zoom should
48019
+ * animate, and how — see `FlipPlan`. Returns `undefined` to skip the
48020
+ * animation: reduced-motion is requested, or the node needed to anchor it
48021
+ * isn't in either the old or new layout's visible window.
48022
+ */
48023
+ #planFlip(targetKey) {
48024
+ if (typeof matchMedia === `function` && matchMedia(`(prefers-reduced-motion: reduce)`).matches) return void 0;
48025
+ if (targetKey !== void 0) {
48026
+ const visible = this.#rects().find((r) => r.node.item.key === targetKey);
48027
+ if (visible) return {
48028
+ direction: `in`,
48029
+ rect: {
48030
+ x: visible.x,
48031
+ y: visible.y,
48032
+ width: visible.width,
48033
+ height: visible.height
48034
+ }
48035
+ };
48036
+ }
48037
+ const fromKey = this._zoomKey;
48038
+ if (fromKey === void 0) return void 0;
48039
+ const landing = this.#rectsForZoom(targetKey).find((r) => r.node.item.key === fromKey);
48040
+ if (!landing) return void 0;
48041
+ return {
48042
+ direction: `out`,
48043
+ rect: {
48044
+ x: landing.x,
48045
+ y: landing.y,
48046
+ width: landing.width,
48047
+ height: landing.height
48048
+ }
48049
+ };
48050
+ }
48051
+ /** Apply a planned FLIP animation once the new layout has rendered — see `FlipPlan`. */
48052
+ #applyFlip(flip) {
48053
+ const inner = this.renderRoot.querySelector(`.viewport-inner`);
48054
+ const viewport = this.renderRoot.querySelector(`.viewport`);
48055
+ if (!(inner instanceof HTMLElement) || !(viewport instanceof HTMLElement)) return;
48056
+ const vw = viewport.clientWidth;
48057
+ const vh = viewport.clientHeight;
48058
+ if (vw <= 0 || vh <= 0) return;
48059
+ const { rect, direction } = flip;
48060
+ const startTransform = direction === `in` ? `translate(${rect.x * vw}px, ${rect.y * vh}px) scale(${Math.max(rect.width, 1e-4)}, ${Math.max(rect.height, 1e-4)})` : (() => {
48061
+ const sx = rect.width > 0 ? 1 / rect.width : 1;
48062
+ const sy = rect.height > 0 ? 1 / rect.height : 1;
48063
+ return `translate(${-rect.x * sx * vw}px, ${-rect.y * sy * vh}px) scale(${sx}, ${sy})`;
48064
+ })();
48065
+ inner.classList.remove(`flipping`);
48066
+ inner.style.transform = startTransform;
48067
+ inner.offsetWidth;
48068
+ requestAnimationFrame(() => {
48069
+ inner.classList.add(`flipping`);
48070
+ inner.style.transform = `none`;
48071
+ });
48072
+ inner.addEventListener(`transitionend`, () => inner.classList.remove(`flipping`), { once: true });
48073
+ }
46284
48074
  #canZoom(node) {
46285
48075
  if (node.item.isLeaf === true) return false;
46286
- if (node.children !== void 0) return node.children.length > 0;
48076
+ if (node.children !== void 0) return this.hideLeaves ? node.children.some((n) => n.item.isLeaf !== true) : node.children.length > 0;
46287
48077
  return this.model !== void 0 || this.loadChildren !== void 0;
46288
48078
  }
46289
48079
  /**
@@ -46510,6 +48300,39 @@ let TreemapListElement = class TreemapListElement extends i {
46510
48300
  }
46511
48301
  }
46512
48302
  }
48303
+ /**
48304
+ * Cmd+wheel zooms: scrolling backward (deltaY < 0, towards the user) zooms
48305
+ * into the tile under the pointer — the same as clicking it; scrolling
48306
+ * forward (deltaY > 0, away from the user) zooms out one level at a time
48307
+ * (see `#zoomOutOneStep`), not straight to the root. Plain wheel (no Cmd)
48308
+ * is left alone for normal page scroll. A short cooldown after each step
48309
+ * keeps a single trackpad gesture (which fires many small wheel events)
48310
+ * from firing a cascade of zoom steps.
48311
+ */
48312
+ #handleWheel(event) {
48313
+ if (!event.metaKey || event.deltaY === 0) return;
48314
+ event.preventDefault();
48315
+ if (performance.now() < this.#wheelCooldownUntil) return;
48316
+ if (event.deltaY > 0) {
48317
+ if (this._zoomKey === void 0) return;
48318
+ this.#wheelCooldownUntil = performance.now() + 260;
48319
+ this.#zoomOutOneStep();
48320
+ return;
48321
+ }
48322
+ const key = (event.target?.closest(`.tile`))?.getAttribute(`data-key`) ?? void 0;
48323
+ if (key === void 0) return;
48324
+ const rect = this.#rects().find((r) => r.node.item.key === key);
48325
+ if (!rect || !this.#canZoom(rect.node)) return;
48326
+ this.#wheelCooldownUntil = performance.now() + 260;
48327
+ this.zoomTo(rect.node);
48328
+ }
48329
+ /** Zoom to the current zoom node's parent, or fully out when it's a direct child of the root. */
48330
+ #zoomOutOneStep() {
48331
+ const path = this._zoomKey === void 0 ? void 0 : findTreemapPath(this.#effectiveRoot, this._zoomKey);
48332
+ const parent = path && path.length > 2 ? path[path.length - 2] : void 0;
48333
+ if (parent) this.zoomTo(parent);
48334
+ else this.zoomOut();
48335
+ }
46513
48336
  #renderBreadcrumb() {
46514
48337
  const zoomNode = this.focusedNode;
46515
48338
  if (zoomNode === void 0) return A;
@@ -46539,25 +48362,64 @@ let TreemapListElement = class TreemapListElement extends i {
46539
48362
  if (node.item.key === this.#effectiveRoot?.item.key) this.zoomOut();
46540
48363
  else this.zoomTo(node);
46541
48364
  }
46542
- #hideLabel(rect) {
48365
+ /** On-screen size (px) of `rect`, or `undefined` before the host has been measured. */
48366
+ #pixelSize(rect) {
46543
48367
  const hostWidth = this.clientWidth || 0;
46544
48368
  const hostHeight = this.clientHeight || 0;
46545
- if (hostWidth <= 0 || hostHeight <= 0) return false;
46546
- return rect.width * hostWidth * rect.height * hostHeight < this.minLabelArea;
48369
+ if (hostWidth <= 0 || hostHeight <= 0) return void 0;
48370
+ return {
48371
+ width: rect.width * hostWidth,
48372
+ height: rect.height * hostHeight
48373
+ };
46547
48374
  }
46548
- #renderTile(rect, positionStyle, children, byParent, nested) {
48375
+ /** Hide when too small by area, or too narrow to fit any readable text even truncated. */
48376
+ #hideLabel(rect) {
48377
+ const size = this.#pixelSize(rect);
48378
+ if (size === void 0) return false;
48379
+ return size.width * size.height < this.minLabelArea || size.width < this.minTileWidth;
48380
+ }
48381
+ /** Whether `rect` is large enough on-screen for its own children to be subdivided and drawn. */
48382
+ #canDescend(rect) {
48383
+ const size = this.#pixelSize(rect);
48384
+ if (size === void 0) return true;
48385
+ const offset = this.presentation === `cascaded` && Number.isFinite(this.cascadeOffset) && this.cascadeOffset > 0 ? this.cascadeOffset : 0;
48386
+ const w = size.width - offset;
48387
+ const h = size.height - offset;
48388
+ if (w <= 0 || h <= 0) return false;
48389
+ return w * h >= this.minChildArea && w >= this.minTileWidth;
48390
+ }
48391
+ /** Slugify a category name into a CSS custom-property-safe token. */
48392
+ #categorySlug(category) {
48393
+ return category.replace(/[^\w-]/g, `-`);
48394
+ }
48395
+ /**
48396
+ * Inline colour overrides for `node`'s category, or `undefined` for none.
48397
+ * Set as real `background-color`/`color` (not custom properties) so they
48398
+ * simply win over `.tile`'s CSS-variable-driven defaults by specificity —
48399
+ * no risk of an unresolvable `var()` chain leaving the property invalid.
48400
+ */
48401
+ #categoryStyle(node) {
48402
+ const category = this.categoryAccessor?.(node.item);
48403
+ if (!category) return void 0;
48404
+ const slug = this.#categorySlug(category);
48405
+ return `background-color:var(--treemap-category-${slug}-bg, var(--treemap-tile-bg));color:var(--treemap-category-${slug}-text, var(--treemap-tile-text));`;
48406
+ }
48407
+ #renderTileBox(rect, positionStyle, hasVisibleChildren, childrenContent) {
46549
48408
  const { node } = rect;
46550
48409
  const selected = this._selectedNodes.has(node);
46551
48410
  const tickled = this.tickled.isTickled(node.item.key);
46552
48411
  const filtered = !this.#matchesFilter(node, rect.depth);
46553
48412
  const pending = this.#pendingKeys.has(node.item.key);
46554
48413
  const hideLabel = this.#hideLabel(rect);
46555
- const showHeader = children.length > 0 && !hideLabel;
48414
+ const showHeader = hasVisibleChildren && !hideLabel;
48415
+ const category = this.categoryAccessor?.(node.item);
48416
+ const categoryStyle = this.#categoryStyle(node);
46556
48417
  return b`
46557
48418
  <div
46558
- class="item tile ${nested ? `nested` : ``} ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${filtered ? `filtered` : ``} ${pending ? `loading` : ``} ${rect.width <= 0 || rect.height <= 0 ? `empty` : ``}"
48419
+ class="item tile ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${filtered ? `filtered` : ``} ${pending ? `loading` : ``} ${rect.width <= 0 || rect.height <= 0 ? `empty` : ``}"
46559
48420
  data-key=${node.item.key}
46560
48421
  data-depth=${rect.relativeDepth}
48422
+ data-category=${category ?? A}
46561
48423
  role="treeitem"
46562
48424
  tabindex="-1"
46563
48425
  aria-level=${rect.depth + 1}
@@ -46567,7 +48429,7 @@ let TreemapListElement = class TreemapListElement extends i {
46567
48429
  aria-label=${node.item.label}
46568
48430
  title=${node.item.label}
46569
48431
  part="tile"
46570
- style=${positionStyle}
48432
+ style=${categoryStyle ? `${positionStyle};${categoryStyle}` : positionStyle}
46571
48433
  @click=${(e) => {
46572
48434
  e.stopPropagation();
46573
48435
  this.#handleItemClick(rect, this.#rects().findIndex((r) => r.node.item.key === node.item.key), e);
@@ -46579,23 +48441,46 @@ let TreemapListElement = class TreemapListElement extends i {
46579
48441
  @pointerenter=${() => this.tickled.handlePointerEnter(node.item.key)}
46580
48442
  >
46581
48443
  ${showHeader ? b`<div class="tile-header" part="tile-header"><span class="treemap-label" part="label">${node.item.label}</span></div>` : b`<span class="treemap-label ${hideLabel ? `hide-label` : ``}" part="label">${node.item.label}</span>`}
46582
- ${children.length > 0 ? b`
46583
- <div class="tile-children" role="group">
46584
- ${repeat(children, (r) => r.node.item.key, (r) => this.#renderNestedTile(r, rect, byParent))}
46585
- </div>
46586
- ` : A}
48444
+ ${childrenContent}
46587
48445
  </div>
46588
48446
  `;
46589
48447
  }
48448
+ /**
48449
+ * Render `rect` plus (if it's large enough to descend into — see
48450
+ * `#canDescend`) a `.tile-children` holding its own children, each
48451
+ * positioned relative to `rect` itself and recursing the same way.
48452
+ *
48453
+ * Each level gets its own real `.tile-header` + `.tile-children` box, so a
48454
+ * header reserves its own space via normal box-model layout (flex column:
48455
+ * header, then the remaining space for children) at *every* level — not
48456
+ * just the top one. That matters because layout rects are purely
48457
+ * geometric (no allowance for header height), so a folder whose one child
48458
+ * fills ~100% of its area gets a child rect nearly identical to its own;
48459
+ * positioning that child against its direct parent (not some flattened
48460
+ * shared ancestor) is what keeps the parent's header from being painted
48461
+ * over by an opaque, near-identically-sized child box.
48462
+ */
48463
+ #renderTile(rect, positionStyle, byParent) {
48464
+ const children = rect.hasChildren && this.#canDescend(rect) ? byParent.get(rect.node.item.key) ?? [] : [];
48465
+ const childrenContent = children.length > 0 ? b`
48466
+ <div class="tile-children" role="group">
48467
+ ${repeat(children, (r) => r.node.item.key, (r) => this.#renderNestedTile(r, rect, byParent))}
48468
+ </div>
48469
+ ` : A;
48470
+ return this.#renderTileBox(rect, positionStyle, children.length > 0, childrenContent);
48471
+ }
48472
+ /** A child tile, positioned as a percentage of `parent`'s own box. */
46590
48473
  #renderNestedTile(rect, parent, byParent) {
46591
48474
  const relX = parent.width > 0 ? (rect.x - parent.x) / parent.width : 0;
46592
48475
  const relY = parent.height > 0 ? (rect.y - parent.y) / parent.height : 0;
46593
48476
  const relW = parent.width > 0 ? rect.width / parent.width : 0;
46594
48477
  const relH = parent.height > 0 ? rect.height / parent.height : 0;
46595
- return this.#renderTile(rect, `left:${(relX * 100).toFixed(4)}%;top:${(relY * 100).toFixed(4)}%;width:${(relW * 100).toFixed(4)}%;height:${(relH * 100).toFixed(4)}%`, byParent.get(rect.node.item.key) ?? [], byParent, true);
48478
+ const style = `left:${(relX * 100).toFixed(4)}%;top:${(relY * 100).toFixed(4)}%;width:${(relW * 100).toFixed(4)}%;height:${(relH * 100).toFixed(4)}%`;
48479
+ return this.#renderTile(rect, style, byParent);
46596
48480
  }
46597
48481
  #renderTopTile(rect, byParent) {
46598
- return this.#renderTile(rect, `left:${(rect.x * 100).toFixed(4)}%;top:${(rect.y * 100).toFixed(4)}%;width:${(rect.width * 100).toFixed(4)}%;height:${(rect.height * 100).toFixed(4)}%`, byParent.get(rect.node.item.key) ?? [], byParent, false);
48482
+ const style = `left:${(rect.x * 100).toFixed(4)}%;top:${(rect.y * 100).toFixed(4)}%;width:${(rect.width * 100).toFixed(4)}%;height:${(rect.height * 100).toFixed(4)}%`;
48483
+ return this.#renderTile(rect, style, byParent);
46599
48484
  }
46600
48485
  render() {
46601
48486
  const rects = this.#rects();
@@ -46613,15 +48498,19 @@ let TreemapListElement = class TreemapListElement extends i {
46613
48498
  class="treemap"
46614
48499
  role="tree"
46615
48500
  tabindex="0"
48501
+ data-presentation=${this.presentation}
46616
48502
  data-input-mode=${this.tickled.inputMode}
46617
48503
  style=${`--treemap-padding:${this.padding}px`}
46618
48504
  @keydown=${(e) => this.#handleKeyDown(e)}
46619
48505
  @pointerleave=${() => this.tickled.clearTickled()}
48506
+ @wheel=${(e) => this.#handleWheel(e)}
46620
48507
  >
46621
48508
  ${this.#renderBreadcrumb()}
46622
48509
  ${rects.length === 0 ? b`<div class="empty-state">No data</div>` : b`
46623
48510
  <div class="viewport" part="viewport">
46624
- ${repeat(topLevel, (r) => r.node.item.key, (r) => this.#renderTopTile(r, byParent))}
48511
+ <div class="viewport-inner">
48512
+ ${repeat(topLevel, (r) => r.node.item.key, (r) => this.#renderTopTile(r, byParent))}
48513
+ </div>
46625
48514
  </div>
46626
48515
  `}
46627
48516
  </div>
@@ -46644,11 +48533,18 @@ let TreemapListElement = class TreemapListElement extends i {
46644
48533
  --treemap-tile-border: var(--border-subtle);
46645
48534
  --treemap-header-bg: rgb(0 0 0 / 0.12);
46646
48535
  --treemap-font-size: var(--text-s);
48536
+ --treemap-zoom-duration: 220ms;
46647
48537
 
46648
48538
  display: block;
46649
- overflow: auto;
48539
+ overflow: hidden;
46650
48540
  }
46651
48541
 
48542
+ /* Per-category tile colours (set categoryAccessor to opt in): define
48543
+ --treemap-category-SLUG-bg / -text at whatever scope suits (:host
48544
+ here, or globally) for each category your accessor can return —
48545
+ e.g. --treemap-category-urgent-bg: oklch(55% 0.18 25). A category
48546
+ with no matching pair falls back to --treemap-tile-bg/-text. */
48547
+
46652
48548
  .treemap {
46653
48549
  display: flex;
46654
48550
  flex-direction: column;
@@ -46677,18 +48573,44 @@ let TreemapListElement = class TreemapListElement extends i {
46677
48573
  .viewport {
46678
48574
  position: relative;
46679
48575
  width: 100%;
46680
- height: var(--treemap-height);
46681
- flex-shrink: 0;
48576
+ /* flex-basis of --treemap-height (its size when nothing forces a
48577
+ different one — the common case of an auto-height host) but
48578
+ allowed to grow/shrink like any flex item, so it shares the
48579
+ .treemap column's height with the breadcrumb instead of a plain
48580
+ fixed height always claiming that full amount regardless of what
48581
+ else needs space — which is what let the breadcrumb push the
48582
+ host past its own height and scroll once a zoom made it appear. */
48583
+ flex: 1 1 var(--treemap-height);
48584
+ min-height: 0;
46682
48585
  overflow: hidden;
46683
48586
  border-radius: var(--radius-m);
46684
48587
  }
46685
48588
 
48589
+ /* Transform target for the zoom-in/zoom-out FLIP animation (see
48590
+ #planFlip/#applyFlip) — kept separate from .viewport so the
48591
+ transform never fights that element's own sizing/overflow. */
48592
+ .viewport-inner {
48593
+ position: absolute;
48594
+ inset: 0;
48595
+ transform-origin: 0 0;
48596
+ }
48597
+
48598
+ .viewport-inner.flipping {
48599
+ transition: transform var(--treemap-zoom-duration, 220ms) ease-out;
48600
+ }
48601
+
46686
48602
  .tile {
46687
48603
  position: absolute;
46688
48604
  box-sizing: border-box;
46689
48605
  overflow: hidden;
46690
48606
  display: flex;
46691
48607
  flex-direction: column;
48608
+ /* Override the shared .item class's align-items:center (built for
48609
+ icon+label rows) — tiles need their flex children to stretch to
48610
+ full width, or .tile-children collapses to its (empty) content
48611
+ width and every nested tile collapses with it. */
48612
+ align-items: stretch;
48613
+ white-space: normal;
46692
48614
  background: var(--treemap-tile-bg);
46693
48615
  color: var(--treemap-tile-text);
46694
48616
  border: var(--treemap-tile-border);
@@ -46707,6 +48629,14 @@ let TreemapListElement = class TreemapListElement extends i {
46707
48629
  margin-top: 0;
46708
48630
  }
46709
48631
 
48632
+ /* Cascaded presentation (Lü & Fogarty 2008): the geometric inset is
48633
+ already in the layout rects — this adds the stack-of-cards depth
48634
+ cue on top. Parent tiles read as cards sitting behind their
48635
+ children. */
48636
+ .treemap[data-presentation="cascaded"] .tile {
48637
+ box-shadow: 2px 2px 0 rgb(0 0 0 / 0.25);
48638
+ }
48639
+
46710
48640
  .tile-header {
46711
48641
  flex-shrink: 0;
46712
48642
  padding: 1px var(--space-xs);
@@ -46777,16 +48707,37 @@ __decorate([property({
46777
48707
  type: String,
46778
48708
  attribute: `tiling-method`
46779
48709
  })], TreemapListElement.prototype, "tilingMethod", void 0);
48710
+ __decorate([property({
48711
+ type: String,
48712
+ attribute: `presentation`
48713
+ })], TreemapListElement.prototype, "presentation", void 0);
48714
+ __decorate([property({
48715
+ type: Number,
48716
+ attribute: `cascade-offset`
48717
+ })], TreemapListElement.prototype, "cascadeOffset", void 0);
46780
48718
  __decorate([property({
46781
48719
  type: Number,
46782
48720
  attribute: `max-depth`
46783
48721
  })], TreemapListElement.prototype, "maxDepth", void 0);
48722
+ __decorate([property({
48723
+ type: Boolean,
48724
+ attribute: `hide-leaves`
48725
+ })], TreemapListElement.prototype, "hideLeaves", void 0);
46784
48726
  __decorate([property({
46785
48727
  type: Number,
46786
48728
  attribute: `min-label-area`
46787
48729
  })], TreemapListElement.prototype, "minLabelArea", void 0);
48730
+ __decorate([property({
48731
+ type: Number,
48732
+ attribute: `min-tile-width`
48733
+ })], TreemapListElement.prototype, "minTileWidth", void 0);
46788
48734
  __decorate([property({ type: Number })], TreemapListElement.prototype, "padding", void 0);
48735
+ __decorate([property({
48736
+ type: Number,
48737
+ attribute: `min-child-area`
48738
+ })], TreemapListElement.prototype, "minChildArea", void 0);
46789
48739
  __decorate([property({ attribute: false })], TreemapListElement.prototype, "valueAccessor", void 0);
48740
+ __decorate([property({ attribute: false })], TreemapListElement.prototype, "categoryAccessor", void 0);
46790
48741
  __decorate([property({ attribute: false })], TreemapListElement.prototype, "selectedNode", void 0);
46791
48742
  __decorate([state()], TreemapListElement.prototype, "_selectedNodes", void 0);
46792
48743
  __decorate([property({ attribute: false })], TreemapListElement.prototype, "filterPredicate", void 0);
@@ -47916,6 +49867,6 @@ function init() {
47916
49867
  console.log(`Init`);
47917
49868
  }
47918
49869
  //#endregion
47919
- export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, 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, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, dropTargetFor, extractStack, findIcicleNode, findIciclePath, findNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, toolbarGapIndex, walk, wrapRoot };
49870
+ export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, MulticolListElement, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, 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, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, dropTargetFor, extractStack, findIcicleNode, findIciclePath, findNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, toolbarGapIndex, walk, wrapRoot };
47920
49871
 
47921
49872
  //# sourceMappingURL=index.js.map