@ixfx/components 0.7.3 → 0.7.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 (121) hide show
  1. package/bundle/index.d.ts +727 -256
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2046 -35
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.d.ts +1 -1
  6. package/dist/ac-text.js +1 -1
  7. package/dist/{ac-token-TCLAZeVg.d.ts → ac-token--_vCHXWw.d.ts} +2 -2
  8. package/dist/{ac-token-TCLAZeVg.d.ts.map → ac-token--_vCHXWw.d.ts.map} +1 -1
  9. package/dist/ac-token.d.ts +1 -1
  10. package/dist/ac-token.js +1 -1
  11. package/dist/button.d.ts +1 -1
  12. package/dist/button.d.ts.map +1 -1
  13. package/dist/colour-picker.d.ts +1 -1
  14. package/dist/crumbs.d.ts +1 -1
  15. package/dist/crumbs.js +3 -3
  16. package/dist/{data-grid-Db4QpnII.d.ts → data-grid-BddUjAti.d.ts} +34 -2
  17. package/dist/data-grid-BddUjAti.d.ts.map +1 -0
  18. package/dist/data-grid.d.ts +1 -1
  19. package/dist/data-grid.js +180 -7
  20. package/dist/data-grid.js.map +1 -1
  21. package/dist/detail-list-Br0WAnry.d.ts +167 -0
  22. package/dist/detail-list-Br0WAnry.d.ts.map +1 -0
  23. package/dist/detail-list.d.ts +3 -0
  24. package/dist/detail-list.js +1115 -0
  25. package/dist/detail-list.js.map +1 -0
  26. package/dist/grid-list-B_orPsot.d.ts +304 -0
  27. package/dist/grid-list-B_orPsot.d.ts.map +1 -0
  28. package/dist/grid-list.d.ts +3 -0
  29. package/dist/grid-list.js +933 -0
  30. package/dist/grid-list.js.map +1 -0
  31. package/dist/{grouped-item-lister-BWlC9S4e.d.ts → grouped-item-lister-fdZHVxFM.d.ts} +3 -3
  32. package/dist/{grouped-item-lister-BWlC9S4e.d.ts.map → grouped-item-lister-fdZHVxFM.d.ts.map} +1 -1
  33. package/dist/grouped-item-lister.d.ts +1 -1
  34. package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +2 -2
  35. package/dist/icon-CDGprSdE.d.ts.map +1 -0
  36. package/dist/icons.d.ts +2 -2
  37. package/dist/{index-C59-JEPl.d.ts → index-9AhaHIT2.d.ts} +3 -3
  38. package/dist/{index-C59-JEPl.d.ts.map → index-9AhaHIT2.d.ts.map} +1 -1
  39. package/dist/{index-DsjpDksO.d.ts → index-CS9yM8L-.d.ts} +2 -2
  40. package/dist/{index-DsjpDksO.d.ts.map → index-CS9yM8L-.d.ts.map} +1 -1
  41. package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +2 -2
  42. package/dist/{index-BUB5SICW.d.ts.map → index-CoqtMSd9.d.ts.map} +1 -1
  43. package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +2 -2
  44. package/dist/{index-CJ5fshD3.d.ts.map → index-UvnoZUAo.d.ts.map} +1 -1
  45. package/dist/{index-BOUaDIW-.d.ts → index-tq6f8007.d.ts} +2 -2
  46. package/dist/{index-BOUaDIW-.d.ts.map → index-tq6f8007.d.ts.map} +1 -1
  47. package/dist/index.d.ts +185 -240
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +375 -1002
  50. package/dist/index.js.map +1 -1
  51. package/dist/interaction-Cr13azbM.js +3 -0
  52. package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +2 -2
  53. package/dist/{labelled-input-base-Dp_9KP0G.d.ts.map → labelled-input-base-DiTme1JO.d.ts.map} +1 -1
  54. package/dist/labelled-radial-input.d.ts +2 -2
  55. package/dist/labelled-radial-input.d.ts.map +1 -1
  56. package/dist/labelled-range-input.d.ts +1 -1
  57. package/dist/labelled-slider-input.d.ts +1 -1
  58. package/dist/list-item-animation-DuiwgN3l.js +112 -0
  59. package/dist/list-item-animation-DuiwgN3l.js.map +1 -0
  60. package/dist/list-selection-controller-CUo-Hhcm.js +338 -0
  61. package/dist/list-selection-controller-CUo-Hhcm.js.map +1 -0
  62. package/dist/{list-selection-types-DSuRNWpx.d.ts → list-selection-types-B5ioL-dG.d.ts} +6 -1
  63. package/dist/list-selection-types-B5ioL-dG.d.ts.map +1 -0
  64. package/dist/{menu-DMzjkVnK.js → menu-Bg90Qr0B.js} +3 -3
  65. package/dist/{menu-DMzjkVnK.js.map → menu-Bg90Qr0B.js.map} +1 -1
  66. package/dist/{menu-item-D2S4i8u1.js → menu-item-Bx-Fs7jq.js} +3 -3
  67. package/dist/{menu-item-D2S4i8u1.js.map → menu-item-Bx-Fs7jq.js.map} +1 -1
  68. package/dist/{menu-item-ZmkXNjek.d.ts → menu-item-CZi9vy5O.d.ts} +2 -2
  69. package/dist/{menu-item-ZmkXNjek.d.ts.map → menu-item-CZi9vy5O.d.ts.map} +1 -1
  70. package/dist/menu.d.ts +2 -2
  71. package/dist/menu.js +2 -2
  72. package/dist/miller.d.ts +1 -1
  73. package/dist/miller.js +2 -2
  74. package/dist/narrowed-text.js +2 -2
  75. package/dist/panel.d.ts +1 -1
  76. package/dist/plots.js +1 -1
  77. package/dist/select-horiz.js +2 -2
  78. package/dist/{split-layout-C5SYbDrq.d.ts → split-layout-JK3xWVX2.d.ts} +2 -2
  79. package/dist/{split-layout-C5SYbDrq.d.ts.map → split-layout-JK3xWVX2.d.ts.map} +1 -1
  80. package/dist/split-layout.d.ts +1 -1
  81. package/dist/{tab-list-DbMIL520.d.ts → tab-list-CGyPt07Z.d.ts} +2 -2
  82. package/dist/{tab-list-DbMIL520.d.ts.map → tab-list-CGyPt07Z.d.ts.map} +1 -1
  83. package/dist/tabs.d.ts +1 -1
  84. package/dist/{tickled-controller-Ds1DMyG7.d.ts → tickled-controller-Bn3YdQsM.d.ts} +12 -1
  85. package/dist/{tickled-controller-Ds1DMyG7.d.ts.map → tickled-controller-Bn3YdQsM.d.ts.map} +1 -1
  86. package/dist/{tickled-controller-h9GmJ_bU.js → tickled-controller-C66DgJHl.js} +16 -1
  87. package/dist/tickled-controller-C66DgJHl.js.map +1 -0
  88. package/dist/{titlebar-B23MrIbI.d.ts → titlebar-Bj5pfdw_.d.ts} +2 -2
  89. package/dist/{titlebar-B23MrIbI.d.ts.map → titlebar-Bj5pfdw_.d.ts.map} +1 -1
  90. package/dist/titlebar.d.ts +1 -1
  91. package/dist/{tree-Ch4W8qXQ.js → tree-B74zdI60.js} +116 -17
  92. package/dist/tree-B74zdI60.js.map +1 -0
  93. package/dist/tree.d.ts +6 -3
  94. package/dist/tree.d.ts.map +1 -1
  95. package/dist/tree.js +1 -1
  96. package/dist/vertical-list-CQSEVeOT.d.ts +140 -0
  97. package/dist/vertical-list-CQSEVeOT.d.ts.map +1 -0
  98. package/dist/vertical-list.d.ts +3 -121
  99. package/dist/vertical-list.js +710 -1
  100. package/dist/vertical-list.js.map +1 -0
  101. package/dist/{xy-axis-DWizs0bt.js → xy-axis-VrX8dmvS.js} +2 -2
  102. package/dist/{xy-axis-DWizs0bt.js.map → xy-axis-VrX8dmvS.js.map} +1 -1
  103. package/docs-user/README.md +1 -0
  104. package/docs-user/data-grid.md +51 -2
  105. package/docs-user/detail-list.md +38 -0
  106. package/docs-user/grid-list.md +274 -0
  107. package/docs-user/index.json +7 -2
  108. package/docs-user/llms.txt +1 -0
  109. package/docs-user/tree.md +13 -0
  110. package/docs-user/vertical-list.md +51 -1
  111. package/llms.txt +1 -0
  112. package/package.json +17 -2
  113. package/dist/data-grid-Db4QpnII.d.ts.map +0 -1
  114. package/dist/icon-DR_3rSyZ.d.ts.map +0 -1
  115. package/dist/interaction-D5XdKzbR.js +0 -2
  116. package/dist/list-selection-types-DSuRNWpx.d.ts.map +0 -1
  117. package/dist/tickled-controller-h9GmJ_bU.js.map +0 -1
  118. package/dist/tree-Ch4W8qXQ.js.map +0 -1
  119. package/dist/vertical-list-B4w_ksJk.js +0 -814
  120. package/dist/vertical-list-B4w_ksJk.js.map +0 -1
  121. package/dist/vertical-list.d.ts.map +0 -1
