@ixfx/components 0.8.2 → 0.8.4

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 (103) hide show
  1. package/bundle/index.d.ts +274 -93
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2135 -637
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.js +2 -2
  6. package/dist/ac-token.js +2 -2
  7. package/dist/checkbox.js +1 -1
  8. package/dist/{colour-picker-Ddsk-7E_.js → colour-picker-CSxwUmtE.js} +2 -2
  9. package/dist/{colour-picker-Ddsk-7E_.js.map → colour-picker-CSxwUmtE.js.map} +1 -1
  10. package/dist/colour-picker.js +1 -1
  11. package/dist/crumbs.js +2 -2
  12. package/dist/data-display.js +1 -1
  13. package/dist/data-grid-D-z7bx5-.d.ts.map +1 -1
  14. package/dist/data-grid.js +7 -4
  15. package/dist/data-grid.js.map +1 -1
  16. package/dist/detail-list-_ZJHYEBr.d.ts.map +1 -1
  17. package/dist/detail-list.js +67 -4
  18. package/dist/detail-list.js.map +1 -1
  19. package/dist/editable-label.js +1 -1
  20. package/dist/{fallbacks-DNI9xt0F.js → fallbacks-BcKGq-2F.js} +17 -1
  21. package/dist/{fallbacks-DNI9xt0F.js.map → fallbacks-BcKGq-2F.js.map} +1 -1
  22. package/dist/{grid-list-bmBWq6P-.js → grid-list-BXfY3XXX.js} +486 -13
  23. package/dist/grid-list-BXfY3XXX.js.map +1 -0
  24. package/dist/{grid-list-C1T6WlEp.d.ts → grid-list-x550wJ0X.d.ts} +97 -2
  25. package/dist/grid-list-x550wJ0X.d.ts.map +1 -0
  26. package/dist/grid-list.d.ts +2 -2
  27. package/dist/grid-list.js +1 -1
  28. package/dist/handle-BPOxBOKf.js +219 -0
  29. package/dist/handle-BPOxBOKf.js.map +1 -0
  30. package/dist/{icon-DcEEGQa_.js → icon-Dy33gu4Z.js} +2 -2
  31. package/dist/{icon-DcEEGQa_.js.map → icon-Dy33gu4Z.js.map} +1 -1
  32. package/dist/icons.js +1 -1
  33. package/dist/{index-BeuCXtiO.d.ts → index-DV3iaUBW.d.ts} +2 -2
  34. package/dist/{index-BeuCXtiO.d.ts.map → index-DV3iaUBW.d.ts.map} +1 -1
  35. package/dist/index.d.ts +130 -48
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +551 -203
  38. package/dist/index.js.map +1 -1
  39. package/dist/labelled-radial-input.js +2 -2
  40. package/dist/{labelled-slider-input-BInB5GKL.js → labelled-slider-input-BCH9968c.js} +3 -3
  41. package/dist/{labelled-slider-input-BInB5GKL.js.map → labelled-slider-input-BCH9968c.js.map} +1 -1
  42. package/dist/labelled-slider-input.js +1 -1
  43. package/dist/list-selection-controller-SA4piSBi.js +589 -0
  44. package/dist/list-selection-controller-SA4piSBi.js.map +1 -0
  45. package/dist/{menu-BDrTh6oo.js → menu-HMoxCPY2.js} +4 -4
  46. package/dist/{menu-BDrTh6oo.js.map → menu-HMoxCPY2.js.map} +1 -1
  47. package/dist/{menu-item-B6pKg2Qq.js → menu-item-BQDYxk5A.js} +3 -3
  48. package/dist/{menu-item-B6pKg2Qq.js.map → menu-item-BQDYxk5A.js.map} +1 -1
  49. package/dist/menu.js +2 -2
  50. package/dist/miller.js +1 -1
  51. package/dist/narrowed-text.js +1 -1
  52. package/dist/notifier.js +1 -1
  53. package/dist/panel.d.ts +1 -1
  54. package/dist/panel.js +1 -1
  55. package/dist/polar-pad.js +1 -1
  56. package/dist/{radial-input-BfA3GO0H.js → radial-input-DyxfbNzD.js} +2 -2
  57. package/dist/{radial-input-BfA3GO0H.js.map → radial-input-DyxfbNzD.js.map} +1 -1
  58. package/dist/radial-input.js +1 -1
  59. package/dist/range-input.js +1 -1
  60. package/dist/range.js +1 -1
  61. package/dist/select-horiz.js +1 -1
  62. package/dist/{slider-input-Bl1hFE89.js → slider-input-WR8_RwHg.js} +2 -2
  63. package/dist/{slider-input-Bl1hFE89.js.map → slider-input-WR8_RwHg.js.map} +1 -1
  64. package/dist/slider-input.js +1 -1
  65. package/dist/snackbar.js +1 -1
  66. package/dist/{split-layout-BFSbUZQv.js → split-layout-CA5jbNbG.js} +22 -33
  67. package/dist/split-layout-CA5jbNbG.js.map +1 -0
  68. package/dist/split-layout-JK3xWVX2.d.ts.map +1 -1
  69. package/dist/split-layout.js +1 -1
  70. package/dist/swipe.js +1 -1
  71. package/dist/tabs.js +1 -1
  72. package/dist/{timeline-B2fgDL4U.js → timeline-THyvH8xy.js} +14 -8
  73. package/dist/timeline-THyvH8xy.js.map +1 -0
  74. package/dist/timeline.d.ts +1 -1
  75. package/dist/timeline.js +1 -1
  76. package/dist/{titlebar-YQ-jVQoS.d.ts → titlebar-Tqu-VvMU.d.ts} +5 -1
  77. package/dist/titlebar-Tqu-VvMU.d.ts.map +1 -0
  78. package/dist/titlebar.d.ts +1 -1
  79. package/dist/titlebar.js +5 -3
  80. package/dist/titlebar.js.map +1 -1
  81. package/dist/{tree-4Jv28Nig.js → tree-DTQon838.js} +2 -2
  82. package/dist/{tree-4Jv28Nig.js.map → tree-DTQon838.js.map} +1 -1
  83. package/dist/tree.js +1 -1
  84. package/dist/vertical-list-CFuFDygi.d.ts.map +1 -1
  85. package/dist/vertical-list.js +67 -3
  86. package/dist/vertical-list.js.map +1 -1
  87. package/dist/xy-pad.js +1 -1
  88. package/docs-user/data-grid.md +1 -1
  89. package/docs-user/detail-list.md +4 -0
  90. package/docs-user/dock.md +33 -8
  91. package/docs-user/grid-list.md +5 -0
  92. package/docs-user/index.json +2 -2
  93. package/docs-user/split-layout.md +2 -4
  94. package/docs-user/twosplit-layout.md +14 -16
  95. package/docs-user/vertical-list.md +4 -0
  96. package/package.json +1 -1
  97. package/dist/grid-list-C1T6WlEp.d.ts.map +0 -1
  98. package/dist/grid-list-bmBWq6P-.js.map +0 -1
  99. package/dist/list-selection-controller-DTFI40li.js +0 -264
  100. package/dist/list-selection-controller-DTFI40li.js.map +0 -1
  101. package/dist/split-layout-BFSbUZQv.js.map +0 -1
  102. package/dist/timeline-B2fgDL4U.js.map +0 -1
  103. package/dist/titlebar-YQ-jVQoS.d.ts.map +0 -1