@@ -1,814 +0,0 @@
1
- import { t as TickledController } from "./tickled-controller-h9GmJ_bU.js";
2
- import { t as themeFallbacks } from "./fallbacks-DNI9xt0F.js";
3
- import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
4
- import { t as createElementIncrSearch } from "./element-search-Y8SRfPGM.js";
5
- import { LitElement, css, html, render } from "lit";
6
- import { property, query, state } from "lit/decorators.js";
7
- //#region src/interaction/list-selection-controller.ts
8
- /** Whether the interaction mode allows building a multi-item selection. */
9
- function isMultiMode(mode) {
10
- return mode === `checked` || mode === `sticky` || mode === `vscode` || mode === `standard`;
11
- }
12
- /**
13
- * Generic selection state machine shared by list components.
14
- *
15
- * Owns the selected set plus the range-selection anchor, the selection
16
- * primitives (`replace` / `toggle` / `rangeTo` / `clear` / `selectAll` /
17
- * `selectMany` / `deselect`) and the per-interactionMode gesture routers
18
- * (`handleClick` / `handleSpace`).
19
- *
20
- * Identity is generic: both `ixfx-vertical-list` and `ixfx-detail-list` use
21
- * `ListSelectionController<Element>`.
22
- *
23
- * @example
24
- * ```ts
25
- * const selection = new ListSelectionController<Element>({
26
- * getOrderedItems: () => this.getVisibleItems(),
27
- * isSelectable: el => !el.hasAttribute(`not-checked`),
28
- * getModes: () => ({ selectionMode: this.selectionMode, interactionMode: this.interactionMode }),
29
- * onChange: (selected, previous) => {
30
- * this.syncItemStates();
31
- * this.dispatchEvent(new CustomEvent(`list-select`, { detail: { selected, previous } }));
32
- * },
33
- * });
34
- * ```
35
- */
36
- var ListSelectionController = class {
37
- #config;
38
- #selected = /* @__PURE__ */ new Set();
39
- #anchor;
40
- constructor(config) {
41
- this.#config = config;
42
- }
43
- /** The current selection set (read-only). */
44
- get selected() {
45
- return this.#selected;
46
- }
47
- /** Anchor for range (shift) selection — the last plainly-selected item. */
48
- get anchor() {
49
- return this.#anchor;
50
- }
51
- /** Whether "select all" (Cmd/Ctrl+A) is currently permitted. */
52
- canSelectAll() {
53
- return this.canSelectMultiple() && this.#config.getModes().selectionMode !== `none`;
54
- }
55
- /** Whether a multi-item selection can be built in the current modes. */
56
- canSelectMultiple() {
57
- const { selectionMode, interactionMode } = this.#config.getModes();
58
- return selectionMode === `multiple` || isMultiMode(interactionMode);
59
- }
60
- /** Replace the entire selection with a single item; updates the anchor. */
61
- replace(item) {
62
- if (this.#config.getModes().selectionMode === `none`) return;
63
- if (!this.#config.isSelectable(item)) return;
64
- this.#anchor = item;
65
- this.#apply(/* @__PURE__ */ new Set([item]));
66
- }
67
- /** Toggle a single item in/out of the selection without replacing it. Anchor unchanged. */
68
- toggle(item) {
69
- const { selectionMode, interactionMode } = this.#config.getModes();
70
- if (!this.#config.isSelectable(item)) return;
71
- if (selectionMode === `none` && interactionMode !== `checked` && interactionMode !== `sticky`) return;
72
- const next = new Set(this.#selected);
73
- if (next.has(item)) next.delete(item);
74
- else {
75
- if (!this.canSelectMultiple()) next.clear();
76
- next.add(item);
77
- }
78
- this.#apply(next);
79
- }
80
- /** Select the contiguous ordered range from the anchor to `item`. Anchor unchanged. */
81
- rangeTo(item) {
82
- if (this.#config.getModes().selectionMode === `none`) return;
83
- if (this.#anchor === void 0) {
84
- this.replace(item);
85
- return;
86
- }
87
- const ordered = this.#config.getOrderedItems();
88
- const anchorIdx = ordered.indexOf(this.#anchor);
89
- const targetIdx = ordered.indexOf(item);
90
- if (anchorIdx === -1 || targetIdx === -1) {
91
- this.replace(item);
92
- return;
93
- }
94
- const start = Math.min(anchorIdx, targetIdx);
95
- const end = Math.max(anchorIdx, targetIdx);
96
- this.#apply(new Set(ordered.slice(start, end + 1).filter((t) => this.#config.isSelectable(t))));
97
- }
98
- /** Clear the selection. No change event when the selection is already empty. */
99
- clear() {
100
- if (this.#selected.size === 0) return;
101
- this.#apply(/* @__PURE__ */ new Set());
102
- }
103
- /** Select every ordered item that passes `isSelectable`. */
104
- selectAll() {
105
- const ordered = this.#config.getOrderedItems();
106
- if (ordered.length === 0) return;
107
- const next = new Set(ordered.filter((t) => this.#config.isSelectable(t)));
108
- if (next.size === 0) return;
109
- this.#apply(next);
110
- }
111
- /**
112
- * Replace the selection with many items at once. Requires
113
- * `selectionMode="multiple"`, throws otherwise. Updates the anchor.
114
- */
115
- selectMany(items) {
116
- const { selectionMode } = this.#config.getModes();
117
- if (selectionMode !== `multiple`) throw new Error(`selectMany requires selectionMode="multiple" (current: "${selectionMode}")`);
118
- const next = new Set([...items].filter((t) => this.#config.isSelectable(t)));
119
- if (next.size === 0) return;
120
- this.#anchor = [...next].at(-1);
121
- this.#apply(next);
122
- }
123
- /** Remove a single item from the selection. */
124
- deselect(item) {
125
- if (!this.#selected.has(item)) return;
126
- const next = new Set(this.#selected);
127
- next.delete(item);
128
- this.#apply(next);
129
- }
130
- /** Route a click on `item` according to the current interaction mode. */
131
- handleClick(item, gesture) {
132
- const { selectionMode, interactionMode: mode } = this.#config.getModes();
133
- if (mode === `manual`) return;
134
- if (selectionMode === `none` && mode !== `checked` && mode !== `sticky`) return;
135
- switch (mode) {
136
- case `implicit`:
137
- this.replace(item);
138
- break;
139
- case `standard`:
140
- case `vscode`:
141
- if (gesture.ctrlKey || gesture.metaKey) {
142
- if (selectionMode === `multiple` || mode === `vscode`) this.toggle(item);
143
- } else if (gesture.shiftKey && (selectionMode === `multiple` || mode === `vscode`)) this.rangeTo(item);
144
- else this.replace(item);
145
- break;
146
- case `checked`: break;
147
- case `sticky`: this.toggle(item);
148
- }
149
- }
150
- /** Route a Space keypress on `item` according to the current interaction mode. */
151
- handleSpace(item) {
152
- const mode = this.#config.getModes().interactionMode;
153
- if (mode === `manual`) return;
154
- if (mode === `implicit`) this.replace(item);
155
- else this.toggle(item);
156
- }
157
- /** Drop the range-selection anchor (e.g. when the interaction mode changes). */
158
- clearAnchor() {
159
- this.#anchor = void 0;
160
- }
161
- /**
162
- * Re-key the selection across an identity boundary — e.g. when items are
163
- * wrapped/unwrapped and the selected Element references change. Silent:
164
- * no `onChange` is fired.
165
- */
166
- remap(fn) {
167
- if (this.#selected.size === 0) return;
168
- this.#selected = new Set([...this.#selected].map(fn));
169
- }
170
- /**
171
- * Hard reset: empty the selection and clear the anchor without firing
172
- * `onChange` (e.g. when all items are removed).
173
- */
174
- reset() {
175
- this.#selected = /* @__PURE__ */ new Set();
176
- this.#anchor = void 0;
177
- }
178
- /**
179
- * Replace the selection and anchor silently (no `onChange`). Used to
180
- * re-key the selection when items are rebuilt from data — e.g. matching
181
- * new elements to previously selected keys.
182
- */
183
- seed(items, anchor) {
184
- this.#selected = new Set(items);
185
- this.#anchor = anchor;
186
- }
187
- #apply(next) {
188
- const previous = this.#selected;
189
- this.#selected = next;
190
- this.#config.onChange(next, previous);
191
- }
192
- };
193
- //#endregion
194
- //#region src/vertical-list/vertical-list.ts
195
- const ITEM_SELECTOR = `li, [data-list-item]`;
196
- let VerticalListElement = class VerticalListElement extends LitElement {
197
- constructor(..._args2) {
198
- super(..._args2);
199
- this.selectionMode = `single`;
200
- this.interactionMode = `standard`;
201
- this.itemWrap = `stop`;
202
- this._searchVisible = false;
203
- this._searchQuery = ``;
204
- this.#customWrappers = /* @__PURE__ */ new Map();
205
- this.#wrappedElements = /* @__PURE__ */ new Set();
206
- this.selection = new ListSelectionController({
207
- getOrderedItems: () => this._getVisibleItems(),
208
- isSelectable: (el) => this._isCheckable(el),
209
- getModes: () => ({
210
- selectionMode: this.selectionMode,
211
- interactionMode: this.interactionMode
212
- }),
213
- onChange: (selected, previous) => {
214
- this._syncItemStates();
215
- this._dispatch(`list-select`, {
216
- selected,
217
- previous
218
- });
219
- }
220
- });
221
- this.tickled = new TickledController(this, {
222
- getItems: () => this._getVisibleItems().map((el) => ({
223
- id: el,
224
- enabled: !el.hasAttribute(`not-checked`)
225
- })),
226
- onTickle: (id) => {
227
- if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
228
- },
229
- onStateChange: () => this._syncItemStates(),
230
- itemWrap: this.itemWrap
231
- });
232
- this._onPointerOver = (e) => {
233
- const item = this._findListItem(e.composedPath());
234
- if (item) this.tickled.handlePointerEnter(item);
235
- };
236
- this._onPointerLeave = (e) => {
237
- this.tickled.handlePointerLeave(e);
238
- };
239
- this._onKeyDown = (e) => {
240
- if ((e.ctrlKey || e.metaKey) && e.key === `f`) {
241
- e.preventDefault();
242
- this._openSearch();
243
- return;
244
- }
245
- if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
246
- if (this.selection.canSelectAll()) {
247
- e.preventDefault();
248
- this.selection.selectAll();
249
- }
250
- return;
251
- }
252
- if (this._searchVisible) return;
253
- switch (e.key) {
254
- case `ArrowDown`:
255
- e.preventDefault();
256
- this.tickled.handleKeyboardNavigation(`next`);
257
- if (e.shiftKey && this.interactionMode === `vscode`) {
258
- const cur = this.tickled.tickledId;
259
- if (cur) this.selection.rangeTo(cur);
260
- }
261
- break;
262
- case `ArrowUp`:
263
- e.preventDefault();
264
- this.tickled.handleKeyboardNavigation(`previous`);
265
- if (e.shiftKey && this.interactionMode === `vscode`) {
266
- const cur = this.tickled.tickledId;
267
- if (cur) this.selection.rangeTo(cur);
268
- }
269
- break;
270
- case `Enter`: {
271
- const id = this.tickled.tickledId;
272
- if (id) {
273
- e.preventDefault();
274
- this._dispatch(`list-item-click`, { item: id });
275
- this._activateItem(id);
276
- }
277
- break;
278
- }
279
- case ` `: {
280
- const id = this.tickled.tickledId;
281
- if (id) {
282
- e.preventDefault();
283
- this.selection.handleSpace(id);
284
- }
285
- break;
286
- }
287
- case `Escape`: this.selection.clear();
288
- }
289
- };
290
- this._onClick = (e) => {
291
- const item = this._findListItem(e.composedPath());
292
- if (!item) return;
293
- this._dispatch(`list-item-click`, { item });
294
- this.selection.handleClick(item, e);
295
- };
296
- this._onDblClick = (e) => {
297
- const item = this._findListItem(e.composedPath());
298
- if (item) this._activateItem(item);
299
- };
300
- this._onSearchInput = (e) => {
301
- const input = e.target;
302
- this._searchQuery = input.value;
303
- this.#search?.setQuery(this._searchQuery);
304
- };
305
- this._onSearchKeydown = (e) => {
306
- if (e.key === `Escape`) {
307
- e.stopPropagation();
308
- this._closeSearch();
309
- } else if (e.key === `ArrowDown`) {
310
- e.preventDefault();
311
- this.tickled.handleKeyboardNavigation(`next`);
312
- } else if (e.key === `ArrowUp`) {
313
- e.preventDefault();
314
- this.tickled.handleKeyboardNavigation(`previous`);
315
- } else if (e.key === `Enter`) {
316
- e.preventDefault();
317
- e.stopPropagation();
318
- const id = this.tickled.tickledId ?? this._getVisibleItems()[0];
319
- if (id) {
320
- this._dispatch(`list-item-click`, { item: id });
321
- this._activateItem(id);
322
- this._closeSearch();
323
- }
324
- }
325
- };
326
- }
327
- #search;
328
- #customWrappers;
329
- #wrappedElements;
330
- connectedCallback() {
331
- super.connectedCallback();
332
- if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
333
- this.addEventListener(`pointerover`, this._onPointerOver);
334
- this.addEventListener(`pointerleave`, this._onPointerLeave);
335
- this.addEventListener(`keydown`, this._onKeyDown);
336
- this.addEventListener(`click`, this._onClick);
337
- this.addEventListener(`dblclick`, this._onDblClick);
338
- }
339
- disconnectedCallback() {
340
- super.disconnectedCallback();
341
- this.removeEventListener(`pointerover`, this._onPointerOver);
342
- this.removeEventListener(`pointerleave`, this._onPointerLeave);
343
- this.removeEventListener(`keydown`, this._onKeyDown);
344
- this.removeEventListener(`click`, this._onClick);
345
- this.removeEventListener(`dblclick`, this._onDblClick);
346
- this.#search?.dispose();
347
- this.#search = void 0;
348
- }
349
- firstUpdated() {
350
- this.#search = createElementIncrSearch({
351
- container: this,
352
- itemSelector: ITEM_SELECTOR,
353
- getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
354
- getKey: (el) => el.getAttribute(`data-key`) ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
355
- highlightKey: `vertical-list-search`
356
- });
357
- }
358
- updated(changed) {
359
- super.updated(changed);
360
- if (changed.has(`interactionMode`)) {
361
- this.selection.clearAnchor();
362
- const previousSelected = this.selection.selected;
363
- const savedWrappers = changed.get(`interactionMode`) === `checked` ? new Map(this.#customWrappers) : null;
364
- this._syncCheckboxes();
365
- if (previousSelected.size > 0) {
366
- if (savedWrappers && savedWrappers.size > 0) this.selection.remap((el) => savedWrappers.get(el) ?? el);
367
- else if (this.#customWrappers.size > 0) {
368
- const innerToWrapper = new Map([...this.#customWrappers].map(([w, i]) => [i, w]));
369
- this.selection.remap((el) => innerToWrapper.get(el) ?? el);
370
- }
371
- }
372
- this._syncItemStates();
373
- } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
374
- }
375
- /** The most recently selected item, or `undefined` if nothing is selected. */
376
- get selectedItem() {
377
- return [...this.selection.selected].at(-1);
378
- }
379
- /** The full current selection set (read-only). */
380
- get selectedItems() {
381
- return this.selection.selected;
382
- }
383
- addItem(item, options) {
384
- let el;
385
- if (typeof item === `string`) {
386
- const li = document.createElement(`li`);
387
- li.textContent = item;
388
- el = li;
389
- } else el = item;
390
- if (options?.notChecked) el.setAttribute(`not-checked`, ``);
391
- this.appendChild(el);
392
- if (this.interactionMode === `checked`) this._syncCheckboxes();
393
- }
394
- removeItem(item) {
395
- item.remove();
396
- }
397
- clearItems() {
398
- this.#customWrappers.clear();
399
- this.replaceChildren();
400
- this.selection.reset();
401
- this._syncItemStates();
402
- }
403
- /**
404
- * Replace the entire item list in one call.
405
- * Equivalent to `clearItems()` followed by `addItem()` for each element.
406
- * Preserves `interactionMode` checkbox wrapping.
407
- */
408
- setItems(items) {
409
- this.clearItems();
410
- for (const item of items) this.appendChild(item);
411
- if (this.interactionMode === `checked`) this._syncCheckboxes();
412
- }
413
- /**
414
- * Dispatch a command to this list. No-op by default.
415
- * Intended for use with `ixfx-grouped-item-lister`'s `dispatch()` method,
416
- * which forwards commands to all sub-components.
417
- */
418
- dispatch(_commandId, _args) {}
419
- select(item) {
420
- this.selection.replace(item);
421
- }
422
- selectMany(items) {
423
- this.selection.selectMany(items);
424
- }
425
- deselect(item) {
426
- this.selection.deselect(item);
427
- }
428
- clearSelection() {
429
- this.selection.clear();
430
- }
431
- selectAll() {
432
- if (this.selection.canSelectAll()) this.selection.selectAll();
433
- }
434
- *selectedFilter(kind) {
435
- const wanted = (el) => {
436
- switch (kind) {
437
- case `selected`: return el.hasAttribute(`data-selected`);
438
- case `checked`: return el.hasAttribute(`data-checked`);
439
- case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);
440
- }
441
- };
442
- for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
443
- if (this.#wrappedElements.has(el)) continue;
444
- if (el.hidden) continue;
445
- if (wanted(el)) yield el;
446
- }
447
- }
448
- _getVisibleItems() {
449
- return Array.from(this.querySelectorAll(ITEM_SELECTOR)).filter((el) => {
450
- if (this.#wrappedElements.has(el)) return false;
451
- if (el.hidden) return false;
452
- if (this.filterPredicate && !this.filterPredicate(el)) return false;
453
- return true;
454
- });
455
- }
456
- _isCheckable(item) {
457
- if (this.interactionMode !== `checked`) return true;
458
- return !(this.#customWrappers.get(item) ?? item).hasAttribute(`not-checked`);
459
- }
460
- _syncItemStates() {
461
- for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
462
- if (this.#wrappedElements.has(el)) continue;
463
- const item = this.#customWrappers.get(el) ?? el;
464
- el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
465
- el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
466
- if (this.interactionMode === `checked`) el.toggleAttribute(`data-checked`, !item.hasAttribute(`not-checked`) && this.selection.selected.has(el));
467
- else el.removeAttribute(`data-checked`);
468
- }
469
- this.dataset.inputMode = this.tickled.inputMode;
470
- this._syncCheckboxes();
471
- }
472
- _syncCheckboxes() {
473
- const inCheckedMode = this.interactionMode === `checked`;
474
- const wantsCustom = typeof this.checkboxRenderer === `function`;
475
- if (inCheckedMode) for (const inner of Array.from(this.children)) {
476
- if (!inner.hasAttribute(`data-list-item`)) continue;
477
- if (inner.parentElement !== this) continue;
478
- const wrapper = document.createElement(`li`);
479
- wrapper.className = `ixfx-custom-row`;
480
- const key = inner.getAttribute(`data-key`);
481
- const label = inner.getAttribute(`data-search-label`);
482
- if (key !== null) wrapper.setAttribute(`data-key`, key);
483
- if (label !== null) wrapper.setAttribute(`data-search-label`, label);
484
- inner.removeAttribute(`data-list-item`);
485
- this.insertBefore(wrapper, inner);
486
- wrapper.appendChild(inner);
487
- this.#customWrappers.set(wrapper, inner);
488
- this.#wrappedElements.add(inner);
489
- }
490
- else {
491
- for (const [wrapper, inner] of this.#customWrappers) {
492
- if (wrapper.parentElement !== this) continue;
493
- inner.setAttribute(`data-list-item`, ``);
494
- this.insertBefore(inner, wrapper);
495
- wrapper.remove();
496
- }
497
- this.#customWrappers.clear();
498
- this.#wrappedElements.clear();
499
- }
500
- for (const el of this.querySelectorAll(`li`)) {
501
- const existing = el.querySelector(`.ixfx-list-checkbox`);
502
- if (!inCheckedMode) {
503
- if (existing) existing.remove();
504
- continue;
505
- }
506
- if ((this.#customWrappers.get(el) ?? el).hasAttribute(`not-checked`)) {
507
- if (existing) existing.remove();
508
- continue;
509
- }
510
- const existingIsCustom = existing?.classList.contains(`ixfx-list-checkbox-custom`) ?? false;
511
- if (existing && existingIsCustom !== wantsCustom) existing.remove();
512
- let cb = el.querySelector(`.ixfx-list-checkbox`);
513
- if (!cb) {
514
- if (wantsCustom) {
515
- const opts = this.checkboxRenderer;
516
- const margin = opts.margin ?? `0`;
517
- const width = opts.width ?? `24px`;
518
- cb = this._createCustomCheckbox(el, margin, width);
519
- } else cb = this._createNativeCheckboxCell(el);
520
- el.prepend(cb);
521
- }
522
- const checked = this.selection.selected.has(el);
523
- if (wantsCustom) {
524
- cb.setAttribute(`aria-checked`, String(checked));
525
- cb.toggleAttribute(`data-checked`, checked);
526
- if (this.checkboxRenderer) {
527
- const renderer = typeof this.checkboxRenderer === `function` ? this.checkboxRenderer : this.checkboxRenderer.renderer;
528
- render(renderer(checked), cb);
529
- }
530
- } else {
531
- const input = cb.querySelector(`input.ixfx-list-checkbox`);
532
- if (input) input.checked = checked;
533
- }
534
- }
535
- }
536
- /**
537
- * Native checkbox cell: a span that stretches to the row's full cross-axis
538
- * (so the entire column height is a click target) with a real `<input>`
539
- * centred inside. The span owns the click; the input is purely visual and
540
- * has `pointer-events:none` so a single click produces one toggle.
541
- */
542
- _createNativeCheckboxCell(el) {
543
- const cell = document.createElement(`span`);
544
- cell.className = `ixfx-list-checkbox ixfx-list-checkbox-native`;
545
- cell.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:18px;margin:0 6px 0 0;cursor:pointer;pointer-events:auto`;
546
- cell.tabIndex = -1;
547
- cell.setAttribute(`role`, `checkbox`);
548
- cell.setAttribute(`part`, `checkbox`);
549
- cell.addEventListener(`click`, (e) => {
550
- e.stopPropagation();
551
- this.selection.toggle(el);
552
- });
553
- const input = document.createElement(`input`);
554
- input.type = `checkbox`;
555
- input.className = `ixfx-list-checkbox`;
556
- input.style.cssText = `pointer-events:none;cursor:pointer;margin:0`;
557
- cell.appendChild(input);
558
- return cell;
559
- }
560
- /**
561
- * Custom checkbox cell: same shape as the native cell (a stretching span)
562
- * so behaviour is identical. The renderer's content goes inside.
563
- */
564
- _createCustomCheckbox(el, margin, width) {
565
- const span = document.createElement(`span`);
566
- span.className = `ixfx-list-checkbox ixfx-list-checkbox-custom`;
567
- span.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:${width};margin:${margin};cursor:pointer;pointer-events:auto`;
568
- span.tabIndex = -1;
569
- span.setAttribute(`role`, `checkbox`);
570
- span.setAttribute(`part`, `checkbox`);
571
- span.addEventListener(`click`, (e) => {
572
- e.stopPropagation();
573
- this.selection.toggle(el);
574
- });
575
- return span;
576
- }
577
- _findListItem(path) {
578
- for (const target of path) {
579
- if (!(target instanceof Element)) continue;
580
- if (target === this) break;
581
- if (target.parentElement === this && target.matches(ITEM_SELECTOR)) return target;
582
- }
583
- }
584
- _dispatch(name, detail) {
585
- this.dispatchEvent(new CustomEvent(name, {
586
- detail,
587
- bubbles: true,
588
- composed: true
589
- }));
590
- }
591
- _openSearch() {
592
- this._searchVisible = true;
593
- this.updateComplete.then(() => this._searchInputEl?.focus());
594
- }
595
- _closeSearch() {
596
- this._searchVisible = false;
597
- this._searchQuery = ``;
598
- this.#search?.clearQuery();
599
- this.focus();
600
- }
601
- _activateItem(item) {
602
- if (this.interactionMode !== `manual`) this.selection.replace(item);
603
- this._dispatch(`list-activate`, { item });
604
- }
605
- static {
606
- this.styles = [themeFallbacks, css`
607
- :host {
608
- display: block;
609
- position: relative;
610
- background: var(--list-bg, var(--surface-3));
611
- color: var(--list-text, var(--surface-3-text));
612
- border-radius: var(--list-radius, var(--radius-s));
613
- border: 1px solid var(--list-border, var(--border));
614
- width: 100%;
615
- height: 100%;
616
- overflow: hidden;
617
- box-sizing: border-box;
618
- outline: none;
619
- --ixfx-list-item-padding: var(--ixfx-list-item-padding, var(--list-item-padding, 4px var(--space-m, 8px)));
620
- }
621
-
622
- :host(:focus-visible) {
623
- outline: 2px solid var(--accent);
624
- outline-offset: 2px;
625
- }
626
-
627
- .container {
628
- position: relative;
629
- width: 100%;
630
- height: 100%;
631
- }
632
-
633
- ul {
634
- list-style: none;
635
- margin: 0;
636
- padding: 0;
637
- width: 100%;
638
- height: 100%;
639
- overflow-x: hidden;
640
- overflow-y: auto;
641
- box-sizing: border-box;
642
- /* Scrollbar theming: end users override these CSS variables to control
643
- the native scrollbar (scrollbar-width, scrollbar-gutter, scrollbar-color). */
644
- scrollbar-width: var(--list-scrollbar-width, auto);
645
- scrollbar-gutter: var(--list-scrollbar-gutter, auto);
646
- scrollbar-color: var(--list-scrollbar-color, auto);
647
- }
648
-
649
- ::slotted([hidden]) {
650
- display: none !important;
651
- }
652
-
653
- ::slotted(li) {
654
- display: flex;
655
- align-items: center;
656
- padding: var(--ixfx-list-item-padding);
657
- min-height: var(--list-item-height, 28px);
658
- cursor: pointer;
659
- user-select: none;
660
- transition: background var(--transition, 0.1s ease-out),
661
- outline-color var(--transition, 0.1s ease-out);
662
- box-sizing: border-box;
663
- outline: 1px solid transparent;
664
- }
665
-
666
- /* Custom shadow-DOM components handle their own internal layout/padding */
667
- ::slotted([data-list-item]) {
668
- display: block;
669
- cursor: pointer;
670
- user-select: none;
671
- transition: background var(--transition, 0.1s ease-out),
672
- outline-color var(--transition, 0.1s ease-out);
673
- outline: 1px solid transparent;
674
- }
675
-
676
- /* In checked mode, only the checkbox column is clickable; the row body
677
- gets a default cursor so it doesn't suggest otherwise. */
678
- :host([interaction-mode="checked"]) ::slotted(li),
679
- :host([interaction-mode="checked"]) ::slotted([data-list-item]) {
680
- cursor: default;
681
- }
682
-
683
- /* Wrapper inserted around custom [data-list-item] elements in checked mode.
684
- Provides the flex layout so the injected checkbox can stretch to the
685
- row's full height via align-self:stretch. */
686
- .ixfx-custom-row {
687
- display: flex;
688
- align-items: center;
689
- min-height: var(--list-item-height, 28px);
690
- box-sizing: border-box;
691
- cursor: default;
692
- user-select: none;
693
- }
694
-
695
- ::slotted([data-tickled]) {
696
- background: var(--item-bg-tickled);
697
- outline: var(--item-border-tickled);
698
- }
699
-
700
- ::slotted([data-selected]) {
701
- background: var(--item-bg-selected);
702
- color: var(--item-text-selected);
703
- outline: 1px solid transparent;
704
- }
705
-
706
- ::slotted([data-selected][data-tickled]) {
707
- background: var(--item-bg-selected-tickled);
708
- outline: 2px dashed var(--surface-3);
709
- outline-offset: -2px;
710
- }
711
-
712
- /* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */
713
- :host([interaction-mode="checked"]) ::slotted([data-selected]) {
714
- background: transparent;
715
- color: inherit;
716
- outline: 1px solid transparent;
717
- }
718
-
719
- :host([interaction-mode="checked"]) ::slotted([data-selected][data-tickled]) {
720
- background: var(--item-bg-tickled);
721
- color: inherit;
722
- outline: var(--item-border-tickled);
723
- }
724
-
725
- .search-overlay {
726
- position: absolute;
727
- top: 8px;
728
- right: 8px;
729
- z-index: 10;
730
- display: flex;
731
- align-items: center;
732
- gap: 4px;
733
- background: var(--surface-4, #2a2a2a);
734
- border: 1px solid var(--border, #444);
735
- border-radius: var(--radius-m, 4px);
736
- padding: 4px 6px;
737
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
738
- }
739
-
740
- .search-overlay[hidden] {
741
- display: none;
742
- }
743
-
744
- .search-input {
745
- background: transparent;
746
- border: none;
747
- outline: none;
748
- color: inherit;
749
- font-size: var(--text-m, 0.875em);
750
- min-width: 140px;
751
- }
752
-
753
- .search-close {
754
- background: transparent;
755
- border: none;
756
- color: var(--surface-muted-text, #888);
757
- cursor: pointer;
758
- font-size: 1.1em;
759
- line-height: 1;
760
- padding: 0 2px;
761
- }
762
-
763
- .search-close:hover {
764
- color: inherit;
765
- }
766
- `];
767
- }
768
- render() {
769
- return html`
770
- <div class="container">
771
- <ul part="list">
772
- <slot></slot>
773
- </ul>
774
- <div
775
- class="search-overlay"
776
- part="search-overlay"
777
- ?hidden=${!this._searchVisible}
778
- >
779
- <input
780
- type="text"
781
- class="search-input"
782
- placeholder="Search…"
783
- .value=${this._searchQuery}
784
- @input=${this._onSearchInput}
785
- @keydown=${this._onSearchKeydown}
786
- />
787
- <button class="search-close" @click=${this._closeSearch} title="Close search (Esc)">×</button>
788
- </div>
789
- </div>
790
- `;
791
- }
792
- };
793
- __decorate([property({
794
- type: String,
795
- attribute: `selection-mode`
796
- })], VerticalListElement.prototype, "selectionMode", void 0);
797
- __decorate([property({
798
- type: String,
799
- attribute: `interaction-mode`
800
- })], VerticalListElement.prototype, "interactionMode", void 0);
801
- __decorate([property({ attribute: false })], VerticalListElement.prototype, "filterPredicate", void 0);
802
- __decorate([property({
803
- type: String,
804
- attribute: `item-wrap`
805
- })], VerticalListElement.prototype, "itemWrap", void 0);
806
- __decorate([property({ attribute: false })], VerticalListElement.prototype, "checkboxRenderer", void 0);
807
- __decorate([state()], VerticalListElement.prototype, "_searchVisible", void 0);
808
- __decorate([state()], VerticalListElement.prototype, "_searchQuery", void 0);
809
- __decorate([query(`.search-input`)], VerticalListElement.prototype, "_searchInputEl", void 0);
810
- VerticalListElement = __decorate([safeCustomElement(`ixfx-vertical-list`)], VerticalListElement);
811
- //#endregion
812
- export { ListSelectionController as n, isMultiMode as r, VerticalListElement as t };
813
-
814
- //# sourceMappingURL=vertical-list-B4w_ksJk.js.map