package/bundle/index.js CHANGED
@@ -3,8 +3,8 @@ import { customElement, eventOptions, property, query, queryAssignedElements, st
3
3
  import { styleMap } from "lit/directives/style-map.js";
4
4
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
5
5
  import { Fzf } from "fzf";
6
- import { Colour } from "@ixfx/visual";
7
6
  import { createCommandRegistry } from "@clinth/ui-commands";
7
+ import { Colour } from "@ixfx/visual";
8
8
  import { Trees } from "@ixfx/collections";
9
9
  import { clamp, clamper, rangeStream, round, scaler } from "@ixfx/numbers";
10
10
  import { repeat } from "lit/directives/repeat.js";
@@ -1290,6 +1290,22 @@ const _themeFallbacksCss = i$3`
1290
1290
  inherits: true;
1291
1291
  initial-value: 0 4px 24px rgba(0, 0, 0, 0.5);
1292
1292
  }
1293
+ @property --deboss-3 {
1294
+ syntax: '*';
1295
+ inherits: true;
1296
+ initial-value:
1297
+ inset 1px 1px 1px 1px color-mix(in oklch, oklch(8% 0.02 240) 60%, transparent),
1298
+ inset -1px -1px 1px 1px color-mix(in oklch, oklch(15% 0.02 240) 50%, transparent),
1299
+ -2px -2px 1px 0px color-mix(in oklch, oklch(12% 0.02 240) 30%, transparent);
1300
+ }
1301
+ @property --emboss-3 {
1302
+ syntax: '*';
1303
+ inherits: true;
1304
+ initial-value:
1305
+ inset 1px 1px 1px 1px color-mix(in oklch, oklch(22% 0.02 240) 30%, transparent),
1306
+ inset -1px -1px 1px 1px color-mix(in oklch, oklch(12% 0.02 240) 20%, transparent),
1307
+ 2px 2px 1px 0px color-mix(in oklch, oklch(12% 0.02 240) 30%, transparent);
1308
+ }
1293
1309
  @property --item-bg {
1294
1310
  syntax: '<color>';
1295
1311
  inherits: true;
@@ -3896,6 +3912,1123 @@ __decorate([state()], AcTokenElement.prototype, "_selectedTokenIndex", void 0);
3896
3912
  __decorate([state()], AcTokenElement.prototype, "_isOpen", void 0);
3897
3913
  AcTokenElement = __decorate([safeCustomElement(`ixfx-ac-token`)], AcTokenElement);
3898
3914
  //#endregion
3915
+ //#region src/styles/handle.ts
3916
+ /**
3917
+ * Shared drag / resize **handle** styling.
3918
+ *
3919
+ * A handle is the grab affordance for moving or resizing an object. Apply the
3920
+ * `.ixfx-handle` class to a small decorative element inside your drag / resize
3921
+ * hit-zone and choose a look with two independent attributes:
3922
+ *
3923
+ * - `data-handle` — texture: `flat` (default) · `dotted` · `dimpled` · `grated`
3924
+ * - `data-shape` — topology: `horizontal` (default) · `vertical` · `spot`
3925
+ *
3926
+ * - **flat** — a solid rounded capsule (bar) or dot (`spot`), filled with
3927
+ * `--ixfx-handle-color`.
3928
+ * - **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
3934
+ * copy peeking out above-left and a darker copy below-right of the base mark.
3935
+ * - **grated** — fine 45° knurling: each ridge is a lit edge, the base colour,
3936
+ * then a shaded edge. `spot` is the same hatch on a square crop.
3937
+ *
3938
+ * `dotted` / `dimpled` / `grated` are texture only — no plate, box or border; the
3939
+ * gaps are transparent so the surface behind shows through. The base colour is
3940
+ * `--ixfx-handle-color` (a theme token); the deboss edges default to that colour
3941
+ * lightened / darkened.
3942
+ *
3943
+ * The handle is `pointer-events: none` — bind pointer handlers to the surrounding
3944
+ * hit-zone. It reaches its "active" look (`--ixfx-handle-opacity-active`, and for
3945
+ * `flat` / `dotted` the `--ixfx-handle-color-active` tint) when its immediate
3946
+ * parent (the hit-zone) is `:hover` / `:focus-visible` / `[data-active]`, or when
3947
+ * the handle itself is. Set `[data-active]` on the hit-zone yourself while a drag
3948
+ * is live so the handle stays lit after the pointer leaves it.
3949
+ *
3950
+ * ```ts
3951
+ * import { handleStyles } from '../styles/handle.js';
3952
+ *
3953
+ * static styles = [themeFallbacks, handleStyles, css`...`];
3954
+ * ```
3955
+ * ```html
3956
+ * <div class="hit-zone" @pointerdown=${this.#onDown}>
3957
+ * <span class="ixfx-handle" data-handle="dotted" data-shape="spot" part="handle"></span>
3958
+ * </div>
3959
+ * ```
3960
+ *
3961
+ * ## CSS variables (defaults shown; override on the element or any ancestor)
3962
+ * - `--ixfx-handle-color` `var(--surface-muted-text)` — `flat` / `dotted` fill
3963
+ * - `--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)
3966
+ * - `--ixfx-handle-spot-size` `14px` — width = height of a `spot`
3967
+ * - `--ixfx-handle-radius` `var(--radius-l)` — `flat` capsule / dotted & dimpled mark rounding
3968
+ * - `--ixfx-handle-opacity` `0.55` — rest opacity
3969
+ * - `--ixfx-handle-opacity-active` `1`
3970
+ * - `--ixfx-handle-dot-size` `3px` — mark size (dotted / dimpled)
3971
+ * - `--ixfx-handle-dot-gap` `length/4` (bar) · `spot/3` — mark centre-to-centre step
3972
+ * - `--ixfx-handle-tile` `5px` — knurl period (grated)
3973
+ * - `--ixfx-handle-highlight` `color-mix(currentColor 35%, #fff)` — lit deboss edge
3974
+ * - `--ixfx-handle-shadow` `color-mix(currentColor 60%, #000)` — shaded deboss edge
3975
+ */
3976
+ const handleStyles = i$3`
3977
+ .ixfx-handle {
3978
+ /* Bridge: public --ixfx-handle-* (element or ancestor) → internal alias → fallback. */
3979
+ --_handle-color: var(--ixfx-handle-color, var(--surface-3-text));
3980
+ --_handle-color-active: var(--ixfx-handle-color-active, var(--surface-3-text));
3981
+ --_handle-thickness: var(--ixfx-handle-thickness, 6px);
3982
+ --_handle-length: var(--ixfx-handle-length, 44px);
3983
+ --_handle-spot: var(--ixfx-handle-spot-size, 14px);
3984
+ --_handle-radius: var(--ixfx-handle-radius, var(--radius-l));
3985
+ --_handle-opacity: var(--ixfx-handle-opacity, 0.55);
3986
+ --_handle-opacity-active: var(--ixfx-handle-opacity-active, 1);
3987
+ --_handle-dot: var(--ixfx-handle-dot-size, 3px);
3988
+ --_handle-tile: var(--ixfx-handle-tile, 5px);
3989
+ /* Centre-to-centre step of the dotted / dimpled marks (a length). Defaults to
3990
+ a share of the long axis; a bar shows ~3 marks. */
3991
+ --_handle-gap: var(--ixfx-handle-dot-gap, calc(var(--_handle-length) / 4));
3992
+ /* How far a dimpled mark's highlight / shadow copy is offset. */
3993
+ --_dimple-off: calc(var(--_handle-dot) * 0.15);
3994
+ /* The mark colour lightened / darkened for the two deboss edges. */
3995
+ --_handle-hi: var(--ixfx-handle-highlight, color-mix(in oklch, var(--surface-4) 50%, var(--_handle-color)));
3996
+ --_handle-lo: var(--ixfx-handle-shadow, color-mix(in oklch, var(--surface-2) 40%, var(--_handle-color)));
3997
+
3998
+ box-sizing: border-box;
3999
+ display: inline-block;
4000
+ flex: none;
4001
+ width: var(--_handle-length);
4002
+ height: var(--_handle-thickness);
4003
+ color: var(--_handle-color);
4004
+ opacity: var(--_handle-opacity);
4005
+ pointer-events: none;
4006
+ transition:
4007
+ color var(--transition, 0.15s ease-out),
4008
+ opacity var(--transition, 0.15s ease-out);
4009
+ }
4010
+
4011
+ .ixfx-handle[data-shape='vertical'] {
4012
+ width: var(--_handle-thickness);
4013
+ height: var(--_handle-length);
4014
+ }
4015
+
4016
+ .ixfx-handle[data-shape='spot'] {
4017
+ width: var(--_handle-spot);
4018
+ height: var(--_handle-spot);
4019
+ --_handle-gap: var(--ixfx-handle-dot-gap, calc(var(--_handle-spot) / 3));
4020
+ }
4021
+
4022
+ .ixfx-handle:hover,
4023
+ .ixfx-handle:focus-visible,
4024
+ .ixfx-handle[data-active],
4025
+ :hover > .ixfx-handle,
4026
+ :focus-visible > .ixfx-handle,
4027
+ [data-active] > .ixfx-handle {
4028
+ color: var(--_handle-color-active);
4029
+ opacity: var(--_handle-opacity-active);
4030
+ }
4031
+
4032
+ /* ── flat: solid capsule / dot ────────────────────────────────────────── */
4033
+ .ixfx-handle[data-handle='flat'] {
4034
+ background: currentColor;
4035
+ border-radius: var(--_handle-radius);
4036
+ }
4037
+ .ixfx-handle[data-handle='flat'][data-shape='spot'] {
4038
+ border-radius: 50%;
4039
+ }
4040
+
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. ─────────────────────────────────────────────── */
4045
+ .ixfx-handle[data-handle='dotted'],
4046
+ .ixfx-handle[data-handle='dimpled'] {
4047
+ position: relative;
4048
+ }
4049
+ .ixfx-handle[data-handle='dotted']::before,
4050
+ .ixfx-handle[data-handle='dimpled']::before {
4051
+ content: '';
4052
+ position: absolute;
4053
+ top: 50%;
4054
+ left: 50%;
4055
+ width: var(--_handle-dot);
4056
+ height: var(--_handle-dot);
4057
+ transform: translate(-50%, -50%);
4058
+ border-radius: var(--_handle-radius);
4059
+ background: currentColor;
4060
+ }
4061
+
4062
+ /* dotted — the two other marks are plain copies along the axis. */
4063
+ .ixfx-handle[data-handle='dotted']::before {
4064
+ box-shadow:
4065
+ calc(-1 * var(--_handle-gap)) 0 0 0 currentColor,
4066
+ var(--_handle-gap) 0 0 0 currentColor;
4067
+ }
4068
+ .ixfx-handle[data-handle='dotted'][data-shape='vertical']::before {
4069
+ box-shadow:
4070
+ 0 calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4071
+ 0 var(--_handle-gap) 0 0 currentColor;
4072
+ }
4073
+ .ixfx-handle[data-handle='dotted'][data-shape='spot']::before,
4074
+ .ixfx-handle[data-handle='dimpled'][data-shape='spot']::before {
4075
+ box-shadow:
4076
+ calc(-1 * var(--_handle-gap)) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4077
+ 0 calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4078
+ var(--_handle-gap) calc(-1 * var(--_handle-gap)) 0 0 currentColor,
4079
+ calc(-1 * var(--_handle-gap)) 0 0 0 currentColor,
4080
+ var(--_handle-gap) 0 0 0 currentColor,
4081
+ calc(-1 * var(--_handle-gap)) var(--_handle-gap) 0 0 currentColor,
4082
+ 0 var(--_handle-gap) 0 0 currentColor,
4083
+ var(--_handle-gap) var(--_handle-gap) 0 0 currentColor;
4084
+ }
4085
+
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 {
4089
+ box-shadow:
4090
+ calc(-1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4091
+ var(--_dimple-off) var(--_dimple-off) 0 0 var(--_handle-lo),
4092
+ 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),
4094
+ 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);
4098
+ }
4099
+ .ixfx-handle[data-handle='dimpled'][data-shape='vertical']::before {
4100
+ box-shadow:
4101
+ calc(-1 * var(--_dimple-off)) calc(-1 * var(--_dimple-off)) 0 0 var(--_handle-hi),
4102
+ var(--_dimple-off) var(--_dimple-off) 0 0 var(--_handle-lo),
4103
+ 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),
4105
+ 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);
4109
+ }
4110
+
4111
+ /* ── grated: fine 45° knurling — each ridge is a lit edge, the base colour,
4112
+ then a shaded edge. Same hatch at every shape (spot is a square crop). ── */
4113
+ .ixfx-handle[data-handle='grated'] {
4114
+ background-image: repeating-linear-gradient(
4115
+ 45deg,
4116
+ var(--_handle-hi) 0 0.9px,
4117
+ currentColor 0.9px 1.6px,
4118
+ var(--_handle-lo) 1.6px 2.5px,
4119
+ transparent 2.5px var(--_handle-tile)
4120
+ );
4121
+ }
4122
+
4123
+ @media (prefers-reduced-motion: reduce) {
4124
+ .ixfx-handle {
4125
+ transition: none;
4126
+ }
4127
+ }
4128
+ `;
4129
+ //#endregion
4130
+ //#region src/titlebar/titlebar.ts
4131
+ let TitlebarElement = class TitlebarElement extends i {
4132
+ constructor(..._args) {
4133
+ super(..._args);
4134
+ this.title = ``;
4135
+ this.iconName = ``;
4136
+ this.orientation = `horizontal`;
4137
+ this.variant = `titlebar`;
4138
+ this.closable = false;
4139
+ this.collapsible = false;
4140
+ this.collapsed = false;
4141
+ this.toggleInteraction = `implicit`;
4142
+ }
4143
+ _onToggleClick() {
4144
+ if (!this.collapsible) return;
4145
+ this.collapsed = !this.collapsed;
4146
+ this.dispatchEvent(new CustomEvent(`toggle`, {
4147
+ detail: { collapsed: this.collapsed },
4148
+ bubbles: true,
4149
+ composed: true
4150
+ }));
4151
+ }
4152
+ _onCloseClick() {
4153
+ this.dispatchEvent(new CustomEvent(`close`, {
4154
+ bubbles: true,
4155
+ composed: true
4156
+ }));
4157
+ }
4158
+ _getCaretIcon() {
4159
+ return `chevron-down`;
4160
+ }
4161
+ render() {
4162
+ const caretIcon = this._getCaretIcon();
4163
+ const isVertical = this.orientation === `vertical`;
4164
+ const showCaret = this.collapsible && this.toggleInteraction !== `disabled`;
4165
+ const implicitToggle = this.collapsible && this.toggleInteraction === `implicit`;
4166
+ const caretRotation = isVertical ? this.collapsed ? `` : `-90deg` : this.collapsed ? `-90deg` : ``;
4167
+ const treeVariant = this.variant === `tree`;
4168
+ return b`
4169
+ <div
4170
+ class="bar"
4171
+ role="toolbar"
4172
+ @click=${implicitToggle ? this._onToggleClick : A}
4173
+ >
4174
+ ${treeVariant && showCaret ? b`
4175
+ <button
4176
+ class="caret-btn"
4177
+ type="button"
4178
+ aria-label=${this.collapsed ? `Expand panel` : `Collapse panel`}
4179
+ @click=${this._onToggleClick}
4180
+ >
4181
+ <ixfx-icon name=${caretIcon} prominence .rotate=${caretRotation.length > 0 ? caretRotation : A}></ixfx-icon>
4182
+ </button>
4183
+ ` : A}
4184
+ <div class="title-text-wrapper">
4185
+ ${this.iconName ? b`<ixfx-icon class="title-icon" name=${this.iconName}></ixfx-icon>` : A}
4186
+ <span class="title-text">${this.title}</span>
4187
+ </div>
4188
+
4189
+ <div class="toolbar" @click=${(e) => e.stopPropagation()}>
4190
+ <slot name="toolbar"></slot>
4191
+ ${this.closable ? b`
4192
+ <button
4193
+ class="close-btn"
4194
+ type="button"
4195
+ aria-label="Close panel"
4196
+ @click=${this._onCloseClick}
4197
+ >
4198
+ <ixfx-icon name="close"></ixfx-icon>
4199
+ </button>
4200
+ ` : A}
4201
+ ${!treeVariant && showCaret ? b`
4202
+ <button
4203
+ class="caret-btn"
4204
+ type="button"
4205
+ aria-label=${this.collapsed ? `Expand panel` : `Collapse panel`}
4206
+ @click=${this._onToggleClick}
4207
+ >
4208
+ <ixfx-icon name=${caretIcon} prominence .rotate=${caretRotation.length > 0 ? caretRotation : A}></ixfx-icon>
4209
+ </button>
4210
+ ` : A}
4211
+ </div>
4212
+ </div>
4213
+ `;
4214
+ }
4215
+ static {
4216
+ this.styles = [themeFallbacks, i$3`
4217
+ :host {
4218
+ --titlebar-background: var(--surface-4);
4219
+
4220
+ display: block;
4221
+ box-sizing: border-box;
4222
+ }
4223
+
4224
+ .bar {
4225
+ display: flex;
4226
+ flex-direction: row;
4227
+ align-items: center;
4228
+ padding: var(--space-s) var(--space-m);
4229
+ background: var(--titlebar-background);
4230
+ color: var(--surface-3-text);
4231
+ min-height: 2em;
4232
+ height: 100%;
4233
+ flex-shrink: 0;
4234
+ box-sizing: border-box;
4235
+ }
4236
+
4237
+ :host([orientation="vertical"]) .bar {
4238
+ flex-direction: row;
4239
+ min-width: 2em;
4240
+ writing-mode: vertical-rl;
4241
+ text-orientation: mixed;
4242
+ }
4243
+
4244
+ .caret-btn,
4245
+ .close-btn {
4246
+ display: inline-flex;
4247
+ align-items: center;
4248
+ justify-content: center;
4249
+ background: none;
4250
+ border: none;
4251
+ padding: var(--space-xs);
4252
+ margin: 0;
4253
+ cursor: pointer;
4254
+ color: inherit;
4255
+ border-radius: var(--radius-s);
4256
+ transition: background var(--transition);
4257
+ opacity: var(--prominence-idle-opacity);
4258
+ }
4259
+
4260
+ .caret-btn:hover,
4261
+ .close-btn:hover {
4262
+ background: var(--surface-5);
4263
+ opacity: var(--prominence-tickled-opacity);
4264
+ }
4265
+
4266
+ .caret-btn:focus-visible,
4267
+ .close-btn:focus-visible {
4268
+ outline: 2px solid var(--accent);
4269
+ outline-offset: 1px;
4270
+ }
4271
+
4272
+ ixfx-icon {
4273
+ width: 1.25em;
4274
+ height: 1.25em;
4275
+ transition: transform var(--transition);
4276
+ }
4277
+
4278
+ .title-icon {
4279
+ flex-shrink: 0;
4280
+ }
4281
+
4282
+ .title-text {
4283
+ flex: 1;
4284
+ padding-left: 1ch;
4285
+ font-family: var(--font-family);
4286
+ font-size: var(--text-m);
4287
+ font-weight: var(--font-weight-bold, 600);
4288
+ white-space: nowrap;
4289
+ overflow: hidden;
4290
+ align-self: center;
4291
+ text-overflow: ellipsis;
4292
+ user-select: none;
4293
+ -webkit-user-select: none;
4294
+ }
4295
+
4296
+ :host([collapsible][toggle-interaction="implicit"]) .bar {
4297
+ cursor: pointer;
4298
+ }
4299
+
4300
+ :host([orientation="vertical"]) .title-text {
4301
+ writing-mode: vertical-rl;
4302
+ padding-left: 0;
4303
+ padding-bottom: 1ch;
4304
+ text-orientation: mixed;
4305
+ transform: rotate(180deg);
4306
+ }
4307
+
4308
+ .toolbar {
4309
+ display: flex;
4310
+ align-items: center;
4311
+ gap: var(--space-xs);
4312
+ margin-left: auto;
4313
+ }
4314
+
4315
+ :host([orientation="vertical"]) .toolbar {
4316
+ flex-direction: column;
4317
+ margin-left: 0;
4318
+ margin-top: auto;
4319
+ writing-mode: horizontal-tb;
4320
+ }
4321
+
4322
+ slot[name="toolbar"] {
4323
+ display: contents;
4324
+ }
4325
+
4326
+ ::slotted(slot[name="toolbar"]) {
4327
+ display: contents;
4328
+ }
4329
+
4330
+ .title-text-wrapper {
4331
+ display: flex;
4332
+ padding: 0 var(--space-s);
4333
+ }
4334
+
4335
+ /* === Midline variant === */
4336
+
4337
+ :host([variant="midline"]) .bar {
4338
+ background: transparent;
4339
+ padding: var(--space-xs) var(--space-m);
4340
+ min-height: unset;
4341
+ position: relative;
4342
+ }
4343
+
4344
+ :host([variant="midline"][orientation="vertical"]) .bar {
4345
+ padding: var(--space-m) var(--space-xs);
4346
+ min-width: unset;
4347
+ }
4348
+
4349
+ :host([variant="midline"]) .title-text-wrapper {
4350
+ position: relative;
4351
+ z-index: 1;
4352
+ display: flex;
4353
+ align-items: center;
4354
+ gap: var(--space-s);
4355
+ background: var(--surface-3);
4356
+ }
4357
+
4358
+ :host([variant="midline"]) .title-text {
4359
+ font-weight: normal;
4360
+ color: var(--surface-3-text);
4361
+ padding: 0 0.5ch 0 0.5ch;
4362
+ background: transparent;
4363
+ box-shadow: none;
4364
+ flex: unset;
4365
+ width: auto;
4366
+ white-space: nowrap;
4367
+ }
4368
+
4369
+ :host([variant="midline"]) .title-icon {
4370
+ background: transparent;
4371
+ flex-shrink: 0;
4372
+ }
4373
+
4374
+ :host([variant="midline"]) .caret-btn {
4375
+ position: relative;
4376
+ z-index: 1;
4377
+ background: var(--surface-3);
4378
+ opacity: 1;
4379
+ }
4380
+
4381
+ :host([variant="midline"][orientation="vertical"]) .title-text {
4382
+ transform: rotate(180deg);
4383
+ padding: 0.5ch 0 0.5ch 0ch;
4384
+ }
4385
+
4386
+ :host([variant="midline"]) .toolbar {
4387
+ position: relative;
4388
+ z-index: 2;
4389
+ background: var(--surface-3);
4390
+ margin-left: 0;
4391
+ }
4392
+
4393
+ :host([variant="midline"][orientation="horizontal"]) .toolbar {
4394
+ margin-left: auto;
4395
+ }
4396
+
4397
+ /* Debossed line runs from after title-text to before toolbar */
4398
+ :host([variant="midline"]) .bar::after {
4399
+ content: '';
4400
+ position: absolute;
4401
+ top: 50%;
4402
+ left: 0;
4403
+ right: 0;
4404
+ height: 1px;
4405
+ transform: translateY(-50%);
4406
+ background: var(--surface-2);
4407
+ box-shadow:
4408
+ 0 -1px 0 var(--surface-5),
4409
+ 0 1px 0 var(--surface-1);
4410
+ z-index: 0;
4411
+ }
4412
+
4413
+ /* For vertical midline, line runs vertically */
4414
+ :host([variant="midline"][orientation="vertical"]) .bar::after {
4415
+ top: 0;
4416
+ bottom: 0;
4417
+ left: 50%;
4418
+ right: unset;
4419
+ width: 1px;
4420
+ height: 100%;
4421
+ transform: translateX(-50%);
4422
+ box-shadow:
4423
+ -1px 0 0 var(--surface-5),
4424
+ 1px 0 0 var(--surface-1);
4425
+ }
4426
+
4427
+ /* === Tree variant: VS Code explorer / Outline style === */
4428
+ :host([variant="tree"]) .bar {
4429
+ background: transparent;
4430
+ padding: 0 var(--space-xs);
4431
+ min-height: unset;
4432
+ gap: var(--space-xs);
4433
+ }
4434
+
4435
+ :host([variant="tree"][orientation="vertical"]) .bar {
4436
+ padding: var(--space-xs) 0;
4437
+ flex-direction: column;
4438
+ min-width: unset;
4439
+ writing-mode: horizontal-tb;
4440
+ }
4441
+
4442
+ :host([variant="tree"]) .title-text-wrapper {
4443
+ display: inline-flex;
4444
+ align-items: center;
4445
+ gap: var(--space-xs);
4446
+ padding: 0;
4447
+ background: transparent;
4448
+ }
4449
+
4450
+ :host([variant="tree"]) .title-icon {
4451
+ background: transparent;
4452
+ flex-shrink: 0;
4453
+ }
4454
+
4455
+ :host([variant="tree"]) .title-text {
4456
+ font-weight: normal;
4457
+ color: var(--surface-3-text);
4458
+ padding: 0;
4459
+ background: transparent;
4460
+ box-shadow: none;
4461
+ flex: unset;
4462
+ width: auto;
4463
+ white-space: nowrap;
4464
+ }
4465
+
4466
+ :host([variant="tree"][orientation="vertical"]) .title-text {
4467
+ writing-mode: vertical-rl;
4468
+ transform: rotate(180deg);
4469
+ padding: 0;
4470
+ }
4471
+
4472
+ :host([variant="tree"]) .caret-btn {
4473
+ background: transparent;
4474
+ opacity: var(--prominence-idle-opacity);
4475
+ padding: 0;
4476
+ }
4477
+
4478
+ :host([variant="tree"]) .caret-btn:hover {
4479
+ background: transparent;
4480
+ opacity: var(--prominence-tickled-opacity);
4481
+ }
4482
+
4483
+ :host([variant="tree"]) .toolbar {
4484
+ margin-left: auto;
4485
+ }
4486
+
4487
+ :host([variant="tree"][orientation="vertical"]) .toolbar {
4488
+ flex-direction: row;
4489
+ margin-left: 0;
4490
+ margin-top: auto;
4491
+ }
4492
+
4493
+ :host([variant="tree"]) ixfx-icon {
4494
+ width: 1em;
4495
+ height: 1em;
4496
+ }
4497
+ `];
4498
+ }
4499
+ };
4500
+ __decorate([property({ type: String })], TitlebarElement.prototype, "title", void 0);
4501
+ __decorate([property({
4502
+ type: String,
4503
+ attribute: `icon-name`
4504
+ })], TitlebarElement.prototype, "iconName", void 0);
4505
+ __decorate([property({
4506
+ type: String,
4507
+ attribute: `orientation`,
4508
+ reflect: true
4509
+ })], TitlebarElement.prototype, "orientation", void 0);
4510
+ __decorate([property({
4511
+ type: String,
4512
+ attribute: `variant`,
4513
+ reflect: true
4514
+ })], TitlebarElement.prototype, "variant", void 0);
4515
+ __decorate([property({ type: Boolean })], TitlebarElement.prototype, "closable", void 0);
4516
+ __decorate([property({
4517
+ type: Boolean,
4518
+ reflect: true
4519
+ })], TitlebarElement.prototype, "collapsible", void 0);
4520
+ __decorate([property({
4521
+ type: Boolean,
4522
+ reflect: true
4523
+ })], TitlebarElement.prototype, "collapsed", void 0);
4524
+ __decorate([property({
4525
+ type: String,
4526
+ attribute: `toggle-interaction`
4527
+ })], TitlebarElement.prototype, "toggleInteraction", void 0);
4528
+ TitlebarElement = __decorate([safeCustomElement(`ixfx-titlebar`)], TitlebarElement);
4529
+ //#endregion
4530
+ //#region src/bottom-drawer/bottom-drawer.ts
4531
+ /** Builds the command id for a named drawer's command, e.g. `inspector.expand`. */
4532
+ function bottomDrawerCommandId(name, command) {
4533
+ return `${name.trim()}.${command}`;
4534
+ }
4535
+ function clampRange(value, min, max) {
4536
+ return Math.min(max, Math.max(min, value));
4537
+ }
4538
+ let BottomDrawerElement = class BottomDrawerElement extends i {
4539
+ constructor(..._args) {
4540
+ super(..._args);
4541
+ this.open = false;
4542
+ this.height = 320;
4543
+ this.minHeight = 160;
4544
+ this.dismissThreshold = 96;
4545
+ this.maxHeightRatio = .85;
4546
+ this.label = `Drawer`;
4547
+ this.iconName = ``;
4548
+ this.closable = true;
4549
+ this.collapsible = true;
4550
+ this.maintainHandle = false;
4551
+ this.name = ``;
4552
+ this.#dragStartY = 0;
4553
+ this.#dragStartHeight = 0;
4554
+ this.#collapsedDragging = false;
4555
+ this.#commandName = ``;
4556
+ this.#commandIds = [];
4557
+ this.#onResizeStart = (event) => {
4558
+ if (event.button !== 0) return;
4559
+ event.preventDefault();
4560
+ const edge = event.currentTarget;
4561
+ edge.setPointerCapture(event.pointerId);
4562
+ if (!this.open) {
4563
+ this.#collapsedDragging = false;
4564
+ this.#dragStartY = event.clientY;
4565
+ this.#dragStartHeight = this.dismissThreshold;
4566
+ edge.addEventListener(`pointermove`, this.#onTabPointerMove);
4567
+ edge.addEventListener(`pointerup`, this.#onTabPointerUp, { once: true });
4568
+ edge.addEventListener(`pointercancel`, this.#onTabPointerUp, { once: true });
4569
+ return;
4570
+ }
4571
+ this.#dragStartY = event.clientY;
4572
+ this.#dragStartHeight = this.#displayHeight();
4573
+ this._dragHeight = this.#dragStartHeight;
4574
+ this.toggleAttribute(`resizing`, true);
4575
+ edge.addEventListener(`pointermove`, this.#onResizeMove);
4576
+ edge.addEventListener(`pointerup`, this.#onResizeEnd, { once: true });
4577
+ edge.addEventListener(`pointercancel`, this.#onResizeEnd, { once: true });
4578
+ };
4579
+ this.#onResizeMove = (event) => {
4580
+ this._dragHeight = clampRange(this.#dragStartHeight + this.#dragStartY - event.clientY, 0, this.#maxHeight());
4581
+ };
4582
+ this.#onResizeEnd = (event) => {
4583
+ const edge = event.currentTarget;
4584
+ edge.removeEventListener(`pointermove`, this.#onResizeMove);
4585
+ edge.removeEventListener(`pointerup`, this.#onResizeEnd);
4586
+ edge.removeEventListener(`pointercancel`, this.#onResizeEnd);
4587
+ if (edge.hasPointerCapture(event.pointerId)) edge.releasePointerCapture(event.pointerId);
4588
+ this.toggleAttribute(`resizing`, false);
4589
+ const height = this._dragHeight ?? this.height;
4590
+ this._dragHeight = void 0;
4591
+ if (height < this.dismissThreshold) {
4592
+ this.close();
4593
+ return;
4594
+ }
4595
+ this.#setHeight(height);
4596
+ };
4597
+ this.#onResizeKeyDown = (event) => {
4598
+ if (!this.open) {
4599
+ if (event.key === `ArrowUp` || event.key === `Enter` || event.key === ` ` || event.key === `Home` || event.key === `End`) {
4600
+ event.preventDefault();
4601
+ this.show();
4602
+ }
4603
+ return;
4604
+ }
4605
+ const delta = event.shiftKey ? 64 : 16;
4606
+ if (event.key === `ArrowUp`) {
4607
+ event.preventDefault();
4608
+ this.#setHeight(this.height + delta);
4609
+ } else if (event.key === `ArrowDown`) {
4610
+ event.preventDefault();
4611
+ if (this.height - delta < this.dismissThreshold) this.close();
4612
+ else this.#setHeight(this.height - delta);
4613
+ } else if (event.key === `Home`) {
4614
+ event.preventDefault();
4615
+ this.#setHeight(this.minHeight);
4616
+ } else if (event.key === `End`) {
4617
+ event.preventDefault();
4618
+ this.#setHeight(this.#maxHeight());
4619
+ }
4620
+ };
4621
+ this.#onTabPointerMove = (event) => {
4622
+ const delta = this.#dragStartY - event.clientY;
4623
+ if (!this.#collapsedDragging) {
4624
+ if (delta < 4) return;
4625
+ this.#collapsedDragging = true;
4626
+ this.show();
4627
+ this.toggleAttribute(`resizing`, true);
4628
+ }
4629
+ this._dragHeight = clampRange(this.#dragStartHeight + this.#dragStartY - event.clientY, 0, this.#maxHeight());
4630
+ };
4631
+ this.#onTabPointerUp = (event) => {
4632
+ const target = event.currentTarget;
4633
+ target.removeEventListener(`pointermove`, this.#onTabPointerMove);
4634
+ target.removeEventListener(`pointerup`, this.#onTabPointerUp);
4635
+ target.removeEventListener(`pointercancel`, this.#onTabPointerUp);
4636
+ if (target.hasPointerCapture(event.pointerId)) target.releasePointerCapture(event.pointerId);
4637
+ if (!this.#collapsedDragging) {
4638
+ this.show();
4639
+ return;
4640
+ }
4641
+ this.#collapsedDragging = false;
4642
+ this.toggleAttribute(`resizing`, false);
4643
+ const height = this._dragHeight ?? this.height;
4644
+ this._dragHeight = void 0;
4645
+ if (height < this.dismissThreshold) {
4646
+ this.close();
4647
+ return;
4648
+ }
4649
+ this.#setHeight(height);
4650
+ };
4651
+ this.#onDrawerKeyDown = (event) => {
4652
+ if (event.key !== `Escape`) return;
4653
+ event.preventDefault();
4654
+ event.stopPropagation();
4655
+ this.close();
4656
+ };
4657
+ }
4658
+ static {
4659
+ this.styles = [
4660
+ themeFallbacks,
4661
+ handleStyles,
4662
+ i$3`
4663
+ :host {
4664
+ --bottom-drawer-background: color-mix(in srgb, var(--surface-2) 84%, transparent);
4665
+ --bottom-drawer-border: 1px solid color-mix(in srgb, var(--surface-2-text) 14%, transparent);
4666
+ --bottom-drawer-radius: var(--radius-l);
4667
+ --bottom-drawer-blur: 18px;
4668
+ --bottom-drawer-shadow: var(--shadow-l);
4669
+ --bottom-drawer-collapsed-width: 96px;
4670
+
4671
+ display: block;
4672
+ position: relative;
4673
+ height: 100%;
4674
+ min-width: 0;
4675
+ min-height: 0;
4676
+ overflow: hidden;
4677
+ }
4678
+ .content {
4679
+ position: absolute;
4680
+ inset: 0;
4681
+ min-width: 0;
4682
+ min-height: 0;
4683
+ }
4684
+ .content ::slotted(*) {
4685
+ width: 100%;
4686
+ height: 100%;
4687
+ }
4688
+ .drawer {
4689
+ --m: 0;
4690
+ position: absolute;
4691
+ z-index: 2;
4692
+ right: calc(var(--m) * (50% - var(--bottom-drawer-collapsed-width) / 2));
4693
+ bottom: 0;
4694
+ left: calc(var(--m) * (50% - var(--bottom-drawer-collapsed-width) / 2));
4695
+ display: flex;
4696
+ flex-direction: column;
4697
+ box-sizing: border-box;
4698
+ height: var(--bottom-drawer-height);
4699
+ max-height: 100%;
4700
+ min-height: 0;
4701
+ color: var(--surface-2-text);
4702
+ background: var(--bottom-drawer-background);
4703
+ border: var(--bottom-drawer-border);
4704
+ border-bottom: 0;
4705
+ border-radius: var(--bottom-drawer-radius) var(--bottom-drawer-radius) 0 0;
4706
+ box-shadow: var(--bottom-drawer-shadow);
4707
+ backdrop-filter: blur(var(--bottom-drawer-blur)) saturate(1.15);
4708
+ -webkit-backdrop-filter: blur(var(--bottom-drawer-blur)) saturate(1.15);
4709
+ opacity: 0;
4710
+ transform: translateY(100%);
4711
+ visibility: hidden;
4712
+ transition:
4713
+ transform var(--transition),
4714
+ opacity var(--transition),
4715
+ left var(--transition),
4716
+ right var(--transition),
4717
+ height var(--transition),
4718
+ visibility 0s linear 0.16s;
4719
+ will-change: transform, opacity;
4720
+ }
4721
+ :host([open]) .drawer {
4722
+ opacity: var(--bottom-drawer-opacity, 1);
4723
+ transform: translateY(0);
4724
+ visibility: visible;
4725
+ transition:
4726
+ transform var(--transition),
4727
+ opacity var(--transition),
4728
+ left var(--transition),
4729
+ right var(--transition),
4730
+ height var(--transition);
4731
+ }
4732
+ :host([resizing]) {
4733
+ cursor: ns-resize;
4734
+ user-select: none;
4735
+ }
4736
+ :host([resizing]) .drawer {
4737
+ transition: none;
4738
+ }
4739
+ .resize-edge {
4740
+ position: absolute;
4741
+ z-index: 1;
4742
+ top: -5px;
4743
+ right: 0;
4744
+ left: 0;
4745
+ height: 11px;
4746
+ cursor: ns-resize;
4747
+ touch-action: none;
4748
+ outline: none;
4749
+ }
4750
+ .resize-edge .ixfx-handle {
4751
+ position: absolute;
4752
+ top: 7px;
4753
+ left: 50%;
4754
+ transform: translateX(-50%);
4755
+ --ixfx-handle-length: 44px;
4756
+ --ixfx-handle-thickness: 3px;
4757
+ --ixfx-handle-radius: 999px;
4758
+ }
4759
+ /* Inner content fades as the drawer morphs into its collapsed tab. */
4760
+ ixfx-titlebar,
4761
+ .body {
4762
+ opacity: calc(1 - var(--m, 0));
4763
+ transition: opacity var(--transition);
4764
+ }
4765
+ :host([resizing]) ixfx-titlebar,
4766
+ :host([resizing]) .body {
4767
+ transition: none;
4768
+ }
4769
+ /* Collapsed tab: docked to the bottom edge, narrowed to handle width. */
4770
+ :host([maintain-handle]:not([open])) .drawer {
4771
+ --m: 1;
4772
+ height: 20px;
4773
+ opacity: 1;
4774
+ transform: translateY(0);
4775
+ visibility: visible;
4776
+ transition:
4777
+ transform var(--transition),
4778
+ opacity var(--transition),
4779
+ left var(--transition),
4780
+ right var(--transition),
4781
+ height var(--transition);
4782
+ }
4783
+ :host([maintain-handle]:not([open])) ixfx-titlebar,
4784
+ :host([maintain-handle]:not([open])) .body {
4785
+ display: none;
4786
+ }
4787
+ :host([maintain-handle]:not([open])) .resize-edge {
4788
+ top: 0;
4789
+ height: 100%;
4790
+ }
4791
+ :host([maintain-handle]:not([open])) .resize-edge .ixfx-handle {
4792
+ top: 50%;
4793
+ transform: translate(-50%, -50%);
4794
+ }
4795
+ ixfx-titlebar {
4796
+ flex: none;
4797
+ --titlebar-background: var(--bottom-drawer-title-background, transparent);
4798
+ border-bottom: 1px solid color-mix(in srgb, var(--surface-2-text) 10%, transparent);
4799
+ }
4800
+ .body {
4801
+ flex: 1;
4802
+ min-height: 0;
4803
+ overflow: auto;
4804
+ overscroll-behavior: contain;
4805
+ }
4806
+ .body ::slotted(*) {
4807
+ box-sizing: border-box;
4808
+ }
4809
+ @media (prefers-reduced-motion: reduce) {
4810
+ .drawer,
4811
+ ixfx-titlebar,
4812
+ .body {
4813
+ transition-duration: 0s;
4814
+ }
4815
+ }
4816
+ `
4817
+ ];
4818
+ }
4819
+ #dragStartY;
4820
+ #dragStartHeight;
4821
+ #collapsedDragging;
4822
+ #ownedRegistry;
4823
+ #commandTarget;
4824
+ #commandName;
4825
+ #commandIds;
4826
+ connectedCallback() {
4827
+ super.connectedCallback();
4828
+ this.#syncCommands();
4829
+ }
4830
+ disconnectedCallback() {
4831
+ super.disconnectedCallback();
4832
+ this.#teardownCommands();
4833
+ if (this.#ownedRegistry !== void 0) {
4834
+ if (this.registry === this.#ownedRegistry) this.registry = void 0;
4835
+ this.#ownedRegistry = void 0;
4836
+ }
4837
+ }
4838
+ updated(changed) {
4839
+ super.updated(changed);
4840
+ if (changed.has(`name`) || changed.has(`registry`)) this.#syncCommands();
4841
+ }
4842
+ show() {
4843
+ this.open = true;
4844
+ }
4845
+ close() {
4846
+ if (!this.open) return;
4847
+ this.open = false;
4848
+ this.dispatchEvent(new CustomEvent(`drawer-close`, {
4849
+ bubbles: true,
4850
+ composed: true
4851
+ }));
4852
+ }
4853
+ toggle() {
4854
+ if (this.open) this.close();
4855
+ else this.show();
4856
+ }
4857
+ #syncCommands() {
4858
+ const name = this.name.trim();
4859
+ const target = this.registry ?? this.#ownedRegistry;
4860
+ if (name !== `` && this.isConnected && target !== void 0 && this.#commandTarget === target && this.#commandName === name) return;
4861
+ this.#teardownCommands();
4862
+ if (name === `` || !this.isConnected) return;
4863
+ const resolved = this.registry ?? (this.#ownedRegistry ??= createCommandRegistry());
4864
+ if (this.#ownedRegistry !== void 0 && resolved !== this.#ownedRegistry) this.#ownedRegistry = void 0;
4865
+ if (this.registry === void 0) this.registry = resolved;
4866
+ const registered = [];
4867
+ const commands = [
4868
+ {
4869
+ command: `expand`,
4870
+ label: `Expand ${this.label}`,
4871
+ description: `Open the ${this.label} drawer`,
4872
+ execute: () => this.show()
4873
+ },
4874
+ {
4875
+ command: `collapse`,
4876
+ label: `Collapse ${this.label}`,
4877
+ description: `Close the ${this.label} drawer`,
4878
+ execute: () => this.close()
4879
+ },
4880
+ {
4881
+ command: `toggle`,
4882
+ label: `Toggle ${this.label}`,
4883
+ description: `Open the ${this.label} drawer if closed, close it if open`,
4884
+ execute: () => this.toggle()
4885
+ }
4886
+ ];
4887
+ for (const { command, label, description, execute } of commands) {
4888
+ const id = bottomDrawerCommandId(name, command);
4889
+ if (resolved.has(id)) continue;
4890
+ resolved.register({
4891
+ id,
4892
+ label,
4893
+ description,
4894
+ execute
4895
+ });
4896
+ registered.push(id);
4897
+ }
4898
+ this.#commandTarget = resolved;
4899
+ this.#commandName = name;
4900
+ this.#commandIds = registered;
4901
+ }
4902
+ #teardownCommands() {
4903
+ if (this.#commandTarget !== void 0) {
4904
+ for (const id of this.#commandIds) this.#commandTarget.unregister(id);
4905
+ this.#commandTarget = void 0;
4906
+ this.#commandName = ``;
4907
+ this.#commandIds = [];
4908
+ }
4909
+ }
4910
+ _onTitlebarToggle(event) {
4911
+ event.stopPropagation();
4912
+ if (event.detail.collapsed) this.close();
4913
+ else this.open = true;
4914
+ }
4915
+ _onTitlebarClose(event) {
4916
+ event.stopPropagation();
4917
+ this.close();
4918
+ }
4919
+ render() {
4920
+ const raw = this.#displayHeight();
4921
+ const max = this.#maxHeight();
4922
+ const morph = this.#morphProgress(raw);
4923
+ const height = this.open ? raw * (1 - morph) + 20 * morph : 20;
4924
+ const opacity = this.maintainHandle && this.open ? 1 : this.#dragOpacity(raw);
4925
+ return b`
4926
+ <div class="content"><slot></slot></div>
4927
+ <section
4928
+ class="drawer"
4929
+ style=${`--bottom-drawer-height: ${height}px; --bottom-drawer-opacity: ${opacity}; --m: ${morph}`}
4930
+ role="region"
4931
+ aria-label=${this.label}
4932
+ @keydown=${this.#onDrawerKeyDown}
4933
+ >
4934
+ <div
4935
+ class="resize-edge"
4936
+ role="separator"
4937
+ tabindex="0"
4938
+ aria-label=${`${this.open ? `Resize` : `Expand`} ${this.label}`}
4939
+ aria-orientation="horizontal"
4940
+ aria-valuemin=${this.minHeight}
4941
+ aria-valuemax=${max}
4942
+ aria-valuenow=${Math.round(raw)}
4943
+ @pointerdown=${this.#onResizeStart}
4944
+ @keydown=${this.#onResizeKeyDown}
4945
+ ><span class="ixfx-handle" data-handle="dimpled" data-shape="horizontal" part="handle"></span></div>
4946
+ <ixfx-titlebar
4947
+ .title=${this.label}
4948
+ .iconName=${this.iconName}
4949
+ .closable=${this.closable}
4950
+ .collapsible=${this.collapsible}
4951
+ .collapsed=${!this.open}
4952
+ ?inert=${!this.open}
4953
+ @toggle=${this._onTitlebarToggle}
4954
+ @close=${this._onTitlebarClose}
4955
+ >
4956
+ <slot name="toolbar" slot="toolbar"></slot>
4957
+ </ixfx-titlebar>
4958
+ <div class="body" ?inert=${!this.open}><slot name="drawer"></slot></div>
4959
+ </section>
4960
+ `;
4961
+ }
4962
+ #maxHeight() {
4963
+ const availableHeight = this.clientHeight || this.height;
4964
+ return Math.max(this.minHeight, availableHeight * clampRange(this.maxHeightRatio, 0, 1));
4965
+ }
4966
+ #displayHeight() {
4967
+ return clampRange(this._dragHeight ?? this.height, 0, this.#maxHeight());
4968
+ }
4969
+ #dragOpacity(height) {
4970
+ if (this._dragHeight === void 0 || height >= this.minHeight) return 1;
4971
+ const fadeRange = this.minHeight - this.dismissThreshold;
4972
+ if (fadeRange <= 0) return height >= this.dismissThreshold ? 1 : 0;
4973
+ return clampRange((height - this.dismissThreshold) / fadeRange, 0, 1);
4974
+ }
4975
+ #morphProgress(raw) {
4976
+ if (!this.open) return this.maintainHandle ? 1 : 0;
4977
+ if (this._dragHeight === void 0 || !this.maintainHandle) return 0;
4978
+ const range = this.minHeight - this.dismissThreshold;
4979
+ if (range <= 0) return raw < this.dismissThreshold ? 1 : 0;
4980
+ return clampRange((this.minHeight - raw) / range, 0, 1);
4981
+ }
4982
+ #setHeight(height) {
4983
+ this.height = clampRange(height, this.minHeight, this.#maxHeight());
4984
+ this.dispatchEvent(new CustomEvent(`drawer-resize`, {
4985
+ detail: { height: this.height },
4986
+ bubbles: true,
4987
+ composed: true
4988
+ }));
4989
+ }
4990
+ #onResizeStart;
4991
+ #onResizeMove;
4992
+ #onResizeEnd;
4993
+ #onResizeKeyDown;
4994
+ #onTabPointerMove;
4995
+ #onTabPointerUp;
4996
+ #onDrawerKeyDown;
4997
+ };
4998
+ __decorate([property({
4999
+ type: Boolean,
5000
+ reflect: true
5001
+ })], BottomDrawerElement.prototype, "open", void 0);
5002
+ __decorate([property({ type: Number })], BottomDrawerElement.prototype, "height", void 0);
5003
+ __decorate([property({
5004
+ type: Number,
5005
+ attribute: `min-height`
5006
+ })], BottomDrawerElement.prototype, "minHeight", void 0);
5007
+ __decorate([property({
5008
+ type: Number,
5009
+ attribute: `dismiss-threshold`
5010
+ })], BottomDrawerElement.prototype, "dismissThreshold", void 0);
5011
+ __decorate([property({
5012
+ type: Number,
5013
+ attribute: `max-height-ratio`
5014
+ })], BottomDrawerElement.prototype, "maxHeightRatio", void 0);
5015
+ __decorate([property({ type: String })], BottomDrawerElement.prototype, "label", void 0);
5016
+ __decorate([property({
5017
+ type: String,
5018
+ attribute: `icon-name`
5019
+ })], BottomDrawerElement.prototype, "iconName", void 0);
5020
+ __decorate([property({ type: Boolean })], BottomDrawerElement.prototype, "closable", void 0);
5021
+ __decorate([property({ type: Boolean })], BottomDrawerElement.prototype, "collapsible", void 0);
5022
+ __decorate([property({
5023
+ type: Boolean,
5024
+ attribute: `maintain-handle`,
5025
+ reflect: true
5026
+ })], BottomDrawerElement.prototype, "maintainHandle", void 0);
5027
+ __decorate([property({ type: String })], BottomDrawerElement.prototype, "name", void 0);
5028
+ __decorate([property({ attribute: false })], BottomDrawerElement.prototype, "registry", void 0);
5029
+ __decorate([state()], BottomDrawerElement.prototype, "_dragHeight", void 0);
5030
+ BottomDrawerElement = __decorate([safeCustomElement(`ixfx-bottom-drawer`)], BottomDrawerElement);
5031
+ //#endregion
3899
5032
  //#region src/icons/defaults.ts
3900
5033
  /**
3901
5034
  * Chevron pointing down. Used for split-button dropdown toggle.
@@ -4562,6 +5695,306 @@ __decorate([state()], RadioGroup.prototype, "_icons", void 0);
4562
5695
  __decorate([query(`slot`)], RadioGroup.prototype, "_slot", void 0);
4563
5696
  RadioGroup = __decorate([safeCustomElement(`ixfx-radio-group`)], RadioGroup);
4564
5697
  //#endregion
5698
+ //#region src/interaction/list-drag-select-controller.ts
5699
+ const DEFAULT_POINTER_TYPES = [`mouse`, `pen`];
5700
+ const DEFAULT_THRESHOLD = 4;
5701
+ /** Pointer distance (px) from an edge that triggers auto-scroll. */
5702
+ const EDGE_ZONE = 28;
5703
+ /** Max auto-scroll speed in px per frame. */
5704
+ const MAX_SCROLL_SPEED = 16;
5705
+ /**
5706
+ * Shared pointer-drag (marquee / rubber-band) selection for list components.
5707
+ *
5708
+ * Owns all pointer plumbing and rectangle state; knows nothing about DOM
5709
+ * geometry or the selection model — those are host callbacks. Mirrors the
5710
+ * `TickledController` attach pattern (`new ListDragSelectController(this, …)`).
5711
+ *
5712
+ * Behaviour: `pointerdown` on the surface records a start point; once the
5713
+ * pointer moves past `threshold`, the surface captures the pointer and every
5714
+ * move recomputes the rect, hit-tests, and calls `onPreview`. On release the
5715
+ * final set is delivered once via `onCommit`. A press that never crosses the
5716
+ * threshold does nothing (the normal `@click` path runs).
5717
+ */
5718
+ var ListDragSelectController = class {
5719
+ #host;
5720
+ #config;
5721
+ #boundSurface;
5722
+ #pointerId;
5723
+ #startClientX = 0;
5724
+ #startClientY = 0;
5725
+ #startX = 0;
5726
+ #startY = 0;
5727
+ #currentClientX = 0;
5728
+ #currentClientY = 0;
5729
+ #lastModifiers = {
5730
+ shiftKey: false,
5731
+ ctrlKey: false,
5732
+ metaKey: false
5733
+ };
5734
+ #dragging = false;
5735
+ #suppressClick = false;
5736
+ /** Selection as it was when the drag started — the combine baseline. */
5737
+ #base = /* @__PURE__ */ new Set();
5738
+ /** Combine mode of the most recent preview (drives the commit flag). */
5739
+ #lastMode = `replace`;
5740
+ #preview = /* @__PURE__ */ new Set();
5741
+ #raf = 0;
5742
+ constructor(host, config) {
5743
+ this.#host = host;
5744
+ this.#config = config;
5745
+ host.addController(this);
5746
+ }
5747
+ /** `true` while a drag is in progress or its trailing click is still pending. */
5748
+ get isDragging() {
5749
+ return this.#dragging || this.#suppressClick;
5750
+ }
5751
+ /** `true` once the press has crossed the threshold and a rect is live. */
5752
+ get isActive() {
5753
+ return this.#dragging;
5754
+ }
5755
+ hostConnected() {
5756
+ this.#bindSurface();
5757
+ window.addEventListener(`pointermove`, this.#onPointerMove);
5758
+ window.addEventListener(`pointerup`, this.#onPointerUp);
5759
+ window.addEventListener(`pointercancel`, this.#onPointerCancel);
5760
+ }
5761
+ hostDisconnected() {
5762
+ this.#unbindSurface();
5763
+ window.removeEventListener(`pointermove`, this.#onPointerMove);
5764
+ window.removeEventListener(`pointerup`, this.#onPointerUp);
5765
+ window.removeEventListener(`pointercancel`, this.#onPointerCancel);
5766
+ this.#stopAutoScroll();
5767
+ this.#reset();
5768
+ }
5769
+ /** Re-bind when the surface element appears/changes (e.g. first render). */
5770
+ hostUpdated() {
5771
+ if ((this.#safeSurface() ?? this.#host) !== this.#boundSurface) this.#bindSurface();
5772
+ }
5773
+ #safeSurface() {
5774
+ try {
5775
+ return this.#config.getSurface();
5776
+ } catch {
5777
+ return;
5778
+ }
5779
+ }
5780
+ #bindSurface() {
5781
+ const next = this.#safeSurface() ?? this.#host;
5782
+ if (next === this.#boundSurface) return;
5783
+ this.#unbindSurface();
5784
+ this.#boundSurface = next;
5785
+ next.addEventListener(`pointerdown`, this.#onPointerDown);
5786
+ }
5787
+ #unbindSurface() {
5788
+ this.#boundSurface?.removeEventListener(`pointerdown`, this.#onPointerDown);
5789
+ this.#boundSurface = void 0;
5790
+ }
5791
+ #enabledTypes() {
5792
+ return this.#config.pointerTypes ?? DEFAULT_POINTER_TYPES;
5793
+ }
5794
+ #threshold() {
5795
+ return this.#config.threshold ?? DEFAULT_THRESHOLD;
5796
+ }
5797
+ #onPointerDown = (event) => {
5798
+ if (event.button !== 0) return;
5799
+ if (!this.#enabledTypes().includes(event.pointerType)) return;
5800
+ let enabled = false;
5801
+ try {
5802
+ enabled = this.#config.isEnabled();
5803
+ } catch {
5804
+ return;
5805
+ }
5806
+ if (!enabled) return;
5807
+ const target = event.target;
5808
+ if (target instanceof Element && typeof target.closest === `function`) {
5809
+ if (target.closest(`input,button,a,select,textarea,[contenteditable]`)) return;
5810
+ }
5811
+ if (this.#config.shouldStart) try {
5812
+ if (!this.#config.shouldStart(event)) return;
5813
+ } catch {
5814
+ return;
5815
+ }
5816
+ const box = (this.#safeSurface() ?? this.#host).getBoundingClientRect();
5817
+ this.#pointerId = event.pointerId;
5818
+ this.#startClientX = event.clientX;
5819
+ this.#startClientY = event.clientY;
5820
+ this.#currentClientX = event.clientX;
5821
+ this.#currentClientY = event.clientY;
5822
+ this.#startX = Math.min(Math.max(event.clientX - box.left, 0), Math.max(0, box.width));
5823
+ this.#startY = Math.min(Math.max(event.clientY - box.top, 0), Math.max(0, box.height));
5824
+ this.#dragging = false;
5825
+ this.#suppressClick = false;
5826
+ this.#base = /* @__PURE__ */ new Set();
5827
+ this.#lastMode = `replace`;
5828
+ this.#lastModifiers = {
5829
+ shiftKey: event.shiftKey,
5830
+ ctrlKey: event.ctrlKey,
5831
+ metaKey: event.metaKey
5832
+ };
5833
+ };
5834
+ /** Combine mode for the current modifiers: Shift adds, Cmd/Ctrl inverts. */
5835
+ #modeFor(modifiers) {
5836
+ if (modifiers.metaKey || modifiers.ctrlKey) return `invert`;
5837
+ if (modifiers.shiftKey) return `add`;
5838
+ return `replace`;
5839
+ }
5840
+ #onPointerMove = (event) => {
5841
+ if (this.#pointerId === void 0 || event.pointerId !== this.#pointerId) return;
5842
+ this.#currentClientX = event.clientX;
5843
+ this.#currentClientY = event.clientY;
5844
+ this.#lastModifiers = {
5845
+ shiftKey: event.shiftKey,
5846
+ ctrlKey: event.ctrlKey,
5847
+ metaKey: event.metaKey
5848
+ };
5849
+ const moved = Math.hypot(event.clientX - this.#startClientX, event.clientY - this.#startClientY);
5850
+ if (!this.#dragging) {
5851
+ if (moved < this.#threshold()) return;
5852
+ this.#dragging = true;
5853
+ try {
5854
+ this.#base = new Set(this.#config.getBaseSelection());
5855
+ } catch {
5856
+ this.#base = /* @__PURE__ */ new Set();
5857
+ }
5858
+ const surface = this.#safeSurface() ?? this.#host;
5859
+ try {
5860
+ if (typeof surface.setPointerCapture === `function`) surface.setPointerCapture(event.pointerId);
5861
+ } catch {}
5862
+ }
5863
+ try {
5864
+ event.preventDefault?.();
5865
+ } catch {}
5866
+ this.#updatePreview();
5867
+ this.#kickAutoScroll();
5868
+ };
5869
+ #surfaceBox() {
5870
+ const surface = this.#safeSurface() ?? this.#host;
5871
+ try {
5872
+ return surface.getBoundingClientRect();
5873
+ } catch {
5874
+ return;
5875
+ }
5876
+ }
5877
+ #currentRect() {
5878
+ const box = this.#surfaceBox();
5879
+ if (!box) return void 0;
5880
+ const cx = Math.min(Math.max(this.#currentClientX - box.left, 0), Math.max(0, box.width));
5881
+ const cy = Math.min(Math.max(this.#currentClientY - box.top, 0), Math.max(0, box.height));
5882
+ return {
5883
+ x: Math.min(this.#startX, cx),
5884
+ y: Math.min(this.#startY, cy),
5885
+ width: Math.abs(cx - this.#startX),
5886
+ height: Math.abs(cy - this.#startY)
5887
+ };
5888
+ }
5889
+ #updatePreview() {
5890
+ const rect = this.#currentRect();
5891
+ if (!rect) return;
5892
+ let hit = [];
5893
+ try {
5894
+ hit = this.#config.hitTest(rect);
5895
+ } catch {
5896
+ hit = [];
5897
+ }
5898
+ const mode = this.#modeFor(this.#lastModifiers);
5899
+ this.#lastMode = mode;
5900
+ const hitSet = new Set(hit);
5901
+ let preview;
5902
+ if (mode === `add`) preview = /* @__PURE__ */ new Set([...this.#base, ...hitSet]);
5903
+ else if (mode === `invert`) {
5904
+ const toggled = new Set(this.#base);
5905
+ for (const item of hitSet) if (toggled.has(item)) toggled.delete(item);
5906
+ else toggled.add(item);
5907
+ preview = toggled;
5908
+ } else preview = hitSet;
5909
+ this.#preview = preview;
5910
+ try {
5911
+ this.#config.onPreview(preview, rect);
5912
+ } catch {}
5913
+ }
5914
+ #onPointerUp = (event) => {
5915
+ if (this.#pointerId === void 0 || event.pointerId !== this.#pointerId) return;
5916
+ this.#lastModifiers = {
5917
+ shiftKey: event.shiftKey,
5918
+ ctrlKey: event.ctrlKey,
5919
+ metaKey: event.metaKey
5920
+ };
5921
+ const wasDragging = this.#dragging;
5922
+ const surface = this.#safeSurface() ?? this.#host;
5923
+ try {
5924
+ if (typeof surface.hasPointerCapture === `function` && typeof surface.releasePointerCapture === `function` && surface.hasPointerCapture(event.pointerId)) surface.releasePointerCapture(event.pointerId);
5925
+ } catch {}
5926
+ this.#stopAutoScroll();
5927
+ this.#pointerId = void 0;
5928
+ if (!wasDragging) {
5929
+ this.#reset();
5930
+ return;
5931
+ }
5932
+ this.#dragging = false;
5933
+ const final = this.#preview;
5934
+ try {
5935
+ this.#config.onCommit(final, { additive: this.#lastMode !== `replace` });
5936
+ } catch {}
5937
+ try {
5938
+ this.#config.onPreview(/* @__PURE__ */ new Set(), void 0);
5939
+ } catch {}
5940
+ this.#preview = /* @__PURE__ */ new Set();
5941
+ this.#suppressClick = true;
5942
+ window.setTimeout(() => {
5943
+ this.#suppressClick = false;
5944
+ }, 0);
5945
+ };
5946
+ #onPointerCancel = (event) => {
5947
+ if (this.#pointerId === void 0 || event.pointerId !== this.#pointerId) return;
5948
+ this.#stopAutoScroll();
5949
+ this.#pointerId = void 0;
5950
+ this.#dragging = false;
5951
+ this.#preview = /* @__PURE__ */ new Set();
5952
+ try {
5953
+ this.#config.onPreview(/* @__PURE__ */ new Set(), void 0);
5954
+ } catch {}
5955
+ this.#suppressClick = false;
5956
+ };
5957
+ #kickAutoScroll() {
5958
+ if (!this.#config.autoScroll || this.#raf !== 0) return;
5959
+ this.#raf = requestAnimationFrame(this.#autoScrollTick);
5960
+ }
5961
+ #autoScrollTick = () => {
5962
+ this.#raf = 0;
5963
+ if (!this.#dragging || this.#pointerId === void 0) return;
5964
+ const box = this.#surfaceBox();
5965
+ if (!box) return;
5966
+ const px = this.#currentClientX - box.left;
5967
+ const py = this.#currentClientY - box.top;
5968
+ let dx = 0;
5969
+ let dy = 0;
5970
+ if (px < EDGE_ZONE) dx = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, px) / EDGE_ZONE));
5971
+ else if (px > box.width - EDGE_ZONE) dx = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, box.width - px) / EDGE_ZONE));
5972
+ if (py < EDGE_ZONE) dy = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, py) / EDGE_ZONE));
5973
+ else if (py > box.height - EDGE_ZONE) dy = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, box.height - py) / EDGE_ZONE));
5974
+ if (dx === 0 && dy === 0) return;
5975
+ const autoScroll = this.#config.autoScroll;
5976
+ if (!autoScroll) return;
5977
+ try {
5978
+ autoScroll(dx, dy);
5979
+ } catch {}
5980
+ this.#updatePreview();
5981
+ this.#raf = requestAnimationFrame(this.#autoScrollTick);
5982
+ };
5983
+ #stopAutoScroll() {
5984
+ if (this.#raf !== 0) {
5985
+ cancelAnimationFrame(this.#raf);
5986
+ this.#raf = 0;
5987
+ }
5988
+ }
5989
+ #reset() {
5990
+ this.#dragging = false;
5991
+ this.#suppressClick = false;
5992
+ this.#base = /* @__PURE__ */ new Set();
5993
+ this.#lastMode = `replace`;
5994
+ this.#preview = /* @__PURE__ */ new Set();
5995
+ }
5996
+ };
5997
+ //#endregion
4565
5998
  //#region src/interaction/list-item-animation.ts
4566
5999
  /**
4567
6000
  * Shared insertion / removal animation for the list & tree components.
@@ -4800,6 +6233,31 @@ var ListSelectionController = class {
4800
6233
  this.#anchor = [...next].at(-1);
4801
6234
  this.#apply(next);
4802
6235
  }
6236
+ /**
6237
+ * Whether a drag / marquee gesture may build a selection in the current
6238
+ * modes: `selectionMode="multiple"`, or a multi interaction mode
6239
+ * (`checked` / `sticky` / `vscode`) outside `selectionMode="none"`.
6240
+ * Stricter than `canSelectMultiple()` — `single` + `standard` does not
6241
+ * qualify (a drag there would be a surprising multi-select).
6242
+ */
6243
+ canDragSelect() {
6244
+ return this.#config.getModes().selectionMode !== `none` && this.#effectiveMulti();
6245
+ }
6246
+ /**
6247
+ * Replace the selection with `items` from a drag / marquee gesture. Like
6248
+ * `selectMany` but gated on `canDragSelect()` instead of throwing, and a
6249
+ * no-op in single/none modes. Non-selectable items are dropped; the anchor
6250
+ * moves to the last item. Fires `onChange` once. An empty result clears a
6251
+ * non-empty selection (e.g. a Cmd-drag that toggles everything off) but is
6252
+ * a no-op when nothing is selected.
6253
+ */
6254
+ applyDragSelection(items) {
6255
+ if (!this.canDragSelect()) return;
6256
+ const next = new Set([...items].filter((t) => this.#config.isSelectable(t)));
6257
+ if (next.size === 0 && this.#selected.size === 0) return;
6258
+ this.#anchor = [...next].at(-1);
6259
+ this.#apply(next);
6260
+ }
4803
6261
  /** Remove a single item from the selection. */
4804
6262
  deselect(item) {
4805
6263
  if (!this.#selected.has(item)) return;
@@ -11892,6 +13350,7 @@ let DataGridElement = class DataGridElement extends i {
11892
13350
  static {
11893
13351
  this.styles = [
11894
13352
  themeFallbacks,
13353
+ handleStyles,
11895
13354
  tickledStyles,
11896
13355
  tickledItemStyles,
11897
13356
  listItemFadeKeyframes,
@@ -11935,7 +13394,8 @@ let DataGridElement = class DataGridElement extends i {
11935
13394
  .header-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
11936
13395
  .sort-icon { font-size: 10px; opacity: 0.7; }
11937
13396
  .resize-handle { position: absolute; right: 0; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 1; }
11938
- .reorder-handle { cursor: grab; opacity: 0.5; padding: 0 2px; }
13397
+ .reorder-handle { cursor: grab; opacity: 0.5; padding: 0 2px; display: inline-flex; align-items: center; }
13398
+ .reorder-handle .ixfx-handle { --ixfx-handle-spot-size: 14px; --ixfx-handle-dot-size: 3px; }
11939
13399
  .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); }
11940
13400
  .viewport { flex: 1; overflow: auto; position: relative; }
11941
13401
  .spacer { position: relative; }
@@ -12841,7 +14301,7 @@ let DataGridElement = class DataGridElement extends i {
12841
14301
  @pointerenter=${() => this._tickled.handlePointerEnter(col.key)}>
12842
14302
  ${col.reorderable !== false ? b`<span class="reorder-handle" draggable="false"
12843
14303
  @pointerdown=${(e) => this._beginHeaderDrag(e, col, idx)}
12844
- >⠿</span>` : A}
14304
+ ><span class="ixfx-handle" data-handle="dotted" data-shape="spot" part="reorder-handle"></span></span>` : A}
12845
14305
  <span class="header-label">${col.label}</span>
12846
14306
  ${this.sortState?.key === col.key ? b`<span class="sort-icon">${this.sortState.direction === `asc` ? `▲` : `▼`}</span>` : A}
12847
14307
  ${col.resizable !== false ? b`<span class="resize-handle" @pointerdown=${(e) => this._onResizePointerDown(e, col)} @click=${(e) => e.stopPropagation()} @dblclick=${(e) => {
@@ -13429,7 +14889,28 @@ let DetailListElement = class DetailListElement extends i {
13429
14889
  if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
13430
14890
  },
13431
14891
  onStateChange: () => this._syncItemStates(),
13432
- itemWrap: this.itemWrap
14892
+ itemWrap: this.itemWrap,
14893
+ hoverDisabled: () => this.#dragSelect.isDragging
14894
+ });
14895
+ this.#dragSelect = new ListDragSelectController(this, {
14896
+ getSurface: () => this.renderRoot.querySelector(`.container`),
14897
+ isEnabled: () => this.selection.canDragSelect() && this._getVisibleItems().length > 0 && !this.hasAttribute(`disable-drag-select`),
14898
+ hitTest: (rect) => this.#marqueeHitTest(rect),
14899
+ getBaseSelection: () => this.selection.selected,
14900
+ onPreview: (preview, rect) => {
14901
+ this.#marqueeRect = rect;
14902
+ for (const el of this._getVisibleItems()) el.toggleAttribute(`data-marquee`, preview.has(el));
14903
+ this.requestUpdate();
14904
+ },
14905
+ onCommit: (finalSet) => {
14906
+ this.selection.applyDragSelection(finalSet);
14907
+ for (const el of this._getVisibleItems()) el.removeAttribute(`data-marquee`);
14908
+ this.#marqueeRect = void 0;
14909
+ this.requestUpdate();
14910
+ },
14911
+ autoScroll: (dx, _dy) => {
14912
+ if (this._listEl) this._listEl.scrollLeft += dx;
14913
+ }
13433
14914
  });
13434
14915
  this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
13435
14916
  itemSelector: ITEM_SELECTOR$3,
@@ -13506,6 +14987,7 @@ let DetailListElement = class DetailListElement extends i {
13506
14987
  }
13507
14988
  };
13508
14989
  this._onClick = (e) => {
14990
+ if (this.#dragSelect.isDragging) return;
13509
14991
  const item = this._findListItem(e.composedPath());
13510
14992
  if (!item) return;
13511
14993
  this.focus();
@@ -13532,6 +15014,30 @@ let DetailListElement = class DetailListElement extends i {
13532
15014
  #builtFromProvider;
13533
15015
  #providerBuilt;
13534
15016
  #data;
15017
+ /** Live drag-select preview rect (surface-relative); the covered items carry `data-marquee`. */
15018
+ #marqueeRect;
15019
+ #dragSelect;
15020
+ /** Collect visible items whose surface-relative box intersects `rect`. */
15021
+ #marqueeHitTest(rect) {
15022
+ const surface = this.renderRoot.querySelector(`.container`);
15023
+ if (!surface) return [];
15024
+ const box = surface.getBoundingClientRect();
15025
+ const rx0 = rect.x;
15026
+ const ry0 = rect.y;
15027
+ const rx1 = rect.x + rect.width;
15028
+ const ry1 = rect.y + rect.height;
15029
+ const out = [];
15030
+ for (const el of this._getVisibleItems()) {
15031
+ const r = el.getBoundingClientRect();
15032
+ const left = r.left - box.left;
15033
+ const top = r.top - box.top;
15034
+ if (left <= rx1 && left + r.width >= rx0 && top <= ry1 && top + r.height >= ry0) out.push(el);
15035
+ }
15036
+ return out;
15037
+ }
15038
+ #marqueeStyle(rect) {
15039
+ return `left:${rect.x}px;top:${rect.y}px;width:${Math.max(0, rect.width)}px;height:${Math.max(0, rect.height)}px`;
15040
+ }
13535
15041
  #searchRegistry;
13536
15042
  #unregisterSearchCommands;
13537
15043
  /** Command registry carrying this list's `list.search.*` commands. */
@@ -14304,6 +15810,22 @@ let DetailListElement = class DetailListElement extends i {
14304
15810
  outline-offset: -2px;
14305
15811
  }
14306
15812
 
15813
+ /* Drag-select (marquee) rectangle + covered-row preview.
15814
+ Style hooks: part="marquee", --detail-list-marquee-border,
15815
+ --detail-list-marquee-fill. */
15816
+ .marquee {
15817
+ position: absolute;
15818
+ pointer-events: none;
15819
+ z-index: 5;
15820
+ border: 1px solid var(--detail-list-marquee-border, var(--accent));
15821
+ background: var(--detail-list-marquee-fill, color-mix(in srgb, var(--accent) 15%, transparent));
15822
+ }
15823
+
15824
+ ::slotted([data-marquee]) {
15825
+ background: var(--item-bg-selected);
15826
+ color: var(--item-text-selected);
15827
+ }
15828
+
14307
15829
  /* checked mode: the injected checkbox communicates selection — suppress bg */
14308
15830
  :host([interaction-mode="checked"]) ::slotted([data-selected]) {
14309
15831
  background: transparent;
@@ -14344,6 +15866,7 @@ let DetailListElement = class DetailListElement extends i {
14344
15866
  <ul part="list" class="scroll-fade-x">
14345
15867
  <slot></slot>
14346
15868
  </ul>
15869
+ ${this.#marqueeRect ? b`<div class="marquee" part="marquee" style=${this.#marqueeStyle(this.#marqueeRect)}></div>` : ``}
14347
15870
  <div class="loading" part="loading" ?hidden=${!loading}>
14348
15871
  ${loading ? progress !== void 0 ? `${Math.round(progress * 100)}%` : `loading…` : A}
14349
15872
  </div>
@@ -15997,16 +17520,20 @@ let SplitLayoutElement = class SplitLayoutElement extends i {
15997
17520
  style="cursor: ${cursor}; grid-column: ${gridColumn}; grid-row: ${gridRow};"
15998
17521
  @pointerdown=${(e) => this._onGutterPointerDown(e, groupId, index)}
15999
17522
  >
16000
- <div class="grab-handle">
16001
- <span class="dot"></span>
16002
- <span class="dot"></span>
16003
- <span class="dot"></span>
16004
- </div>
17523
+ <span
17524
+ class="ixfx-handle"
17525
+ part="grip"
17526
+ data-handle="dimpled"
17527
+ data-shape=${isRow ? `vertical` : `horizontal`}
17528
+ ></span>
16005
17529
  </div>
16006
17530
  `;
16007
17531
  }
16008
17532
  static {
16009
- this.styles = [themeFallbacks, i$3`
17533
+ this.styles = [
17534
+ themeFallbacks,
17535
+ handleStyles,
17536
+ i$3`
16010
17537
  :host {
16011
17538
  display: block;
16012
17539
  width: 100%;
@@ -16017,10 +17544,10 @@ let SplitLayoutElement = class SplitLayoutElement extends i {
16017
17544
  --split-gutter-active-color: var(--accent);
16018
17545
  --split-collapsed-size: 0px;
16019
17546
  --split-snap: 24px;
16020
- --split-grab-color: var(--border-subtle-color);
16021
- --split-grab-dot-size: 3px;
16022
- --split-grab-size: 16px;
16023
- --split-grab-thickness: 8px;
17547
+
17548
+ /* Gutter grip — shared handle primitive (docs/infra-handle.md). */
17549
+ --ixfx-handle-length: 16px;
17550
+ --ixfx-handle-thickness: 8px;
16024
17551
  }
16025
17552
 
16026
17553
  * {
@@ -16082,31 +17609,15 @@ let SplitLayoutElement = class SplitLayoutElement extends i {
16082
17609
  user-select: none;
16083
17610
  }
16084
17611
 
16085
- .grab-handle .dot {
16086
- width: var(--split-grab-dot-size);
16087
- height: var(--split-grab-dot-size);
16088
- border-radius: 50%;
16089
- background: var(--split-grab-color);
16090
- }
16091
-
16092
- :host([direction="row"]) .grab-handle {
16093
- flex-direction: column;
16094
- gap: var(--split-grab-dot-size);
16095
- }
16096
-
16097
- :host([direction="column"]) .grab-handle {
16098
- flex-direction: row;
16099
- gap: var(--split-grab-dot-size);
16100
- }
16101
-
16102
- :host([data-dragging]) .grab-handle {
17612
+ :host([data-dragging]) .gutter .ixfx-handle {
16103
17613
  display: none;
16104
17614
  }
16105
17615
 
16106
- :host(:not([data-dragging])) .gutter:hover .grab-handle {
16107
- display: flex;
17616
+ :host(:not([data-dragging])) .gutter:hover .ixfx-handle {
17617
+ display: none;
16108
17618
  }
16109
- `];
17619
+ `
17620
+ ];
16110
17621
  }
16111
17622
  };
16112
17623
  __decorate([property({
@@ -17053,404 +18564,6 @@ __decorate([state()], TabListItemElement.prototype, "_compact", void 0);
17053
18564
  __decorate([state()], TabListItemElement.prototype, "_hasToolbarContent", void 0);
17054
18565
  TabListItemElement = __decorate([safeCustomElement(`ixfx-tab-list-item`)], TabListItemElement);
17055
18566
  //#endregion
17056
- //#region src/titlebar/titlebar.ts
17057
- let TitlebarElement = class TitlebarElement extends i {
17058
- constructor(..._args) {
17059
- super(..._args);
17060
- this.title = ``;
17061
- this.iconName = ``;
17062
- this.orientation = `horizontal`;
17063
- this.variant = `titlebar`;
17064
- this.closable = false;
17065
- this.collapsible = false;
17066
- this.collapsed = false;
17067
- this.toggleInteraction = `implicit`;
17068
- }
17069
- _onToggleClick() {
17070
- if (!this.collapsible) return;
17071
- this.collapsed = !this.collapsed;
17072
- this.dispatchEvent(new CustomEvent(`toggle`, {
17073
- detail: { collapsed: this.collapsed },
17074
- bubbles: true,
17075
- composed: true
17076
- }));
17077
- }
17078
- _onCloseClick() {
17079
- this.dispatchEvent(new CustomEvent(`close`, {
17080
- bubbles: true,
17081
- composed: true
17082
- }));
17083
- }
17084
- _getCaretIcon() {
17085
- return `chevron-down`;
17086
- }
17087
- render() {
17088
- const caretIcon = this._getCaretIcon();
17089
- const isVertical = this.orientation === `vertical`;
17090
- const showCaret = this.collapsible && this.toggleInteraction !== `disabled`;
17091
- const implicitToggle = this.collapsible && this.toggleInteraction === `implicit`;
17092
- const caretRotation = isVertical ? this.collapsed ? `` : `-90deg` : this.collapsed ? `-90deg` : ``;
17093
- const treeVariant = this.variant === `tree`;
17094
- return b`
17095
- <div
17096
- class="bar"
17097
- role="toolbar"
17098
- @click=${implicitToggle ? this._onToggleClick : A}
17099
- >
17100
- ${treeVariant && showCaret ? b`
17101
- <button
17102
- class="caret-btn"
17103
- type="button"
17104
- aria-label=${this.collapsed ? `Expand panel` : `Collapse panel`}
17105
- @click=${this._onToggleClick}
17106
- >
17107
- <ixfx-icon name=${caretIcon} prominence .rotate=${caretRotation.length > 0 ? caretRotation : A}></ixfx-icon>
17108
- </button>
17109
- ` : A}
17110
- <div class="title-text-wrapper">
17111
- ${this.iconName ? b`<ixfx-icon class="title-icon" name=${this.iconName}></ixfx-icon>` : A}
17112
- <span class="title-text">${this.title}</span>
17113
- </div>
17114
-
17115
- <div class="toolbar" @click=${(e) => e.stopPropagation()}>
17116
- <slot name="toolbar"></slot>
17117
- ${this.closable ? b`
17118
- <button
17119
- class="close-btn"
17120
- type="button"
17121
- aria-label="Close panel"
17122
- @click=${this._onCloseClick}
17123
- >
17124
- <ixfx-icon name="close"></ixfx-icon>
17125
- </button>
17126
- ` : A}
17127
- ${!treeVariant && showCaret ? b`
17128
- <button
17129
- class="caret-btn"
17130
- type="button"
17131
- aria-label=${this.collapsed ? `Expand panel` : `Collapse panel`}
17132
- @click=${this._onToggleClick}
17133
- >
17134
- <ixfx-icon name=${caretIcon} prominence .rotate=${caretRotation.length > 0 ? caretRotation : A}></ixfx-icon>
17135
- </button>
17136
- ` : A}
17137
- </div>
17138
- </div>
17139
- `;
17140
- }
17141
- static {
17142
- this.styles = [themeFallbacks, i$3`
17143
- :host {
17144
- display: block;
17145
- box-sizing: border-box;
17146
- }
17147
-
17148
- .bar {
17149
- display: flex;
17150
- flex-direction: row;
17151
- align-items: center;
17152
- padding: var(--space-s) var(--space-m);
17153
- background: var(--surface-4);
17154
- color: var(--surface-3-text);
17155
- min-height: 2em;
17156
- height: 100%;
17157
- flex-shrink: 0;
17158
- box-sizing: border-box;
17159
- }
17160
-
17161
- :host([orientation="vertical"]) .bar {
17162
- flex-direction: row;
17163
- min-width: 2em;
17164
- writing-mode: vertical-rl;
17165
- text-orientation: mixed;
17166
- }
17167
-
17168
- .caret-btn,
17169
- .close-btn {
17170
- display: inline-flex;
17171
- align-items: center;
17172
- justify-content: center;
17173
- background: none;
17174
- border: none;
17175
- padding: var(--space-xs);
17176
- margin: 0;
17177
- cursor: pointer;
17178
- color: inherit;
17179
- border-radius: var(--radius-s);
17180
- transition: background var(--transition);
17181
- opacity: var(--prominence-idle-opacity);
17182
- }
17183
-
17184
- .caret-btn:hover,
17185
- .close-btn:hover {
17186
- background: var(--surface-5);
17187
- opacity: var(--prominence-tickled-opacity);
17188
- }
17189
-
17190
- .caret-btn:focus-visible,
17191
- .close-btn:focus-visible {
17192
- outline: 2px solid var(--accent);
17193
- outline-offset: 1px;
17194
- }
17195
-
17196
- ixfx-icon {
17197
- width: 1.25em;
17198
- height: 1.25em;
17199
- transition: transform var(--transition);
17200
- }
17201
-
17202
- .title-icon {
17203
- flex-shrink: 0;
17204
- }
17205
-
17206
- .title-text {
17207
- flex: 1;
17208
- padding-left: 1ch;
17209
- font-family: var(--font-family);
17210
- font-size: var(--text-m);
17211
- font-weight: var(--font-weight-bold, 600);
17212
- white-space: nowrap;
17213
- overflow: hidden;
17214
- align-self: center;
17215
- text-overflow: ellipsis;
17216
- user-select: none;
17217
- -webkit-user-select: none;
17218
- }
17219
-
17220
- :host([collapsible][toggle-interaction="implicit"]) .bar {
17221
- cursor: pointer;
17222
- }
17223
-
17224
- :host([orientation="vertical"]) .title-text {
17225
- writing-mode: vertical-rl;
17226
- padding-left: 0;
17227
- padding-bottom: 1ch;
17228
- text-orientation: mixed;
17229
- transform: rotate(180deg);
17230
- }
17231
-
17232
- .toolbar {
17233
- display: flex;
17234
- align-items: center;
17235
- gap: var(--space-xs);
17236
- margin-left: auto;
17237
- }
17238
-
17239
- :host([orientation="vertical"]) .toolbar {
17240
- flex-direction: column;
17241
- margin-left: 0;
17242
- margin-top: auto;
17243
- writing-mode: horizontal-tb;
17244
- }
17245
-
17246
- slot[name="toolbar"] {
17247
- display: contents;
17248
- }
17249
-
17250
- ::slotted(slot[name="toolbar"]) {
17251
- display: contents;
17252
- }
17253
-
17254
- .title-text-wrapper {
17255
- display: flex;
17256
- padding: 0 var(--space-s);
17257
- }
17258
-
17259
- /* === Midline variant === */
17260
-
17261
- :host([variant="midline"]) .bar {
17262
- background: transparent;
17263
- padding: var(--space-xs) var(--space-m);
17264
- min-height: unset;
17265
- position: relative;
17266
- }
17267
-
17268
- :host([variant="midline"][orientation="vertical"]) .bar {
17269
- padding: var(--space-m) var(--space-xs);
17270
- min-width: unset;
17271
- }
17272
-
17273
- :host([variant="midline"]) .title-text-wrapper {
17274
- position: relative;
17275
- z-index: 1;
17276
- display: flex;
17277
- align-items: center;
17278
- gap: var(--space-s);
17279
- background: var(--surface-3);
17280
- }
17281
-
17282
- :host([variant="midline"]) .title-text {
17283
- font-weight: normal;
17284
- color: var(--surface-3-text);
17285
- padding: 0 0.5ch 0 0.5ch;
17286
- background: transparent;
17287
- box-shadow: none;
17288
- flex: unset;
17289
- width: auto;
17290
- white-space: nowrap;
17291
- }
17292
-
17293
- :host([variant="midline"]) .title-icon {
17294
- background: transparent;
17295
- flex-shrink: 0;
17296
- }
17297
-
17298
- :host([variant="midline"]) .caret-btn {
17299
- position: relative;
17300
- z-index: 1;
17301
- background: var(--surface-3);
17302
- opacity: 1;
17303
- }
17304
-
17305
- :host([variant="midline"][orientation="vertical"]) .title-text {
17306
- transform: rotate(180deg);
17307
- padding: 0.5ch 0 0.5ch 0ch;
17308
- }
17309
-
17310
- :host([variant="midline"]) .toolbar {
17311
- position: relative;
17312
- z-index: 2;
17313
- background: var(--surface-3);
17314
- margin-left: 0;
17315
- }
17316
-
17317
- :host([variant="midline"][orientation="horizontal"]) .toolbar {
17318
- margin-left: auto;
17319
- }
17320
-
17321
- /* Debossed line runs from after title-text to before toolbar */
17322
- :host([variant="midline"]) .bar::after {
17323
- content: '';
17324
- position: absolute;
17325
- top: 50%;
17326
- left: 0;
17327
- right: 0;
17328
- height: 1px;
17329
- transform: translateY(-50%);
17330
- background: var(--surface-2);
17331
- box-shadow:
17332
- 0 -1px 0 var(--surface-5),
17333
- 0 1px 0 var(--surface-1);
17334
- z-index: 0;
17335
- }
17336
-
17337
- /* For vertical midline, line runs vertically */
17338
- :host([variant="midline"][orientation="vertical"]) .bar::after {
17339
- top: 0;
17340
- bottom: 0;
17341
- left: 50%;
17342
- right: unset;
17343
- width: 1px;
17344
- height: 100%;
17345
- transform: translateX(-50%);
17346
- box-shadow:
17347
- -1px 0 0 var(--surface-5),
17348
- 1px 0 0 var(--surface-1);
17349
- }
17350
-
17351
- /* === Tree variant: VS Code explorer / Outline style === */
17352
- :host([variant="tree"]) .bar {
17353
- background: transparent;
17354
- padding: 0 var(--space-xs);
17355
- min-height: unset;
17356
- gap: var(--space-xs);
17357
- }
17358
-
17359
- :host([variant="tree"][orientation="vertical"]) .bar {
17360
- padding: var(--space-xs) 0;
17361
- flex-direction: column;
17362
- min-width: unset;
17363
- writing-mode: horizontal-tb;
17364
- }
17365
-
17366
- :host([variant="tree"]) .title-text-wrapper {
17367
- display: inline-flex;
17368
- align-items: center;
17369
- gap: var(--space-xs);
17370
- padding: 0;
17371
- background: transparent;
17372
- }
17373
-
17374
- :host([variant="tree"]) .title-icon {
17375
- background: transparent;
17376
- flex-shrink: 0;
17377
- }
17378
-
17379
- :host([variant="tree"]) .title-text {
17380
- font-weight: normal;
17381
- color: var(--surface-3-text);
17382
- padding: 0;
17383
- background: transparent;
17384
- box-shadow: none;
17385
- flex: unset;
17386
- width: auto;
17387
- white-space: nowrap;
17388
- }
17389
-
17390
- :host([variant="tree"][orientation="vertical"]) .title-text {
17391
- writing-mode: vertical-rl;
17392
- transform: rotate(180deg);
17393
- padding: 0;
17394
- }
17395
-
17396
- :host([variant="tree"]) .caret-btn {
17397
- background: transparent;
17398
- opacity: var(--prominence-idle-opacity);
17399
- padding: 0;
17400
- }
17401
-
17402
- :host([variant="tree"]) .caret-btn:hover {
17403
- background: transparent;
17404
- opacity: var(--prominence-tickled-opacity);
17405
- }
17406
-
17407
- :host([variant="tree"]) .toolbar {
17408
- margin-left: auto;
17409
- }
17410
-
17411
- :host([variant="tree"][orientation="vertical"]) .toolbar {
17412
- flex-direction: row;
17413
- margin-left: 0;
17414
- margin-top: auto;
17415
- }
17416
-
17417
- :host([variant="tree"]) ixfx-icon {
17418
- width: 1em;
17419
- height: 1em;
17420
- }
17421
- `];
17422
- }
17423
- };
17424
- __decorate([property({ type: String })], TitlebarElement.prototype, "title", void 0);
17425
- __decorate([property({
17426
- type: String,
17427
- attribute: `icon-name`
17428
- })], TitlebarElement.prototype, "iconName", void 0);
17429
- __decorate([property({
17430
- type: String,
17431
- attribute: `orientation`,
17432
- reflect: true
17433
- })], TitlebarElement.prototype, "orientation", void 0);
17434
- __decorate([property({
17435
- type: String,
17436
- attribute: `variant`,
17437
- reflect: true
17438
- })], TitlebarElement.prototype, "variant", void 0);
17439
- __decorate([property({ type: Boolean })], TitlebarElement.prototype, "closable", void 0);
17440
- __decorate([property({
17441
- type: Boolean,
17442
- reflect: true
17443
- })], TitlebarElement.prototype, "collapsible", void 0);
17444
- __decorate([property({
17445
- type: Boolean,
17446
- reflect: true
17447
- })], TitlebarElement.prototype, "collapsed", void 0);
17448
- __decorate([property({
17449
- type: String,
17450
- attribute: `toggle-interaction`
17451
- })], TitlebarElement.prototype, "toggleInteraction", void 0);
17452
- TitlebarElement = __decorate([safeCustomElement(`ixfx-titlebar`)], TitlebarElement);
17453
- //#endregion
17454
18567
  //#region src/dock/dock-panel.ts
17455
18568
  /** Pointer movement threshold before a titlebar press becomes a dock drag. */
17456
18569
  const DRAG_THRESHOLD_PX$3 = 5;
@@ -18690,7 +19803,7 @@ let DockHostElement = class DockHostElement extends i {
18690
19803
  role="separator"
18691
19804
  aria-label="Resize adjacent panels"
18692
19805
  @pointerdown=${(e) => this._onJunctionPointerDown(e, junction)}
18693
- ></div>
19806
+ ><span class="ixfx-handle" data-handle="dotted" data-shape="spot" part="junction-handle"></span></div>
18694
19807
  `)}
18695
19808
  </div>
18696
19809
  <slot @slotchange=${this._onPoolSlotChange}></slot>
@@ -18784,7 +19897,10 @@ let DockHostElement = class DockHostElement extends i {
18784
19897
  };
18785
19898
  }
18786
19899
  static {
18787
- this.styles = [themeFallbacks, i$3`
19900
+ this.styles = [
19901
+ themeFallbacks,
19902
+ handleStyles,
19903
+ i$3`
18788
19904
  :host {
18789
19905
  display: block;
18790
19906
  width: 100%;
@@ -18912,26 +20028,23 @@ let DockHostElement = class DockHostElement extends i {
18912
20028
  border-radius: 50%;
18913
20029
  cursor: nwse-resize;
18914
20030
  pointer-events: auto;
20031
+ display: flex;
20032
+ align-items: center;
20033
+ justify-content: center;
18915
20034
  }
18916
20035
 
18917
- .junction-handle::after {
18918
- content: '';
18919
- position: absolute;
18920
- inset: 4px;
18921
- border-radius: 50%;
18922
- background: var(--dock-junction-color, var(--accent));
18923
- opacity: 0;
18924
- transition: opacity var(--transition);
18925
- }
18926
-
18927
- .junction-handle:hover::after {
18928
- opacity: 0.9;
20036
+ .junction-handle .ixfx-handle {
20037
+ --ixfx-handle-spot-size: 12px;
20038
+ --ixfx-handle-dot-size: 3px;
20039
+ --ixfx-handle-opacity: 0;
20040
+ --ixfx-handle-opacity-active: 0.9;
18929
20041
  }
18930
20042
 
18931
20043
  slot:not([name]) {
18932
20044
  display: none;
18933
20045
  }
18934
- `];
20046
+ `
20047
+ ];
18935
20048
  }
18936
20049
  };
18937
20050
  __decorate([property({
@@ -20401,6 +21514,233 @@ var DataGridListSource = class {
20401
21514
  }
20402
21515
  };
20403
21516
  //#endregion
21517
+ //#region src/pan-text/pan-text.ts
21518
+ const SPEED = 45;
21519
+ const END_PADDING = 4;
21520
+ const START_DELAY = 300;
21521
+ const END_DWELL = 2e3;
21522
+ const RESET_DURATION = 400;
21523
+ const PAN_EASING = `cubic-bezier(0.25, 0.46, 0.45, 0.94)`;
21524
+ let PanTextElement = class PanTextElement extends i {
21525
+ constructor(..._args) {
21526
+ super(..._args);
21527
+ this.direction = `end`;
21528
+ this.disabled = false;
21529
+ this.mode = `hover`;
21530
+ this._isOverflowing = false;
21531
+ this._isHovering = false;
21532
+ this._loopActive = false;
21533
+ this._loopAtEnd = false;
21534
+ this._overflow = 0;
21535
+ this._resizeObserver = null;
21536
+ this._loopTimer = 0;
21537
+ this._onMouseEnter = () => {
21538
+ this._isHovering = true;
21539
+ };
21540
+ this._onMouseLeave = () => {
21541
+ this._isHovering = false;
21542
+ };
21543
+ this._measure = () => {
21544
+ if (this._isHovering || this._loopAtEnd) return;
21545
+ const el = this._textEl;
21546
+ if (!el) return;
21547
+ this._overflow = Math.max(0, el.scrollWidth - el.clientWidth);
21548
+ const overflowing = this._overflow > 0;
21549
+ this.classList.toggle(`is-overflowing`, overflowing);
21550
+ if (overflowing !== this._isOverflowing) this._isOverflowing = overflowing;
21551
+ if (this._loopActive && this.mode === `loop` && !this.disabled && overflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
21552
+ if (!overflowing && this._loopAtEnd) {
21553
+ this._loopAtEnd = false;
21554
+ this.requestUpdate();
21555
+ }
21556
+ };
21557
+ }
21558
+ connectedCallback() {
21559
+ super.connectedCallback();
21560
+ this._resizeObserver = new ResizeObserver(() => this._measure());
21561
+ this.addEventListener(`mouseenter`, this._onMouseEnter);
21562
+ this.addEventListener(`mouseleave`, this._onMouseLeave);
21563
+ }
21564
+ disconnectedCallback() {
21565
+ super.disconnectedCallback();
21566
+ this._clearLoopTimer();
21567
+ this._loopActive = false;
21568
+ this._loopAtEnd = false;
21569
+ this._resizeObserver?.disconnect();
21570
+ this._resizeObserver = null;
21571
+ this.removeEventListener(`mouseenter`, this._onMouseEnter);
21572
+ this.removeEventListener(`mouseleave`, this._onMouseLeave);
21573
+ }
21574
+ firstUpdated() {
21575
+ this._measure();
21576
+ this._resizeObserver?.observe(this);
21577
+ document.fonts?.ready.then(() => this._measure());
21578
+ if (this.mode === `loop`) this.startLoop();
21579
+ }
21580
+ updated(changed) {
21581
+ if (changed.has(`mode`)) {
21582
+ if (this.mode === `loop`) this.startLoop();
21583
+ else this.stopLoop();
21584
+ }
21585
+ if (changed.has(`disabled`) && this.disabled) this.stopLoop();
21586
+ this._measure();
21587
+ }
21588
+ /**
21589
+ * Whether a loop pan is currently active (between `startLoop()` and `stopLoop()`).
21590
+ */
21591
+ get looping() {
21592
+ return this._loopActive;
21593
+ }
21594
+ /**
21595
+ * Begin looping: pan to the end, dwell there for 2 seconds, reset to the
21596
+ * start and pan again, repeating until `stopLoop()` is called. A no-op when
21597
+ * `disabled`, when there is nothing to reveal, or under reduced-motion.
21598
+ */
21599
+ startLoop() {
21600
+ if (this.disabled || this._prefersReducedMotion()) return;
21601
+ this._loopActive = true;
21602
+ this._loopAtEnd = false;
21603
+ this.requestUpdate();
21604
+ if (this._isOverflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
21605
+ }
21606
+ /**
21607
+ * Stop a loop started with `startLoop()` and snap back to the rest position.
21608
+ */
21609
+ stopLoop() {
21610
+ if (!this._loopActive && !this._loopAtEnd && this._loopTimer === 0) return;
21611
+ this._clearLoopTimer();
21612
+ this._loopActive = false;
21613
+ this._loopAtEnd = false;
21614
+ this.requestUpdate();
21615
+ }
21616
+ _prefersReducedMotion() {
21617
+ return globalThis.matchMedia?.(`(prefers-reduced-motion: reduce)`).matches ?? false;
21618
+ }
21619
+ _panDuration() {
21620
+ return Math.max(1.5, (this._overflow + END_PADDING) / SPEED);
21621
+ }
21622
+ _clearLoopTimer() {
21623
+ if (this._loopTimer !== 0) {
21624
+ clearTimeout(this._loopTimer);
21625
+ this._loopTimer = 0;
21626
+ }
21627
+ }
21628
+ _scheduleLoopPan(delay) {
21629
+ this._clearLoopTimer();
21630
+ this._loopTimer = window.setTimeout(() => {
21631
+ this._loopTimer = 0;
21632
+ if (!this._loopActive || this.disabled || !this._isOverflowing) return;
21633
+ this._loopAtEnd = true;
21634
+ this.requestUpdate();
21635
+ const dwell = this._panDuration() * 1e3 + END_DWELL;
21636
+ this._loopTimer = window.setTimeout(() => {
21637
+ this._loopTimer = 0;
21638
+ if (!this._loopActive) return;
21639
+ this._loopAtEnd = false;
21640
+ this.requestUpdate();
21641
+ this._scheduleLoopPan(700);
21642
+ }, dwell);
21643
+ }, delay);
21644
+ }
21645
+ _textStyle() {
21646
+ if (!this._isOverflowing) return ``;
21647
+ const distance = this._overflow + END_PADDING;
21648
+ const duration = Math.max(1.5, distance / SPEED);
21649
+ const revealEnd = this.direction === `end`;
21650
+ const restX = revealEnd ? 0 : -distance;
21651
+ const hoverX = revealEnd ? -distance : 0;
21652
+ if (this._loopActive && this.mode === `loop`) {
21653
+ if (this._loopAtEnd) return [
21654
+ `transform: translateX(${hoverX}px)`,
21655
+ `transition: transform ${duration}s ${PAN_EASING}`,
21656
+ `overflow: visible`,
21657
+ `text-overflow: clip`
21658
+ ].join(`; `);
21659
+ return [
21660
+ `transform: translateX(${restX}px)`,
21661
+ `transition: transform ${RESET_DURATION}ms ${PAN_EASING}`,
21662
+ revealEnd ? `` : `overflow: visible; text-overflow: clip`
21663
+ ].filter(Boolean).join(`; `);
21664
+ }
21665
+ if (this._isHovering) return [
21666
+ `transform: translateX(${hoverX}px)`,
21667
+ `transition: transform ${duration}s ${PAN_EASING} ${START_DELAY}ms`,
21668
+ `overflow: visible`,
21669
+ `text-overflow: clip`
21670
+ ].join(`; `);
21671
+ return [
21672
+ `transform: translateX(${restX}px)`,
21673
+ `transition: transform 400ms ${PAN_EASING}`,
21674
+ revealEnd ? `` : `overflow: visible; text-overflow: clip`
21675
+ ].filter(Boolean).join(`; `);
21676
+ }
21677
+ render() {
21678
+ const textClasses = {
21679
+ "is-overflowing": this._isOverflowing,
21680
+ "is-hovering": this._isHovering
21681
+ };
21682
+ return b`
21683
+ <span class="text ${classMap(textClasses)}" part="text" style=${this._textStyle()}>
21684
+ <slot @slotchange=${this._measure}></slot>
21685
+ </span>
21686
+ `;
21687
+ }
21688
+ static {
21689
+ this.styles = [themeFallbacks, i$3`
21690
+ /* Renders like plain inline text: no colours, border, padding or margin
21691
+ of its own — it only adds the clipping needed to pan on hover. */
21692
+ :host {
21693
+ display: inline-block;
21694
+ overflow: hidden;
21695
+ white-space: nowrap;
21696
+ max-width: 100%;
21697
+ box-sizing: border-box;
21698
+ font: inherit;
21699
+ color: inherit;
21700
+ }
21701
+
21702
+ :host([disabled]) {
21703
+ opacity: var(--disabled-opacity, 0.5);
21704
+ pointer-events: none;
21705
+ }
21706
+
21707
+ .text {
21708
+ display: block;
21709
+ overflow: hidden;
21710
+ text-overflow: ellipsis;
21711
+ white-space: nowrap;
21712
+ will-change: transform;
21713
+ }
21714
+
21715
+ @media (prefers-reduced-motion: reduce) {
21716
+ .text {
21717
+ transition: none !important;
21718
+ transform: none !important;
21719
+ }
21720
+ }
21721
+ `];
21722
+ }
21723
+ };
21724
+ __decorate([property({
21725
+ type: String,
21726
+ attribute: `direction`
21727
+ })], PanTextElement.prototype, "direction", void 0);
21728
+ __decorate([property({
21729
+ type: Boolean,
21730
+ attribute: `disabled`
21731
+ })], PanTextElement.prototype, "disabled", void 0);
21732
+ __decorate([property({
21733
+ type: String,
21734
+ attribute: `mode`,
21735
+ reflect: true
21736
+ })], PanTextElement.prototype, "mode", void 0);
21737
+ __decorate([state()], PanTextElement.prototype, "_isOverflowing", void 0);
21738
+ __decorate([state()], PanTextElement.prototype, "_isHovering", void 0);
21739
+ __decorate([state()], PanTextElement.prototype, "_loopActive", void 0);
21740
+ __decorate([state()], PanTextElement.prototype, "_loopAtEnd", void 0);
21741
+ __decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
21742
+ PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
21743
+ //#endregion
20404
21744
  //#region src/grid-list/grid-list.ts
20405
21745
  const ITEM_SELECTOR$1 = `:scope > *`;
20406
21746
  const MIN_ITEM_SIZE = 16;
@@ -20496,6 +21836,7 @@ let GridListElement = class GridListElement extends i {
20496
21836
  this.captionBackdrop = `scrim`;
20497
21837
  this.captionHeight = 24;
20498
21838
  this.itemWrap = `stop`;
21839
+ this.itemsDraggable = false;
20499
21840
  this.items = [];
20500
21841
  this._scrollTop = 0;
20501
21842
  this._scrollLeft = 0;
@@ -20532,11 +21873,56 @@ let GridListElement = class GridListElement extends i {
20532
21873
  enabled: this.#source.isSelectable(i)
20533
21874
  })),
20534
21875
  onTickle: (k) => {
21876
+ this.#stopActiveCaptionPan();
20535
21877
  if (k !== void 0) this.#emitItemEvent(`list-tickle`, k);
20536
21878
  },
20537
21879
  onStateChange: () => this._syncItemStates(),
20538
21880
  itemWrap: this.#itemWrap,
20539
- hoverDisabled: () => this.disableHoverTickle
21881
+ hoverDisabled: () => this.disableHoverTickle || this.#dragSelect.isDragging
21882
+ });
21883
+ this.#marqueePreview = /* @__PURE__ */ new Set();
21884
+ this.#nativeDragging = false;
21885
+ this.#dragOriginKey = void 0;
21886
+ this.#postDragSuppressTimer = 0;
21887
+ this.#activeCaptionPan = null;
21888
+ this.#dragSelect = new ListDragSelectController(this, {
21889
+ getSurface: () => this.#viewportRef.value,
21890
+ isEnabled: () => this.selection.canDragSelect() && this.#source.count > 0 && !this.hasAttribute(`disable-drag-select`),
21891
+ hitTest: (rect) => this.#marqueeHitTest(rect),
21892
+ getBaseSelection: () => this.selection.selected,
21893
+ onPreview: (preview, rect) => {
21894
+ this.#marqueePreview = preview;
21895
+ this.#marqueeRect = rect;
21896
+ this.requestUpdate();
21897
+ },
21898
+ onCommit: (finalSet) => {
21899
+ this.selection.applyDragSelection(finalSet);
21900
+ this.#marqueePreview = /* @__PURE__ */ new Set();
21901
+ this.#marqueeRect = void 0;
21902
+ this.requestUpdate();
21903
+ },
21904
+ autoScroll: (dx, dy) => {
21905
+ const vp = this.#viewportRef.value;
21906
+ if (!vp) return;
21907
+ if (dx !== 0) {
21908
+ vp.scrollLeft += dx;
21909
+ this._scrollLeft = vp.scrollLeft;
21910
+ }
21911
+ if (dy !== 0) {
21912
+ vp.scrollTop += dy;
21913
+ this._scrollTop = vp.scrollTop;
21914
+ }
21915
+ },
21916
+ shouldStart: (event) => {
21917
+ if (!this.itemsDraggable) return true;
21918
+ const cell = event.composedPath()[0]?.closest?.(`.cell`);
21919
+ if (!cell) return true;
21920
+ const key = cell.dataset.key;
21921
+ if (!key) return true;
21922
+ const index = this.#source.indexOfKey(key);
21923
+ if (index < 0) return true;
21924
+ return !this.#source.isSelectable(index);
21925
+ }
20540
21926
  });
20541
21927
  this.#selectedSnapshot = /* @__PURE__ */ new Set();
20542
21928
  this.#onScroll = (e) => {
@@ -20549,6 +21935,32 @@ let GridListElement = class GridListElement extends i {
20549
21935
  this.#emitViewportChange();
20550
21936
  });
20551
21937
  };
21938
+ this.#onDragStart = (e, key) => {
21939
+ if (!this.itemsDraggable) return;
21940
+ const index = this.#source.indexOfKey(key);
21941
+ if (index < 0 || !this.#source.isSelectable(index)) return;
21942
+ if (!this.selection.selectedKeys.has(key)) this.selection.replace(key);
21943
+ const selectedKeys = this.#source.keys().filter((k) => !isSyntheticKey(k)).filter((k) => this.selection.selectedKeys.has(k));
21944
+ const detail = {
21945
+ key,
21946
+ index: this.#source.indexOfKey(key),
21947
+ item: this.#source.resolveKeyToData(key) ?? this.#source.resolveKeyToElement(key),
21948
+ selectedKeys
21949
+ };
21950
+ if (!this._dispatch(`grid-list-native-drag-start`, detail, true)) {
21951
+ e.preventDefault();
21952
+ return;
21953
+ }
21954
+ this.#nativeDragging = true;
21955
+ this.#dragOriginKey = key;
21956
+ this.toggleAttribute(`data-native-dragging`, true);
21957
+ const count = selectedKeys.length;
21958
+ this.setAttribute(`aria-label`, count === 1 ? `Dragging 1 item` : `Dragging ${count} items`);
21959
+ };
21960
+ this.#onDragEnd = () => {
21961
+ this.#clearNativeDragState();
21962
+ this.#startPostDragSuppressTimer();
21963
+ };
20552
21964
  this._onKeyDown = (e) => {
20553
21965
  if ((e.ctrlKey || e.metaKey) && e.key === `f`) return;
20554
21966
  if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
@@ -20618,6 +22030,7 @@ let GridListElement = class GridListElement extends i {
20618
22030
  break;
20619
22031
  }
20620
22032
  case `Escape`:
22033
+ if (this.#nativeDragging) this.#clearNativeDragState();
20621
22034
  this.selection.clear();
20622
22035
  break;
20623
22036
  default: return;
@@ -20719,6 +22132,7 @@ let GridListElement = class GridListElement extends i {
20719
22132
  align-items: center;
20720
22133
  justify-content: center;
20721
22134
  overflow: hidden;
22135
+ draggable: false;
20722
22136
  }
20723
22137
  .thumb ::slotted(*:empty)::after {
20724
22138
  content: "Empty";
@@ -20768,6 +22182,18 @@ let GridListElement = class GridListElement extends i {
20768
22182
  }
20769
22183
  :host([caption-align="left"]) .caption { text-align: left; }
20770
22184
  :host([caption-align="right"]) .caption { text-align: right; }
22185
+ /* Tickled captions swap their plain label for an ixfx-pan-text in loop
22186
+ mode so a truncated label scrolls instead of staying an ellipsis. The
22187
+ pan fills the caption band (flex in 'separate', block otherwise) and
22188
+ inherits its alignment. */
22189
+ .caption ixfx-pan-text {
22190
+ display: block;
22191
+ flex: 1;
22192
+ min-width: 0;
22193
+ max-width: 100%;
22194
+ font: inherit;
22195
+ color: inherit;
22196
+ }
20771
22197
 
20772
22198
  /* ---- Box modes (hidden / under / overlaid): the cell is one box with a
20773
22199
  single selection outline ---- */
@@ -20911,6 +22337,26 @@ let GridListElement = class GridListElement extends i {
20911
22337
  :host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
20912
22338
  .cell.entering { animation: none; }
20913
22339
  }
22340
+
22341
+ /* Drag-select (marquee) rectangle + covered-cell preview. The preview
22342
+ reuses the selection wash so covered cells read as "about to be
22343
+ selected". Style hooks: part="marquee", --grid-list-marquee-border,
22344
+ --grid-list-marquee-fill. */
22345
+ .marquee {
22346
+ position: absolute;
22347
+ pointer-events: none;
22348
+ z-index: 5;
22349
+ border: 1px solid var(--grid-list-marquee-border, var(--accent));
22350
+ background: var(--grid-list-marquee-fill, color-mix(in srgb, var(--accent) 15%, transparent));
22351
+ }
22352
+ .cell.marquee .thumb::after {
22353
+ opacity: var(--grid-list-thumb-selection-wash-opacity, 0.18);
22354
+ }
22355
+
22356
+ /* Native item drag origin cell. */
22357
+ .cell.dragging-origin {
22358
+ opacity: 0.6;
22359
+ }
20914
22360
  `
20915
22361
  ];
20916
22362
  }
@@ -20943,6 +22389,62 @@ let GridListElement = class GridListElement extends i {
20943
22389
  */
20944
22390
  #sourceElement;
20945
22391
  #source;
22392
+ /** Live drag-select preview: covered keys + the surface-relative rect. */
22393
+ #marqueePreview;
22394
+ #marqueeRect;
22395
+ /** Native item drag state: true while a drag is active. */
22396
+ #nativeDragging;
22397
+ /** Key of the cell that originated the native drag. */
22398
+ #dragOriginKey;
22399
+ /** Timer handle for post-drag click suppression. */
22400
+ #postDragSuppressTimer;
22401
+ /**
22402
+ * The single caption pan-text currently scrolling. At most one caption is
22403
+ * a pan-text in loop mode at a time — when tickle moves, the old one is
22404
+ * stopped before the new one starts (see `#activateTickledPan`).
22405
+ */
22406
+ #activeCaptionPan;
22407
+ #dragSelect;
22408
+ /**
22409
+ * Map a surface-relative (viewport-visible) rect to covered item keys using
22410
+ * the same `computeGridWindow` geometry the renderer uses — pure arithmetic,
22411
+ * so virtualized off-screen items are covered without DOM measurement.
22412
+ */
22413
+ #marqueeHitTest(rect) {
22414
+ const count = this.#source.count;
22415
+ if (count === 0) return [];
22416
+ const horizontal = this.orientation === `row`;
22417
+ const win = computeGridWindow(count, this.itemSize, this.#rowHeight(), this.gap, Math.max(1, this._viewportWidth), Math.max(1, this._viewportHeight), horizontal ? this._scrollLeft : this._scrollTop, this.orientation, 0);
22418
+ const out = [];
22419
+ if (horizontal) {
22420
+ const x0 = rect.x + this._scrollLeft;
22421
+ const x1 = x0 + rect.width;
22422
+ const lo = Math.max(0, Math.floor(x0 / win.colStride));
22423
+ const hi = Math.min(count - 1, Math.floor(x1 / win.colStride));
22424
+ for (let i = lo; i <= hi; i++) out.push(i);
22425
+ } else {
22426
+ const x0 = rect.x + this._scrollLeft;
22427
+ const x1 = x0 + rect.width;
22428
+ const y0 = rect.y + this._scrollTop;
22429
+ const y1 = y0 + rect.height;
22430
+ const colLo = Math.max(0, Math.floor(x0 / win.colStride));
22431
+ const colHi = Math.min(win.cols - 1, Math.floor(x1 / win.colStride));
22432
+ const rowLo = Math.max(0, Math.floor(y0 / win.rowStride));
22433
+ const rowHi = Math.floor(y1 / win.rowStride);
22434
+ for (let row = rowLo; row <= rowHi; row++) for (let col = colLo; col <= colHi; col++) {
22435
+ const index = row * win.cols + col;
22436
+ if (index >= 0 && index < count) out.push(index);
22437
+ }
22438
+ }
22439
+ const keys = [];
22440
+ for (const index of out) {
22441
+ const key = this.#source.keyAt(index);
22442
+ if (!key || isSyntheticKey(key)) continue;
22443
+ if (!this.#source.isSelectable(index)) continue;
22444
+ keys.push(key);
22445
+ }
22446
+ return keys;
22447
+ }
20946
22448
  #searchMode;
20947
22449
  #searchFilterKeys;
20948
22450
  /** (Re)build {@link search} for the current mode; a no-op if it already matches. */
@@ -21015,6 +22517,7 @@ let GridListElement = class GridListElement extends i {
21015
22517
  }
21016
22518
  disconnectedCallback() {
21017
22519
  super.disconnectedCallback();
22520
+ this.#stopActiveCaptionPan();
21018
22521
  this.removeEventListener(`keydown`, this._onKeyDown);
21019
22522
  this.#resizeObserver?.disconnect();
21020
22523
  this.#resizeObserver = void 0;
@@ -21027,6 +22530,7 @@ let GridListElement = class GridListElement extends i {
21027
22530
  clearTimeout(this.#reflowTimer);
21028
22531
  this.#reflowTimer = 0;
21029
22532
  }
22533
+ if (this.#nativeDragging) this.#clearNativeDragState();
21030
22534
  }
21031
22535
  #onChildrenChanged(records) {
21032
22536
  const previousLength = this.#items.length;
@@ -21214,6 +22718,7 @@ let GridListElement = class GridListElement extends i {
21214
22718
  if (Math.abs(vp.scrollLeft - this._scrollLeft) > .5) vp.scrollLeft = this._scrollLeft;
21215
22719
  }
21216
22720
  this.#applyZoomAnchor();
22721
+ this.#activateTickledPan();
21217
22722
  }
21218
22723
  /** Re-anchor the scroll so the item captured in `willUpdate` stays centred. */
21219
22724
  #applyZoomAnchor() {
@@ -21544,15 +23049,19 @@ let GridListElement = class GridListElement extends i {
21544
23049
  element
21545
23050
  });
21546
23051
  }
21547
- _dispatch(name, detail) {
21548
- this.dispatchEvent(new CustomEvent(name, {
23052
+ _dispatch(name, detail, cancelable = false) {
23053
+ return this.dispatchEvent(new CustomEvent(name, {
21549
23054
  detail,
21550
23055
  bubbles: true,
21551
- composed: true
23056
+ composed: true,
23057
+ cancelable
21552
23058
  }));
21553
23059
  }
21554
23060
  /** Selected keys, snapshotted once per render so the cell renderers don't each rebuild the set. */
21555
23061
  #selectedSnapshot;
23062
+ #marqueeStyle(rect) {
23063
+ return `left:${rect.x}px;top:${rect.y}px;width:${Math.max(0, rect.width)}px;height:${Math.max(0, rect.height)}px`;
23064
+ }
21556
23065
  render() {
21557
23066
  const win = this.#currentWindow();
21558
23067
  this.#selectedSnapshot = this.selection.selectedKeys;
@@ -21569,6 +23078,7 @@ let GridListElement = class GridListElement extends i {
21569
23078
  <div class="spacer" style=${spacerStyle}>
21570
23079
  ${this.#source instanceof DataGridListSource ? this.#renderDataCells(win) : this.#renderElementCells(win)}
21571
23080
  </div>
23081
+ ${this.#marqueeRect ? b`<div class="marquee" part="marquee" style=${this.#marqueeStyle(this.#marqueeRect)}></div>` : ``}
21572
23082
  </div>
21573
23083
  ${this.search?.renderField()}
21574
23084
  `;
@@ -21580,7 +23090,10 @@ let GridListElement = class GridListElement extends i {
21580
23090
  #renderDataCell(index, win) {
21581
23091
  const key = this.#source.keyAt(index);
21582
23092
  const selected = this.#selectedSnapshot.has(key);
23093
+ const marquee = this.#marqueePreview.has(key);
21583
23094
  const tickled = this.tickled.isTickled(key);
23095
+ const selectable = this.#source.isSelectable(index);
23096
+ const draggable = this.itemsDraggable && selectable;
21584
23097
  const style = this.captionStyle;
21585
23098
  const separate = style === `separate`;
21586
23099
  const caption = style === `hidden` ? `` : this.#source.labelAt(index);
@@ -21591,24 +23104,69 @@ let GridListElement = class GridListElement extends i {
21591
23104
  const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
21592
23105
  return b`
21593
23106
  <div
21594
- class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
23107
+ class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${marquee ? ` marquee` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
21595
23108
  part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
21596
23109
  role="option"
21597
23110
  aria-selected=${selected ? `true` : `false`}
21598
23111
  style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
21599
23112
  data-index=${index}
21600
23113
  data-key=${key}
23114
+ draggable=${draggable ? `true` : `false`}
21601
23115
  ?data-selected=${selected}
21602
23116
  ?data-tickled=${tickled}
23117
+ ?data-marquee=${marquee}
21603
23118
  @click=${(e) => this.#onCellClick(e, key)}
21604
23119
  @dblclick=${() => this.#onCellActivate(key)}
21605
23120
  @pointerenter=${() => this.tickled.handlePointerEnter(key)}
23121
+ @dragstart=${(e) => this.#onDragStart(e, key)}
23122
+ @dragend=${() => this.#onDragEnd()}
21606
23123
  >
21607
23124
  <div class="content" part="content">${this.#source.renderCellContent(index)}</div>
21608
- ${showCaption ? b`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>` : ``}
23125
+ ${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
21609
23126
  </div>
21610
23127
  `;
21611
23128
  }
23129
+ /**
23130
+ * A tickled cell swaps its plain label for an `ixfx-pan-text` in loop mode
23131
+ * so a truncated label scrolls instead of staying an ellipsis. Unticked
23132
+ * cells keep the cheap plain-text caption.
23133
+ */
23134
+ #renderCaption(caption, captionCss, tickled) {
23135
+ if (tickled) return b`<div class="caption" part="caption" style=${captionCss} title=${caption}><ixfx-pan-text mode="loop">${caption}</ixfx-pan-text></div>`;
23136
+ return b`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>`;
23137
+ }
23138
+ /** Stop the active caption pan, if any, and forget it. */
23139
+ #stopActiveCaptionPan() {
23140
+ const active = this.#activeCaptionPan;
23141
+ this.#activeCaptionPan = null;
23142
+ try {
23143
+ active?.stopLoop?.();
23144
+ } catch {}
23145
+ }
23146
+ /**
23147
+ * Enforce a single scrolling caption: the tickled cell's pan-text loops,
23148
+ * every other pan-text is stopped. The pan-text also auto-starts when
23149
+ * `mode="loop"` is set; this covers the case where the element was already
23150
+ * connected before overflow was measured.
23151
+ */
23152
+ #activateTickledPan() {
23153
+ const tickledCell = this.renderRoot.querySelector(`.cell[data-tickled]`);
23154
+ const pans = [...this.renderRoot.querySelectorAll(`ixfx-pan-text[mode="loop"]`)];
23155
+ const current = tickledCell?.querySelector(`ixfx-pan-text[mode="loop"]`) ?? null;
23156
+ for (const pan of pans) if (pan !== current) try {
23157
+ pan.stopLoop?.();
23158
+ } catch {}
23159
+ const active = this.#activeCaptionPan;
23160
+ if (active && active !== current) try {
23161
+ active.stopLoop?.();
23162
+ } catch {}
23163
+ if (!current) {
23164
+ this.#activeCaptionPan = null;
23165
+ return;
23166
+ }
23167
+ this.#activeCaptionPan = current;
23168
+ current.startLoop?.();
23169
+ }
21612
23170
  #renderElementCells(win) {
21613
23171
  const slice = this.#items.slice(win.startIndex, win.endIndex);
21614
23172
  return repeat(slice, (_el, i) => this.#source.keyAt(win.startIndex + i), (el, i) => this.#renderCell(el, win.startIndex + i, win));
@@ -21616,8 +23174,11 @@ let GridListElement = class GridListElement extends i {
21616
23174
  #renderCell(el, index, win) {
21617
23175
  const key = this.#source.keyAt(index);
21618
23176
  const selected = this.#selectedSnapshot.has(key);
23177
+ const marquee = this.#marqueePreview.has(key);
21619
23178
  const tickled = this.tickled.isTickled(key);
21620
23179
  const entering = this.#entering.has(el);
23180
+ const selectable = this.#source.isSelectable(index);
23181
+ const draggable = this.itemsDraggable && selectable;
21621
23182
  const style = this.captionStyle;
21622
23183
  const separate = style === `separate`;
21623
23184
  const caption = style === `hidden` ? `` : this.#captionFor(el);
@@ -21629,23 +23190,27 @@ let GridListElement = class GridListElement extends i {
21629
23190
  const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
21630
23191
  return b`
21631
23192
  <div
21632
- class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}"
23193
+ class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}${marquee ? ` marquee` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
21633
23194
  part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
21634
23195
  role="option"
21635
23196
  aria-selected=${selected ? `true` : `false`}
21636
23197
  style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
21637
23198
  data-index=${index}
21638
23199
  data-key=${key}
23200
+ draggable=${draggable ? `true` : `false`}
21639
23201
  ?data-selected=${selected}
21640
23202
  ?data-tickled=${tickled}
23203
+ ?data-marquee=${marquee}
21641
23204
  @click=${(e) => this.#onCellClick(e, key)}
21642
23205
  @dblclick=${() => this.#onCellActivate(key)}
21643
23206
  @pointerenter=${() => this.tickled.handlePointerEnter(key)}
23207
+ @dragstart=${(e) => this.#onDragStart(e, key)}
23208
+ @dragend=${() => this.#onDragEnd()}
21644
23209
  >
21645
23210
  <div class="thumb" part="thumb" style=${thumbStyle}>
21646
23211
  ${this.#source.renderCellContent(index)}
21647
23212
  </div>
21648
- ${showCaption ? b`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>` : ``}
23213
+ ${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
21649
23214
  </div>
21650
23215
  `;
21651
23216
  }
@@ -21657,9 +23222,24 @@ let GridListElement = class GridListElement extends i {
21657
23222
  }
21658
23223
  }
21659
23224
  #onCellClick(e, key) {
23225
+ if (this.#dragSelect.isDragging || this.#nativeDragging || this.#postDragSuppressTimer !== 0) return;
21660
23226
  this.selection.handleClick(key, e);
21661
23227
  this.#emitItemEvent(`list-item-click`, key);
21662
23228
  }
23229
+ #onDragStart;
23230
+ #onDragEnd;
23231
+ #startPostDragSuppressTimer() {
23232
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
23233
+ this.#postDragSuppressTimer = window.setTimeout(() => {
23234
+ this.#postDragSuppressTimer = 0;
23235
+ }, 0);
23236
+ }
23237
+ #clearNativeDragState() {
23238
+ this.#nativeDragging = false;
23239
+ this.#dragOriginKey = void 0;
23240
+ this.toggleAttribute(`data-native-dragging`, false);
23241
+ this.removeAttribute(`aria-label`);
23242
+ }
21663
23243
  #onCellActivate(key) {
21664
23244
  if (this.interactionMode !== `manual`) this.selection.replace(key);
21665
23245
  this.#emitItemEvent(`list-activate`, key);
@@ -21717,6 +23297,11 @@ __decorate([property({
21717
23297
  })], GridListElement.prototype, "itemWrap", void 0);
21718
23298
  __decorate([property({ attribute: false })], GridListElement.prototype, "filterPredicate", void 0);
21719
23299
  __decorate([property({ attribute: false })], GridListElement.prototype, "itemFilterPredicate", void 0);
23300
+ __decorate([property({
23301
+ type: Boolean,
23302
+ attribute: `items-draggable`,
23303
+ reflect: true
23304
+ })], GridListElement.prototype, "itemsDraggable", void 0);
21720
23305
  __decorate([property({
21721
23306
  type: String,
21722
23307
  attribute: `search-modes`,
@@ -23603,7 +25188,29 @@ let VerticalListElement = class VerticalListElement extends i {
23603
25188
  if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
23604
25189
  },
23605
25190
  onStateChange: () => this._syncItemStates(),
23606
- itemWrap: this.itemWrap
25191
+ itemWrap: this.itemWrap,
25192
+ hoverDisabled: () => this.#dragSelect.isDragging
25193
+ });
25194
+ this.#dragSelect = new ListDragSelectController(this, {
25195
+ getSurface: () => this.renderRoot.querySelector(`.container`),
25196
+ isEnabled: () => this.selection.canDragSelect() && this._getVisibleItems().length > 0 && !this.hasAttribute(`disable-drag-select`),
25197
+ hitTest: (rect) => this.#marqueeHitTest(rect),
25198
+ getBaseSelection: () => this.selection.selected,
25199
+ onPreview: (preview, rect) => {
25200
+ this.#marqueeRect = rect;
25201
+ for (const el of this._getVisibleItems()) el.toggleAttribute(`data-marquee`, preview.has(el));
25202
+ this.requestUpdate();
25203
+ },
25204
+ onCommit: (finalSet) => {
25205
+ this.selection.applyDragSelection(finalSet);
25206
+ for (const el of this._getVisibleItems()) el.removeAttribute(`data-marquee`);
25207
+ this.#marqueeRect = void 0;
25208
+ this.requestUpdate();
25209
+ },
25210
+ autoScroll: (_dx, dy) => {
25211
+ const list = this.renderRoot.querySelector(`ul`);
25212
+ if (list) list.scrollTop += dy;
25213
+ }
23607
25214
  });
23608
25215
  this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
23609
25216
  itemSelector: ITEM_SELECTOR,
@@ -23664,6 +25271,7 @@ let VerticalListElement = class VerticalListElement extends i {
23664
25271
  }
23665
25272
  };
23666
25273
  this._onClick = (e) => {
25274
+ if (this.#dragSelect.isDragging) return;
23667
25275
  const item = this._findListItem(e.composedPath());
23668
25276
  if (!item) return;
23669
25277
  this._dispatch(`list-item-click`, { item });
@@ -23680,6 +25288,30 @@ let VerticalListElement = class VerticalListElement extends i {
23680
25288
  #itemObserver;
23681
25289
  /** Items that have already played (or been skipped for) their enter animation. */
23682
25290
  #animatedItems;
25291
+ /** Live drag-select preview rect (surface-relative); the covered items carry `data-marquee`. */
25292
+ #marqueeRect;
25293
+ #dragSelect;
25294
+ /** Collect visible items whose surface-relative box intersects `rect`. */
25295
+ #marqueeHitTest(rect) {
25296
+ const surface = this.renderRoot.querySelector(`.container`);
25297
+ if (!surface) return [];
25298
+ const box = surface.getBoundingClientRect();
25299
+ const rx0 = rect.x;
25300
+ const ry0 = rect.y;
25301
+ const rx1 = rect.x + rect.width;
25302
+ const ry1 = rect.y + rect.height;
25303
+ const out = [];
25304
+ for (const el of this._getVisibleItems()) {
25305
+ const r = el.getBoundingClientRect();
25306
+ const left = r.left - box.left;
25307
+ const top = r.top - box.top;
25308
+ if (left <= rx1 && left + r.width >= rx0 && top <= ry1 && top + r.height >= ry0) out.push(el);
25309
+ }
25310
+ return out;
25311
+ }
25312
+ #marqueeStyle(rect) {
25313
+ return `left:${rect.x}px;top:${rect.y}px;width:${Math.max(0, rect.width)}px;height:${Math.max(0, rect.height)}px`;
25314
+ }
23683
25315
  #searchRegistry;
23684
25316
  #unregisterSearchCommands;
23685
25317
  /** Command registry carrying this list's `list.search.*` commands. */
@@ -24150,6 +25782,22 @@ let VerticalListElement = class VerticalListElement extends i {
24150
25782
  outline-offset: -2px;
24151
25783
  }
24152
25784
 
25785
+ /* Drag-select (marquee) rectangle + covered-row preview.
25786
+ Style hooks: part="marquee", --vertical-list-marquee-border,
25787
+ --vertical-list-marquee-fill. */
25788
+ .marquee {
25789
+ position: absolute;
25790
+ pointer-events: none;
25791
+ z-index: 5;
25792
+ border: 1px solid var(--vertical-list-marquee-border, var(--accent));
25793
+ background: var(--vertical-list-marquee-fill, color-mix(in srgb, var(--accent) 15%, transparent));
25794
+ }
25795
+
25796
+ ::slotted([data-marquee]) {
25797
+ background: var(--item-bg-selected);
25798
+ color: var(--item-text-selected);
25799
+ }
25800
+
24153
25801
  /* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */
24154
25802
  :host([interaction-mode="checked"]) ::slotted([data-selected]) {
24155
25803
  background: transparent;
@@ -24171,6 +25819,7 @@ let VerticalListElement = class VerticalListElement extends i {
24171
25819
  <ul part="list">
24172
25820
  <slot></slot>
24173
25821
  </ul>
25822
+ ${this.#marqueeRect ? b`<div class="marquee" part="marquee" style=${this.#marqueeStyle(this.#marqueeRect)}></div>` : ``}
24174
25823
  ${this.search.renderField()}
24175
25824
  </div>
24176
25825
  `;
@@ -30864,136 +32513,6 @@ function dismiss(id) {
30864
32513
  syncRenderer();
30865
32514
  }
30866
32515
  //#endregion
30867
- //#region src/pan-text/pan-text.ts
30868
- const SPEED = 45;
30869
- const END_PADDING = 4;
30870
- const START_DELAY = 300;
30871
- const PAN_EASING = `cubic-bezier(0.25, 0.46, 0.45, 0.94)`;
30872
- let PanTextElement = class PanTextElement extends i {
30873
- constructor(..._args) {
30874
- super(..._args);
30875
- this.direction = `end`;
30876
- this.disabled = false;
30877
- this._isOverflowing = false;
30878
- this._isHovering = false;
30879
- this._overflow = 0;
30880
- this._resizeObserver = null;
30881
- this._onMouseEnter = () => {
30882
- this._isHovering = true;
30883
- };
30884
- this._onMouseLeave = () => {
30885
- this._isHovering = false;
30886
- };
30887
- this._measure = () => {
30888
- if (this._isHovering) return;
30889
- const el = this._textEl;
30890
- if (!el) return;
30891
- this._overflow = Math.max(0, el.scrollWidth - el.clientWidth);
30892
- const overflowing = this._overflow > 0;
30893
- this.classList.toggle(`is-overflowing`, overflowing);
30894
- if (overflowing !== this._isOverflowing) this._isOverflowing = overflowing;
30895
- };
30896
- }
30897
- connectedCallback() {
30898
- super.connectedCallback();
30899
- this._resizeObserver = new ResizeObserver(() => this._measure());
30900
- this.addEventListener(`mouseenter`, this._onMouseEnter);
30901
- this.addEventListener(`mouseleave`, this._onMouseLeave);
30902
- }
30903
- disconnectedCallback() {
30904
- super.disconnectedCallback();
30905
- this._resizeObserver?.disconnect();
30906
- this._resizeObserver = null;
30907
- this.removeEventListener(`mouseenter`, this._onMouseEnter);
30908
- this.removeEventListener(`mouseleave`, this._onMouseLeave);
30909
- }
30910
- firstUpdated() {
30911
- this._measure();
30912
- this._resizeObserver?.observe(this);
30913
- document.fonts?.ready.then(() => this._measure());
30914
- }
30915
- updated() {
30916
- this._measure();
30917
- }
30918
- _textStyle() {
30919
- if (!this._isOverflowing) return ``;
30920
- const distance = this._overflow + END_PADDING;
30921
- const duration = Math.max(1.5, distance / SPEED);
30922
- const revealEnd = this.direction === `end`;
30923
- const restX = revealEnd ? 0 : -distance;
30924
- const hoverX = revealEnd ? -distance : 0;
30925
- if (this._isHovering) return [
30926
- `transform: translateX(${hoverX}px)`,
30927
- `transition: transform ${duration}s ${PAN_EASING} ${START_DELAY}ms`,
30928
- `overflow: visible`,
30929
- `text-overflow: clip`
30930
- ].join(`; `);
30931
- return [
30932
- `transform: translateX(${restX}px)`,
30933
- `transition: transform 400ms ${PAN_EASING}`,
30934
- revealEnd ? `` : `overflow: visible; text-overflow: clip`
30935
- ].filter(Boolean).join(`; `);
30936
- }
30937
- render() {
30938
- const textClasses = {
30939
- "is-overflowing": this._isOverflowing,
30940
- "is-hovering": this._isHovering
30941
- };
30942
- return b`
30943
- <span class="text ${classMap(textClasses)}" part="text" style=${this._textStyle()}>
30944
- <slot @slotchange=${this._measure}></slot>
30945
- </span>
30946
- `;
30947
- }
30948
- static {
30949
- this.styles = [themeFallbacks, i$3`
30950
- /* Renders like plain inline text: no colours, border, padding or margin
30951
- of its own — it only adds the clipping needed to pan on hover. */
30952
- :host {
30953
- display: inline-block;
30954
- overflow: hidden;
30955
- white-space: nowrap;
30956
- max-width: 100%;
30957
- box-sizing: border-box;
30958
- font: inherit;
30959
- color: inherit;
30960
- }
30961
-
30962
- :host([disabled]) {
30963
- opacity: var(--disabled-opacity, 0.5);
30964
- pointer-events: none;
30965
- }
30966
-
30967
- .text {
30968
- display: block;
30969
- overflow: hidden;
30970
- text-overflow: ellipsis;
30971
- white-space: nowrap;
30972
- will-change: transform;
30973
- }
30974
-
30975
- @media (prefers-reduced-motion: reduce) {
30976
- .text {
30977
- transition: none !important;
30978
- transform: none !important;
30979
- }
30980
- }
30981
- `];
30982
- }
30983
- };
30984
- __decorate([property({
30985
- type: String,
30986
- attribute: `direction`
30987
- })], PanTextElement.prototype, "direction", void 0);
30988
- __decorate([property({
30989
- type: Boolean,
30990
- attribute: `disabled`
30991
- })], PanTextElement.prototype, "disabled", void 0);
30992
- __decorate([state()], PanTextElement.prototype, "_isOverflowing", void 0);
30993
- __decorate([state()], PanTextElement.prototype, "_isHovering", void 0);
30994
- __decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
30995
- PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
30996
- //#endregion
30997
32516
  //#region src/panel/panel-group.ts
30998
32517
  /** Pointer movement threshold (px) before a press becomes a drag. */
30999
32518
  const DRAG_THRESHOLD_PX = 5;
@@ -38230,7 +39749,10 @@ let TimelineTrackTabElement = class TimelineTrackTabElement extends i {
38230
39749
  };
38231
39750
  }
38232
39751
  static {
38233
- this.styles = i$3`
39752
+ this.styles = [
39753
+ themeFallbacks,
39754
+ handleStyles,
39755
+ i$3`
38234
39756
  :host {
38235
39757
  display: flex;
38236
39758
  align-items: center;
@@ -38257,13 +39779,13 @@ let TimelineTrackTabElement = class TimelineTrackTabElement extends i {
38257
39779
  .handle {
38258
39780
  display: none;
38259
39781
  cursor: grab;
38260
- padding: 0 4px;
38261
- opacity: 0.5;
38262
- font-size: 0.65rem;
39782
+ margin: 0 4px;
39783
+ --ixfx-handle-spot-size: 12px;
39784
+ --ixfx-handle-dot-size: 3px;
38263
39785
  }
38264
39786
 
38265
39787
  :host([reorderable]) .handle {
38266
- display: block;
39788
+ display: inline-block;
38267
39789
  }
38268
39790
 
38269
39791
  .label {
@@ -38272,7 +39794,8 @@ let TimelineTrackTabElement = class TimelineTrackTabElement extends i {
38272
39794
  text-overflow: ellipsis;
38273
39795
  padding: 0 8px;
38274
39796
  }
38275
- `;
39797
+ `
39798
+ ];
38276
39799
  }
38277
39800
  #dragging;
38278
39801
  #startScreenY;
@@ -38297,7 +39820,7 @@ let TimelineTrackTabElement = class TimelineTrackTabElement extends i {
38297
39820
  }
38298
39821
  render() {
38299
39822
  return b`
38300
- <span class="handle">\u2630</span>
39823
+ <span class="handle ixfx-handle" data-handle="dotted" data-shape="spot" part="handle"></span>
38301
39824
  <span class="label">${this.label}</span>
38302
39825
  `;
38303
39826
  }
@@ -40174,29 +41697,29 @@ let TwoSplitLayoutElement = class TwoSplitLayoutElement extends i {
40174
41697
  <slot style=${sizeA} name="a"></slot>
40175
41698
  ${this._hasCorner ? b`<div class="corner-zone" @pointerdown=${this.#onCornerResizeDown}></div>` : ``}
40176
41699
  <div @pointerdown=${this.#onResizeHandleDown} class="split-bar">
40177
- ${this.showGrabHandle ? b`
40178
- <div class="grab-handle">
40179
- <span class="dot"></span>
40180
- <span class="dot"></span>
40181
- <span class="dot"></span>
40182
- </div>
40183
- ` : ``}
41700
+ ${this.showGrabHandle ? b`<span
41701
+ class="ixfx-handle"
41702
+ part="grip"
41703
+ data-handle="dimpled"
41704
+ data-shape=${this.layout === `horizontal` ? `horizontal` : `vertical`}
41705
+ ></span>` : ``}
40184
41706
  </div>
40185
41707
  <slot style=${sizeB} name="b"></slot>
40186
41708
  </div>`;
40187
41709
  }
40188
41710
  static {
40189
- this.styles = [themeFallbacks, i$3`
41711
+ this.styles = [
41712
+ themeFallbacks,
41713
+ handleStyles,
41714
+ i$3`
40190
41715
  :host {
40191
41716
  --handle-size: 2px;
40192
41717
  --handle-colour: transparent;
40193
41718
  --handle-hover-colour: var(--surface-6);
40194
41719
  --handle-active-colour: var(--accent);
40195
- --handle-bg: var(--handle-colour);
40196
- --handle-grab-colour: var(--border-subtle-color);
40197
- --handle-grab-dot-size: 3px;
40198
- --handle-grab-size: 16px;
40199
- --handle-grab-thickness: 8px;
41720
+ /* Grip — shared handle primitive (docs/infra-handle.md). */
41721
+ --ixfx-handle-length: 16px;
41722
+ --ixfx-handle-thickness: 8px;
40200
41723
 
40201
41724
  display: block;
40202
41725
  box-sizing: border-box;
@@ -40239,40 +41762,14 @@ let TwoSplitLayoutElement = class TwoSplitLayoutElement extends i {
40239
41762
  to { background-color: var(--handle-hover-colour); }
40240
41763
  }
40241
41764
 
40242
- .grab-handle {
41765
+ .split-bar .ixfx-handle {
40243
41766
  position: absolute;
40244
41767
  top: 50%;
40245
41768
  left: 50%;
40246
41769
  transform: translate(-50%, -50%);
40247
- display: flex;
40248
- align-items: center;
40249
- justify-content: center;
40250
- gap: var(--handle-grab-dot-size);
40251
- background: var(--handle-bg);
40252
- border-radius: var(--radius-s);
40253
41770
  }
40254
41771
 
40255
- .grab-handle .dot {
40256
- width: var(--handle-grab-dot-size);
40257
- height: var(--handle-grab-dot-size);
40258
- border-radius: 50%;
40259
- background: var(--handle-grab-colour);
40260
- flex-shrink: 0;
40261
- }
40262
-
40263
- :host([layout="vertical"]) .grab-handle {
40264
- flex-direction: column;
40265
- width: var(--handle-grab-thickness);
40266
- height: var(--handle-grab-size);
40267
- }
40268
-
40269
- :host([layout="horizontal"]) .grab-handle {
40270
- flex-direction: row;
40271
- width: var(--handle-grab-size);
40272
- height: var(--handle-grab-thickness);
40273
- }
40274
-
40275
- #zone.user-resizing .grab-handle {
41772
+ #zone.user-resizing .ixfx-handle {
40276
41773
  display: none;
40277
41774
  }
40278
41775
  #zone.user-resizing {
@@ -40325,7 +41822,8 @@ let TwoSplitLayoutElement = class TwoSplitLayoutElement extends i {
40325
41822
  :host([data-collapsed-b]) slot[name="b"] {
40326
41823
  display: none;
40327
41824
  }
40328
- `];
41825
+ `
41826
+ ];
40329
41827
  }
40330
41828
  };
40331
41829
  __decorate([property({ type: String })], TwoSplitLayoutElement.prototype, "layout", void 0);
@@ -40877,6 +42375,6 @@ function init() {
40877
42375
  console.log(`Init`);
40878
42376
  }
40879
42377
  //#endregion
40880
- export { AcTextElement, AcTokenElement, 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, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, 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, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createDataListSearchAdapter, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
42378
+ 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, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, 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, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createDataListSearchAdapter, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
40881
42379
 
40882
42380
  //# sourceMappingURL=index.js.map