@ixfx/components 0.7.5 → 0.8.0

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 (72) hide show
  1. package/bundle/index.d.ts +256 -17
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +1461 -165
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-token.js +1 -1
  6. package/dist/{data-grid-CtJ35v8L.d.ts → data-grid-D-z7bx5-.d.ts} +2 -2
  7. package/dist/{data-grid-CtJ35v8L.d.ts.map → data-grid-D-z7bx5-.d.ts.map} +1 -1
  8. package/dist/data-grid.d.ts +1 -1
  9. package/dist/data-grid.js +1 -1
  10. package/dist/{detail-list-BS5dRDda.d.ts → detail-list-_ZJHYEBr.d.ts} +3 -3
  11. package/dist/{detail-list-BS5dRDda.d.ts.map → detail-list-_ZJHYEBr.d.ts.map} +1 -1
  12. package/dist/detail-list.d.ts +2 -2
  13. package/dist/detail-list.js +1 -1
  14. package/dist/grid-list-nRUQOcEL.js +1537 -0
  15. package/dist/grid-list-nRUQOcEL.js.map +1 -0
  16. package/dist/{grid-list-D-GbghSH.d.ts → grid-list-u33vmKzb.d.ts} +116 -15
  17. package/dist/grid-list-u33vmKzb.d.ts.map +1 -0
  18. package/dist/grid-list.d.ts +1 -1
  19. package/dist/grid-list.js +1 -993
  20. package/dist/{incr-search-DpbSJtgj.js → incr-search-C05WjbZh.js} +40 -3
  21. package/dist/incr-search-C05WjbZh.js.map +1 -0
  22. package/dist/incr-search.d.ts +2 -2
  23. package/dist/incr-search.js +2 -2
  24. package/dist/{index-CBS4z1v-.d.ts → index-h6gxtfHH.d.ts} +34 -2
  25. package/dist/index-h6gxtfHH.d.ts.map +1 -0
  26. package/dist/index.d.ts +60 -11
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +342 -15
  29. package/dist/index.js.map +1 -1
  30. package/dist/led.d.ts +33 -1
  31. package/dist/led.d.ts.map +1 -1
  32. package/dist/led.js +157 -13
  33. package/dist/led.js.map +1 -1
  34. package/dist/miller.d.ts +1 -1
  35. package/dist/miller.js +1 -1
  36. package/dist/narrowed-text.js +1 -1
  37. package/dist/panel.d.ts +3 -1
  38. package/dist/panel.d.ts.map +1 -1
  39. package/dist/panel.js +31 -1
  40. package/dist/panel.js.map +1 -1
  41. package/dist/plots.d.ts +2 -2
  42. package/dist/plots.js +1 -1
  43. package/dist/{titlebar-Bj5pfdw_.d.ts → titlebar-YQ-jVQoS.d.ts} +2 -2
  44. package/dist/{titlebar-Bj5pfdw_.d.ts.map → titlebar-YQ-jVQoS.d.ts.map} +1 -1
  45. package/dist/titlebar.d.ts +1 -1
  46. package/dist/titlebar.js +83 -1
  47. package/dist/titlebar.js.map +1 -1
  48. package/dist/{tree-DAii2A1g.js → tree-Chptss7I.js} +2 -2
  49. package/dist/{tree-DAii2A1g.js.map → tree-Chptss7I.js.map} +1 -1
  50. package/dist/tree.d.ts +1 -1
  51. package/dist/tree.js +1 -1
  52. package/dist/{vertical-list-DwtCPy1b.d.ts → vertical-list-CFuFDygi.d.ts} +2 -2
  53. package/dist/{vertical-list-DwtCPy1b.d.ts.map → vertical-list-CFuFDygi.d.ts.map} +1 -1
  54. package/dist/vertical-list.d.ts +1 -1
  55. package/dist/vertical-list.js +1 -1
  56. package/dist/{xy-axis-5KhVTNoX.d.ts → xy-axis-BfP9hKLj.d.ts} +25 -2
  57. package/dist/{xy-axis-5KhVTNoX.d.ts.map → xy-axis-BfP9hKLj.d.ts.map} +1 -1
  58. package/dist/{xy-axis-VrX8dmvS.js → xy-axis-DzM1brBZ.js} +141 -8
  59. package/dist/xy-axis-DzM1brBZ.js.map +1 -0
  60. package/docs-user/grid-list.md +99 -14
  61. package/docs-user/index.json +3 -3
  62. package/docs-user/led.md +37 -7
  63. package/docs-user/llms.txt +1 -1
  64. package/docs-user/panel.md +68 -13
  65. package/docs-user/titlebar.md +3 -4
  66. package/llms.txt +1 -1
  67. package/package.json +1 -1
  68. package/dist/grid-list-D-GbghSH.d.ts.map +0 -1
  69. package/dist/grid-list.js.map +0 -1
  70. package/dist/incr-search-DpbSJtgj.js.map +0 -1
  71. package/dist/index-CBS4z1v-.d.ts.map +0 -1
  72. package/dist/xy-axis-VrX8dmvS.js.map +0 -1
package/dist/grid-list.js CHANGED
@@ -1,994 +1,2 @@
1
- import { t as TickledController } from "./tickled-controller-C66DgJHl.js";
2
- import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
3
- import { t as themeFallbacks } from "./fallbacks-DNI9xt0F.js";
4
- import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
5
- import { c as createElementListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-DpbSJtgj.js";
6
- import { i as listItemFadeKeyframes } from "./list-item-animation-DuiwgN3l.js";
7
- import { t as ListSelectionController } from "./list-selection-controller-DTFI40li.js";
8
- import "./interaction-Cr13azbM.js";
9
- import { t as scrollFadeStyles } from "./scroll-fade-CAs1EabA.js";
10
- import { LitElement, css, html } from "lit";
11
- import { property, state } from "lit/decorators.js";
12
- import { createCommandRegistry } from "@clinth/ui-commands";
13
- import { repeat } from "lit/directives/repeat.js";
14
- import { createRef, ref } from "lit/directives/ref.js";
15
- //#region src/grid-list/grid-list.ts
16
- const ITEM_SELECTOR = `:scope > *`;
17
- const MIN_ITEM_SIZE = 16;
18
- /**
19
- * How long the host keeps the `[reflowing]` attribute after items are inserted
20
- * mid-list — long enough for the cell move-transition and the enter animation
21
- * to finish. Kept a touch above the default `--grid-list-item-move-duration`.
22
- */
23
- const REFLOW_WINDOW_MS = 320;
24
- function computeGridWindow(count, itemWidth, rowHeight, gap, viewportWidth, viewportHeight, scrollOffset, orientation, overscan = 2) {
25
- const w = Math.max(1, itemWidth);
26
- const h = Math.max(1, rowHeight);
27
- const g = Math.max(0, gap);
28
- const colStride = w + g;
29
- const rowStride = h + g;
30
- const offset = Math.max(0, scrollOffset);
31
- if (orientation === `row`) {
32
- const rows = count === 0 ? 0 : 1;
33
- const totalWidth = count === 0 ? 0 : (count - 1) * colStride + w;
34
- if (count === 0 || viewportWidth <= 0) return {
35
- cols: count,
36
- rows,
37
- totalHeight: 0,
38
- totalWidth,
39
- startIndex: 0,
40
- endIndex: 0,
41
- colStride,
42
- rowStride,
43
- horizontal: true
44
- };
45
- const firstVisible = Math.max(0, Math.floor(offset / colStride));
46
- const lastVisible = Math.ceil((offset + viewportWidth) / colStride);
47
- return {
48
- cols: count,
49
- rows,
50
- totalHeight: 0,
51
- totalWidth,
52
- startIndex: Math.max(0, firstVisible - overscan),
53
- endIndex: Math.min(count, lastVisible + overscan),
54
- colStride,
55
- rowStride,
56
- horizontal: true
57
- };
58
- }
59
- const cols = orientation === `column` ? 1 : Math.max(1, Math.floor((Math.max(0, viewportWidth) + g) / colStride));
60
- const rows = count === 0 ? 0 : Math.ceil(count / cols);
61
- const totalHeight = rows === 0 ? 0 : (rows - 1) * rowStride + h;
62
- if (count === 0 || viewportHeight <= 0) return {
63
- cols,
64
- rows,
65
- totalHeight,
66
- totalWidth: 0,
67
- startIndex: 0,
68
- endIndex: 0,
69
- colStride,
70
- rowStride,
71
- horizontal: false
72
- };
73
- const firstVisibleRow = Math.max(0, Math.floor(offset / rowStride));
74
- const lastVisibleRow = Math.ceil((offset + viewportHeight) / rowStride);
75
- const rowStart = Math.max(0, firstVisibleRow - overscan);
76
- const rowEnd = Math.min(rows, lastVisibleRow + overscan);
77
- return {
78
- cols,
79
- rows,
80
- totalHeight,
81
- totalWidth: 0,
82
- startIndex: rowStart * cols,
83
- endIndex: Math.min(count, rowEnd * cols),
84
- colStride,
85
- rowStride,
86
- horizontal: false
87
- };
88
- }
89
- function columnOf(index, cols) {
90
- return cols <= 1 ? 0 : index % cols;
91
- }
92
- function rowOf(index, cols) {
93
- return cols <= 1 ? index : Math.floor(index / cols);
94
- }
95
- let GridListElement = class GridListElement extends LitElement {
96
- constructor(..._args) {
97
- super(..._args);
98
- this.selectionMode = `single`;
99
- this.interactionMode = `standard`;
100
- this.itemSize = 160;
101
- this.gap = 8;
102
- this.orientation = `grid`;
103
- this.captionStyle = `under`;
104
- this.captionAlign = `middle`;
105
- this.captionPosition = `bottom`;
106
- this.captionBackdrop = `scrim`;
107
- this.captionHeight = 24;
108
- this.itemWrap = `stop`;
109
- this._scrollTop = 0;
110
- this._scrollLeft = 0;
111
- this._viewportHeight = 0;
112
- this._viewportWidth = 0;
113
- this.#viewportRef = createRef();
114
- this.#scrollRaf = 0;
115
- this.#items = [];
116
- this.#itemWrap = `stop`;
117
- this.#entering = /* @__PURE__ */ new Set();
118
- this.#reflowTimer = 0;
119
- this.#slotNames = /* @__PURE__ */ new WeakMap();
120
- this.#slotSeq = 0;
121
- this.selection = new ListSelectionController({
122
- getOrderedItems: () => this._getVisibleItems(),
123
- isSelectable: () => true,
124
- getModes: () => ({
125
- selectionMode: this.selectionMode,
126
- interactionMode: this.interactionMode
127
- }),
128
- getKey: keyForElement,
129
- onChange: (selected, previous) => {
130
- this._syncItemStates();
131
- this.requestUpdate();
132
- this._dispatch(`list-select`, {
133
- selected,
134
- previous,
135
- selectedKeys: this.selection.selectedKeys
136
- });
137
- }
138
- });
139
- this.tickled = new TickledController(this, {
140
- getItems: () => this.#items.map((el) => ({
141
- id: el,
142
- enabled: !el.hasAttribute(`not-checked`)
143
- })),
144
- onTickle: (id) => {
145
- if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
146
- },
147
- onStateChange: () => this._syncItemStates(),
148
- itemWrap: this.#itemWrap
149
- });
150
- this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
151
- itemSelector: ITEM_SELECTOR,
152
- getLabel: (el) => el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
153
- highlightKey: `grid-list-search`
154
- }) });
155
- this.#onScroll = (e) => {
156
- const el = e.target;
157
- this.#cancelScrollRaf();
158
- this.#scrollRaf = requestAnimationFrame(() => {
159
- this.#scrollRaf = 0;
160
- this._scrollTop = el.scrollTop;
161
- this._scrollLeft = el.scrollLeft;
162
- this.#emitViewportChange();
163
- });
164
- };
165
- this._onKeyDown = (e) => {
166
- if ((e.ctrlKey || e.metaKey) && e.key === `f`) return;
167
- if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
168
- if (this.selection.canSelectAll()) {
169
- e.preventDefault();
170
- this.selection.selectAll();
171
- }
172
- return;
173
- }
174
- const count = this.#items.length;
175
- if (count === 0) return;
176
- const win = this.#currentWindow();
177
- const move = (delta) => {
178
- const from = this.tickled.tickledId ? this.#items.indexOf(this.tickled.tickledId) : -1;
179
- const nextIndex = Math.max(0, Math.min(count - 1, from + delta));
180
- const next = this.#items[nextIndex];
181
- if (next) {
182
- this.tickled.handleKeyboardNavigation(next);
183
- this.#scrollItemIntoView(next);
184
- }
185
- };
186
- const perLine = this.orientation === `grid` ? win.cols : 1;
187
- switch (e.key) {
188
- case `ArrowLeft`:
189
- move(-1);
190
- break;
191
- case `ArrowRight`:
192
- move(1);
193
- break;
194
- case `ArrowUp`:
195
- move(-perLine);
196
- break;
197
- case `ArrowDown`:
198
- move(perLine);
199
- break;
200
- case `Home`:
201
- this.tickled.handleKeyboardNavigation(`first`);
202
- if (this.tickled.tickledId) this.#scrollItemIntoView(this.tickled.tickledId);
203
- break;
204
- case `End`:
205
- this.tickled.handleKeyboardNavigation(`last`);
206
- if (this.tickled.tickledId) this.#scrollItemIntoView(this.tickled.tickledId);
207
- break;
208
- case `PageUp`:
209
- case `PageDown`: {
210
- const mainExtent = win.horizontal ? this._viewportWidth : this._viewportHeight;
211
- const mainStride = win.horizontal ? win.colStride : win.rowStride;
212
- const visibleLines = Math.max(1, Math.floor(mainExtent / mainStride));
213
- move((e.key === `PageDown` ? 1 : -1) * visibleLines * perLine);
214
- break;
215
- }
216
- case `Enter`: {
217
- const id = this.tickled.tickledId;
218
- if (id) {
219
- e.preventDefault();
220
- this._dispatch(`list-item-click`, { item: id });
221
- this.#onCellActivate(id);
222
- }
223
- break;
224
- }
225
- case ` `: {
226
- const id = this.tickled.tickledId;
227
- if (id) {
228
- e.preventDefault();
229
- this.selection.handleSpace(id);
230
- }
231
- break;
232
- }
233
- case `Escape`:
234
- this.selection.clear();
235
- break;
236
- default: return;
237
- }
238
- e.preventDefault();
239
- };
240
- }
241
- static {
242
- this.styles = [
243
- themeFallbacks,
244
- tickledStyles,
245
- tickledItemStyles,
246
- scrollFadeStyles(`x`),
247
- scrollFadeStyles(`y`),
248
- listItemFadeKeyframes,
249
- listSearchStyles,
250
- css`
251
- :host {
252
- --grid-list-item-bg: var(--item-bg);
253
- --grid-list-radius: var(--radius-m);
254
- --grid-list-gap: var(--space-xs);
255
- position: relative;
256
- display: block;
257
- height: 100%;
258
- min-height: 2em;
259
- overflow: hidden;
260
- user-select: none;
261
- background: var(--surface-3);
262
- color: var(--surface-3-text);
263
- border-radius: var(--radius-s);
264
- border: 1px solid var(--border);
265
- }
266
- :host(:focus-visible) {
267
- outline: 2px solid var(--accent);
268
- outline-offset: -2px;
269
- }
270
- .viewport {
271
- position: relative;
272
- height: 100%;
273
- overflow-y: auto;
274
- overflow-x: hidden;
275
- }
276
- :host([orientation="row"]) .viewport {
277
- overflow-x: auto;
278
- overflow-y: hidden;
279
- }
280
- .spacer {
281
- position: relative;
282
- width: 100%;
283
- }
284
- .cell {
285
- position: absolute;
286
- inset: 0 auto auto 0;
287
- box-sizing: border-box;
288
- display: flex;
289
- flex-direction: column;
290
- align-items: stretch;
291
- contain: layout style;
292
- border-radius: var(--grid-list-radius);
293
- }
294
-
295
- /* The square that holds the slotted content. */
296
- .thumb {
297
- position: relative;
298
- flex: 1;
299
- min-height: 0;
300
- display: flex;
301
- align-items: stretch;
302
- box-sizing: border-box;
303
- overflow: hidden;
304
- border-radius: 0;
305
- background: transparent;
306
- border: 1px solid transparent;
307
- transition: background var(--transition), border-color var(--transition);
308
- }
309
- .thumb ::slotted(*) {
310
- flex: 1;
311
- min-width: 0;
312
- min-height: 0;
313
- width: 100%;
314
- display: flex;
315
- align-items: center;
316
- justify-content: center;
317
- overflow: hidden;
318
- }
319
- .thumb ::slotted(*:empty)::after {
320
- content: "Empty";
321
- color: var(--surface-muted-text);
322
- font-size: var(--text-s);
323
- }
324
- ::slotted([hidden]) {
325
- display: none !important;
326
- }
327
-
328
- /* Selection / cursor wash — sits over the slotted content so it reads
329
- on photos and arbitrary DOM alike. */
330
- .thumb::after {
331
- content: "";
332
- position: absolute;
333
- inset: 0;
334
- pointer-events: none;
335
- border-radius: inherit;
336
- background: var(--accent);
337
- opacity: 0;
338
- transition: opacity var(--transition, 0.1s ease-out);
339
- }
340
- .cell.selected .thumb::after {
341
- opacity: 0.18;
342
- }
343
-
344
- /* ---- Caption (base) ---- */
345
- .caption {
346
- box-sizing: border-box;
347
- flex: none;
348
- padding: 3px 6px;
349
- font-size: var(--text-s, 0.8rem);
350
- line-height: 1.3;
351
- text-align: center;
352
- white-space: nowrap;
353
- overflow: hidden;
354
- text-overflow: ellipsis;
355
- color: var(--surface-3-text);
356
- }
357
- :host([caption-align="left"]) .caption { text-align: left; }
358
- :host([caption-align="right"]) .caption { text-align: right; }
359
-
360
- /* ---- Box modes (hidden / under / overlaid): the cell is one box with a
361
- single selection outline ---- */
362
- :host(:not([caption-style="separate"])) .cell {
363
- background: var(--grid-list-item-bg);
364
- border: 1px solid transparent;
365
- overflow: hidden;
366
- transition: background var(--transition), border-color var(--transition);
367
- }
368
- :host(:not([caption-style="separate"])) .cell:hover,
369
- :host(:not([caption-style="separate"])) .cell.tickled {
370
- background: var(--item-bg-tickled);
371
- border: var(--item-border-tickled);
372
- }
373
- :host(:not([caption-style="separate"])) .cell.selected {
374
- outline: 2px solid var(--accent);
375
- outline-offset: -2px;
376
- }
377
- :host(:not([caption-style="separate"])) .cell.selected.tickled {
378
- background: var(--item-bg-selected-tickled);
379
- outline: 2px dashed var(--surface-3);
380
- outline-offset: -2px;
381
- }
382
-
383
- /* under: caption is a strip inside the box, below the content */
384
- :host([caption-style="under"]) .caption {
385
- border-top: 1px solid var(--border, rgba(128, 128, 128, 0.3));
386
- background: var(--surface-4, transparent);
387
- }
388
-
389
- /* overlaid: caption floats over the content */
390
- :host([caption-style="overlaid"]) .caption {
391
- position: absolute;
392
- left: 0;
393
- right: 0;
394
- bottom: 0;
395
- color: #fff;
396
- }
397
- :host([caption-style="overlaid"][caption-position="top"]) .caption {
398
- top: 0;
399
- bottom: auto;
400
- }
401
- :host([caption-style="overlaid"][caption-position="middle"]) .caption {
402
- top: 50%;
403
- bottom: auto;
404
- transform: translateY(-50%);
405
- }
406
- /* overlaid backdrop */
407
- :host([caption-style="overlaid"][caption-backdrop="scrim"]) .caption {
408
- background: rgba(0, 0, 0, 0.55);
409
- }
410
- :host([caption-style="overlaid"][caption-backdrop="gradient"]) .caption {
411
- background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
412
- padding-block: 10px 4px;
413
- }
414
- :host([caption-style="overlaid"][caption-backdrop="gradient"][caption-position="top"]) .caption {
415
- background: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
416
- padding-block: 4px 10px;
417
- }
418
- :host([caption-style="overlaid"][caption-backdrop="gradient"][caption-position="middle"]) .caption {
419
- background: rgba(0, 0, 0, 0.5);
420
- padding-block: 3px;
421
- }
422
- :host([caption-style="overlaid"][caption-backdrop="blur"]) .caption {
423
- background: rgba(20, 20, 20, 0.3);
424
- backdrop-filter: blur(6px);
425
- -webkit-backdrop-filter: blur(6px);
426
- }
427
- :host([caption-style="overlaid"][caption-backdrop="none"]) .caption {
428
- background: none;
429
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 0 6px rgba(0, 0, 0, 0.7);
430
- }
431
-
432
- /* ---- separate mode: square + detached caption, each highlighted ---- */
433
- :host([caption-style="separate"]) .thumb {
434
- flex: none;
435
- border-radius: var(--grid-list-radius);
436
- background: var(--grid-list-item-bg);
437
- border: 1px solid transparent;
438
- }
439
- :host([caption-style="separate"]) .cell:hover .thumb,
440
- :host([caption-style="separate"]) .cell.tickled .thumb {
441
- background: var(--item-bg-tickled);
442
- border: var(--item-border-tickled);
443
- }
444
- :host([caption-style="separate"]) .caption {
445
- display: flex;
446
- align-items: center;
447
- align-self: center;
448
- max-width: 100%;
449
- margin-top: 4px;
450
- border-radius: var(--radius-s, 4px);
451
- }
452
- :host([caption-style="separate"][caption-align="left"]) .caption { align-self: flex-start; }
453
- :host([caption-style="separate"][caption-align="right"]) .caption { align-self: flex-end; }
454
- :host([caption-style="separate"]) .cell.selected .thumb {
455
- outline: 2px solid var(--accent);
456
- outline-offset: 2px;
457
- }
458
- :host([caption-style="separate"]) .cell.selected.tickled .thumb {
459
- background: var(--item-bg-selected-tickled);
460
- outline: 2px dashed var(--surface-3);
461
- outline-offset: 2px;
462
- }
463
- :host([caption-style="separate"]) .cell.selected .caption {
464
- background: var(--accent);
465
- color: var(--accent-text, #000);
466
- }
467
- :host([caption-style="separate"]) .cell.tickled .caption {
468
- background: var(--item-bg-tickled);
469
- outline: var(--item-border-tickled);
470
- }
471
- :host([caption-style="separate"]) .cell.selected.tickled .caption {
472
- background: var(--item-bg-selected-tickled);
473
- outline: 2px dashed var(--surface-3);
474
- outline-offset: -2px;
475
- }
476
-
477
- /* ---- Insertion / removal animation ----
478
- Active only while the host carries [reflowing] — the ~0.3s window after
479
- the child list changes mid-list (see #noteChildMutation). Kept out of
480
- the base rules so it never fires during scroll virtualisation, where
481
- cells are recycled to wildly different positions every frame. Placed
482
- last so it wins over the earlier cell transition rule regardless of
483
- specificity. Survivors slide to their new slot; a freshly inserted cell
484
- also fades in. (A removed item's element is already gone, so there's
485
- nothing left to fade — the gap simply closes as the others slide.) */
486
- :host([reflowing]) .cell {
487
- transition:
488
- transform var(--grid-list-item-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1),
489
- background var(--transition),
490
- border-color var(--transition);
491
- }
492
- .cell.entering {
493
- animation: ixfx-list-item-in var(--grid-list-item-move-duration, 200ms) ease both;
494
- }
495
- @media (prefers-reduced-motion: reduce) {
496
- :host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
497
- .cell.entering { animation: none; }
498
- }
499
- `
500
- ];
501
- }
502
- #viewportRef;
503
- #resizeObserver;
504
- #mutationObserver;
505
- #scrollRaf;
506
- #items;
507
- #itemWrap;
508
- /** Items added mid-list that are still playing their enter animation. */
509
- #entering;
510
- /** Timer that ends the `[reflowing]` window opened by a mid-list insertion. */
511
- #reflowTimer;
512
- /**
513
- * When cell geometry changes (zoom), the item nearest the viewport centre is
514
- * captured here in `willUpdate` and the scroll position is corrected in
515
- * `updated` so that item stays put — rather than the grid anchoring to its
516
- * top-left corner.
517
- */
518
- #zoomAnchor;
519
- /**
520
- * Per-item slot name. Each cell renders its own named `<slot>` and the
521
- * matching light-DOM child carries `slot="<name>"`, so a child is projected
522
- * into exactly one cell. (A single unnamed slot would collapse every child
523
- * into the first cell.)
524
- */
525
- #slotNames;
526
- #slotSeq;
527
- #searchRegistry;
528
- #unregisterSearchCommands;
529
- /** Command registry carrying this grid's `list.search.*` commands. */
530
- get registry() {
531
- return this.#searchRegistry;
532
- }
533
- /** Effective search modes: this component's capability (all three) ∩ the `search-modes` attribute. */
534
- #effectiveSearchModes() {
535
- const raw = this.searchModes?.trim();
536
- if (raw === void 0) return [...LIST_SEARCH_MODES];
537
- const requested = new Set(raw.split(/\s+/));
538
- return LIST_SEARCH_MODES.filter((m) => requested.has(m));
539
- }
540
- #syncSearchCommands() {
541
- if (!this.#searchRegistry) return;
542
- this.search.setModes(this.#effectiveSearchModes());
543
- this.#unregisterSearchCommands?.();
544
- this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
545
- }
546
- connectedCallback() {
547
- super.connectedCallback();
548
- if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
549
- for (const [attr, value] of [
550
- [`caption-style`, this.captionStyle],
551
- [`caption-align`, this.captionAlign],
552
- [`caption-position`, this.captionPosition],
553
- [`caption-backdrop`, this.captionBackdrop]
554
- ]) if (!this.hasAttribute(attr)) this.setAttribute(attr, value);
555
- this.addEventListener(`keydown`, this._onKeyDown);
556
- this.#mutationObserver ??= new MutationObserver((records) => this.#onChildrenChanged(records));
557
- this.#mutationObserver.observe(this, {
558
- childList: true,
559
- attributeFilter: [`hidden`]
560
- });
561
- this.#searchRegistry ??= createCommandRegistry();
562
- this.#syncSearchCommands();
563
- }
564
- disconnectedCallback() {
565
- super.disconnectedCallback();
566
- this.removeEventListener(`keydown`, this._onKeyDown);
567
- this.#resizeObserver?.disconnect();
568
- this.#resizeObserver = void 0;
569
- this.#mutationObserver?.disconnect();
570
- this.#cancelScrollRaf();
571
- this.#unregisterSearchCommands?.();
572
- this.#unregisterSearchCommands = void 0;
573
- this.#searchRegistry = void 0;
574
- if (this.#reflowTimer) {
575
- clearTimeout(this.#reflowTimer);
576
- this.#reflowTimer = 0;
577
- }
578
- }
579
- #onChildrenChanged(records) {
580
- const previousLength = this.#items.length;
581
- this.#syncItems();
582
- this._syncItemStates();
583
- this.#noteChildMutation(previousLength, records);
584
- this.requestUpdate();
585
- this.updateComplete.then(() => this.#emitViewportChange());
586
- }
587
- /**
588
- * When the child list changes mid-list — not a first populate or a wholesale
589
- * replace — mark any newcomers for an enter animation and open a short
590
- * `[reflowing]` window so the cells that shift slide to their new position
591
- * instead of snapping. Removals get the same window (the survivors slide to
592
- * close the gap). The window is bounded in time so the transform transition
593
- * never bleeds into scroll virtualisation.
594
- *
595
- * Reads the `MutationRecord`s directly rather than diffing `#items`, because a
596
- * `requestUpdate()` between the DOM change and this callback (e.g. a caller
597
- * that clears the selection before removing) lets `updated()` re-sync `#items`
598
- * first, which would hide the change from a naive diff.
599
- */
600
- #noteChildMutation(previousLength, records) {
601
- if (!this.hasUpdated || !records || records.length === 0) return;
602
- const currentSet = new Set(this.#items);
603
- const added = [];
604
- let removedCount = 0;
605
- for (const record of records) {
606
- for (const node of record.addedNodes) if (node.nodeType === Node.ELEMENT_NODE && currentSet.has(node)) added.push(node);
607
- for (const node of record.removedNodes) if (node.nodeType === Node.ELEMENT_NODE) removedCount++;
608
- }
609
- if (added.length === 0 && removedCount === 0) return;
610
- if (previousLength === 0 && removedCount === 0) return;
611
- if (added.length > 0 && added.length === this.#items.length) return;
612
- for (const el of added) this.#entering.add(el);
613
- this.toggleAttribute(`reflowing`, true);
614
- if (this.#reflowTimer) clearTimeout(this.#reflowTimer);
615
- this.#reflowTimer = window.setTimeout(() => {
616
- this.#reflowTimer = 0;
617
- this.#entering.clear();
618
- this.toggleAttribute(`reflowing`, false);
619
- this.requestUpdate();
620
- }, REFLOW_WINDOW_MS);
621
- }
622
- #emitViewportChange() {
623
- this._dispatch(`grid-list-viewport-change`, { visibleKeys: this.visibleKeys() });
624
- }
625
- firstUpdated() {
626
- const el = this.#viewportRef.value;
627
- if (!el) return;
628
- this._viewportWidth = el.clientWidth;
629
- this._viewportHeight = el.clientHeight;
630
- this.#resizeObserver = new ResizeObserver((entries) => {
631
- for (const entry of entries) {
632
- this._viewportWidth = entry.contentRect.width;
633
- this._viewportHeight = entry.contentRect.height;
634
- }
635
- });
636
- this.#resizeObserver.observe(el);
637
- this.#syncItems();
638
- this.requestUpdate();
639
- }
640
- willUpdate(changed) {
641
- if (changed.has(`searchModes`)) this.#syncSearchCommands();
642
- if (changed.has(`filterPredicate`)) this.#syncItems();
643
- if (changed.has(`itemSize`) || changed.has(`gap`)) {
644
- this.itemSize = Math.max(MIN_ITEM_SIZE, Math.round(this.itemSize));
645
- this.gap = Math.max(0, Math.round(this.gap));
646
- }
647
- if (changed.has(`captionHeight`)) this.captionHeight = Math.max(0, Math.round(this.captionHeight));
648
- if (changed.has(`itemWrap`)) this.#itemWrap = this.itemWrap;
649
- if ((changed.has(`itemSize`) || changed.has(`gap`) || changed.has(`captionStyle`) || changed.has(`captionHeight`) || changed.has(`orientation`)) && this.hasUpdated && this.#items.length > 0) {
650
- const prevOrientation = changed.get(`orientation`) ?? this.orientation;
651
- const horizontal = prevOrientation === `row`;
652
- const prevOffset = horizontal ? this._scrollLeft : this._scrollTop;
653
- const prevExtent = horizontal ? this._viewportWidth : this._viewportHeight;
654
- if (prevExtent > 0 && prevOffset > 1) {
655
- const prevItemSize = changed.get(`itemSize`) ?? this.itemSize;
656
- const prevGap = changed.get(`gap`) ?? this.gap;
657
- const prevCaptionStyle = changed.get(`captionStyle`) ?? this.captionStyle;
658
- const prevCaptionHeight = changed.get(`captionHeight`) ?? this.captionHeight;
659
- const prevRowHeight = prevItemSize + (prevCaptionStyle === `separate` || prevCaptionStyle === `under` ? prevCaptionHeight : 0);
660
- const prev = computeGridWindow(this.#items.length, prevItemSize, prevRowHeight, prevGap, this._viewportWidth, this._viewportHeight, prevOffset, prevOrientation);
661
- const mainStride = horizontal ? prev.colStride : prev.rowStride;
662
- const centerLine = (prevOffset + prevExtent / 2) / mainStride;
663
- const lineFloor = Math.max(0, Math.floor(centerLine));
664
- const index = horizontal ? Math.min(this.#items.length - 1, lineFloor) : Math.min(this.#items.length - 1, lineFloor * prev.cols + Math.max(0, Math.floor((prev.cols - 1) / 2)));
665
- this.#zoomAnchor = {
666
- index,
667
- lineFrac: centerLine - lineFloor
668
- };
669
- }
670
- }
671
- }
672
- /** Full cell height: the square, plus a caption band in `separate` / `under`. */
673
- #rowHeight() {
674
- const captionBand = this.captionStyle === `separate` || this.captionStyle === `under`;
675
- return this.itemSize + (captionBand ? this.captionHeight : 0);
676
- }
677
- #captionFor(el) {
678
- return el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? ``;
679
- }
680
- updated(changed) {
681
- super.updated(changed);
682
- this.#syncItems();
683
- if (changed.has(`selectionMode`) || changed.has(`interactionMode`)) this.selection.clearAnchor();
684
- if (changed.has(`orientation`)) {
685
- const vp = this.#viewportRef.value;
686
- if (vp) {
687
- if (this.orientation === `row`) {
688
- vp.scrollTop = 0;
689
- this._scrollTop = 0;
690
- } else {
691
- vp.scrollLeft = 0;
692
- this._scrollLeft = 0;
693
- }
694
- }
695
- }
696
- this.#applyZoomAnchor();
697
- }
698
- /** Re-anchor the scroll so the item captured in `willUpdate` stays centred. */
699
- #applyZoomAnchor() {
700
- if (!this.#zoomAnchor) return;
701
- const { index, lineFrac } = this.#zoomAnchor;
702
- this.#zoomAnchor = void 0;
703
- const vp = this.#viewportRef.value;
704
- if (!vp) return;
705
- const win = this.#currentWindow();
706
- if (win.horizontal) {
707
- if (this._viewportWidth <= 0) return;
708
- const targetCenter = (index + lineFrac) * win.colStride;
709
- const maxScroll = Math.max(0, win.totalWidth - this._viewportWidth);
710
- const next = Math.min(maxScroll, Math.max(0, targetCenter - this._viewportWidth / 2));
711
- if (Math.abs(vp.scrollLeft - next) > .5) {
712
- vp.scrollLeft = next;
713
- this._scrollLeft = next;
714
- }
715
- return;
716
- }
717
- if (this._viewportHeight <= 0) return;
718
- const targetCenter = ((win.cols > 0 ? Math.floor(index / win.cols) : 0) + lineFrac) * win.rowStride;
719
- const maxScroll = Math.max(0, win.totalHeight - this._viewportHeight);
720
- const next = Math.min(maxScroll, Math.max(0, targetCenter - this._viewportHeight / 2));
721
- if (Math.abs(vp.scrollTop - next) > .5) {
722
- vp.scrollTop = next;
723
- this._scrollTop = next;
724
- }
725
- }
726
- #syncItems() {
727
- this.#items = Array.from(this.querySelectorAll(ITEM_SELECTOR)).filter((el) => !el.hidden && (this.filterPredicate === void 0 || this.filterPredicate(el)));
728
- for (const el of this.#items) {
729
- const name = this.#slotNameFor(el);
730
- if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
731
- }
732
- }
733
- #slotNameFor(el) {
734
- let name = this.#slotNames.get(el);
735
- if (name === void 0) {
736
- this.#slotSeq += 1;
737
- name = `gl-item-${this.#slotSeq}`;
738
- this.#slotNames.set(el, name);
739
- }
740
- return name;
741
- }
742
- _getVisibleItems() {
743
- return this.#items.filter((el) => {
744
- if (el.hidden) return false;
745
- return true;
746
- });
747
- }
748
- get count() {
749
- return this.#items.length;
750
- }
751
- /**
752
- * Number of cells currently rendered in the shadow DOM. With virtualization
753
- * this is roughly the visible rows plus overscan, regardless of how large
754
- * `count` is — useful for demos/diagnostics.
755
- */
756
- get renderedCount() {
757
- return this.renderRoot.querySelectorAll(`.cell`).length;
758
- }
759
- get selectedItems() {
760
- return this.selection.selected;
761
- }
762
- /** The selection as stable `data-key` / `data-value` strings. */
763
- get selectedKeys() {
764
- return this.selection.selectedKeys;
765
- }
766
- select(item) {
767
- this.selection.replace(item);
768
- }
769
- selectMany(items) {
770
- this.selection.selectMany(items);
771
- }
772
- deselect(item) {
773
- this.selection.deselect(item);
774
- }
775
- clearSelection() {
776
- this.selection.clear();
777
- }
778
- selectAll() {
779
- if (this.selection.canSelectAll()) this.selection.selectAll();
780
- }
781
- /**
782
- * Replace the selection with the items carrying these keys. Unknown keys are
783
- * ignored; survives the host rebuilding the item set. Requires each item to
784
- * have a `data-key` (or `data-value`).
785
- */
786
- selectKeys(keys) {
787
- this.selection.selectKeys(keys, (key) => this.getItem(key));
788
- }
789
- /** Add items by key to the selection (multi-select modes only). */
790
- addSelectedKeys(keys) {
791
- this.selection.addKeys(keys, (key) => this.getItem(key));
792
- }
793
- /** Remove items by key from the selection. */
794
- removeSelectedKeys(keys) {
795
- this.selection.removeKeys(keys, (key) => this.getItem(key));
796
- }
797
- /** Move the cursor to the item with this key and scroll it into view. */
798
- focusKey(key) {
799
- const el = this.getItem(key);
800
- if (!el) return;
801
- this.tickled.focus(el);
802
- this.#scrollItemIntoView(el);
803
- }
804
- /** The key of the item under the cursor, or `undefined`. */
805
- get tickledKey() {
806
- const id = this.tickled.tickledId;
807
- return id ? keyForElement(id) : void 0;
808
- }
809
- getItem(key) {
810
- return this.#items.find((el) => keyForElement(el) === key);
811
- }
812
- getItemByIndex(index) {
813
- return this.#items[index];
814
- }
815
- getKey(item) {
816
- return keyForElement(item);
817
- }
818
- visibleKeys() {
819
- const win = this.#currentWindow();
820
- return this.#items.slice(win.startIndex, win.endIndex).map((el) => keyForElement(el)).filter((k) => k !== void 0);
821
- }
822
- /** Adjust the scroll offset so `el` is fully within the viewport. */
823
- #scrollItemIntoView(el) {
824
- const index = this.#items.indexOf(el);
825
- const vp = this.#viewportRef.value;
826
- if (index < 0 || !vp) return;
827
- const win = this.#currentWindow();
828
- if (win.horizontal) {
829
- const left = index * win.colStride;
830
- const right = left + this.itemSize;
831
- const viewLeft = vp.scrollLeft;
832
- let next = viewLeft;
833
- if (left < viewLeft) next = left;
834
- else if (right > viewLeft + this._viewportWidth) next = right - this._viewportWidth;
835
- if (next !== viewLeft) {
836
- vp.scrollLeft = next;
837
- this._scrollLeft = next;
838
- }
839
- return;
840
- }
841
- const top = (win.cols > 0 ? Math.floor(index / win.cols) : 0) * win.rowStride;
842
- const bottom = top + this.#rowHeight();
843
- const viewTop = vp.scrollTop;
844
- let next = viewTop;
845
- if (top < viewTop) next = top;
846
- else if (bottom > viewTop + this._viewportHeight) next = bottom - this._viewportHeight;
847
- if (next !== viewTop) {
848
- vp.scrollTop = next;
849
- this._scrollTop = next;
850
- }
851
- }
852
- _syncItemStates() {
853
- for (const el of this.#items) {
854
- el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
855
- el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
856
- }
857
- this.setAttribute(`data-input-mode`, this.tickled.inputMode);
858
- }
859
- _dispatch(name, detail) {
860
- this.dispatchEvent(new CustomEvent(name, {
861
- detail,
862
- bubbles: true,
863
- composed: true
864
- }));
865
- }
866
- render() {
867
- const win = this.#currentWindow();
868
- const slice = this.#items.slice(win.startIndex, win.endIndex);
869
- const spacerStyle = win.horizontal ? `width:${win.totalWidth}px;height:100%` : `height:${win.totalHeight}px`;
870
- const scrollFade = win.horizontal ? `scroll-fade-x` : `scroll-fade-y`;
871
- return html`
872
- <div
873
- class="viewport ${scrollFade}"
874
- part="viewport"
875
- ${ref(this.#viewportRef)}
876
- data-input-mode=${this.tickled.inputMode}
877
- @scroll=${this.#onScroll}
878
- >
879
- <div class="spacer" style=${spacerStyle}>
880
- ${repeat(slice, (el) => this.#slotNameFor(el), (el, i) => this.#renderCell(el, win.startIndex + i, win))}
881
- </div>
882
- </div>
883
- ${this.search.renderField()}
884
- `;
885
- }
886
- #renderCell(el, index, win) {
887
- const selected = this.selection.selected.has(el);
888
- const tickled = this.tickled.isTickled(el);
889
- const entering = this.#entering.has(el);
890
- const style = this.captionStyle;
891
- const separate = style === `separate`;
892
- const caption = style === `hidden` ? `` : this.#captionFor(el);
893
- const showCaption = caption !== ``;
894
- const cellHeight = win.rowStride - this.gap;
895
- const x = win.horizontal ? index * win.colStride : columnOf(index, win.cols) * win.colStride;
896
- const y = win.horizontal ? Math.max(0, (this._viewportHeight - cellHeight) / 2) : rowOf(index, win.cols) * win.rowStride;
897
- const thumbStyle = separate ? `height:${this.itemSize}px` : ``;
898
- const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
899
- return html`
900
- <div
901
- class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}"
902
- part="item"
903
- style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
904
- data-index=${index}
905
- @click=${(e) => this.#onCellClick(e, el)}
906
- @dblclick=${() => this.#onCellActivate(el)}
907
- @pointerenter=${() => this.tickled.handlePointerEnter(el)}
908
- >
909
- <div class="thumb" part="thumb" style=${thumbStyle}>
910
- <slot name=${this.#slotNameFor(el)}></slot>
911
- </div>
912
- ${showCaption ? html`<div class="caption" part="caption" style=${captionCss}>${caption}</div>` : ``}
913
- </div>
914
- `;
915
- }
916
- #onScroll;
917
- #cancelScrollRaf() {
918
- if (this.#scrollRaf !== 0) {
919
- cancelAnimationFrame(this.#scrollRaf);
920
- this.#scrollRaf = 0;
921
- }
922
- }
923
- #onCellClick(e, item) {
924
- this.selection.handleClick(item, e);
925
- this._dispatch(`list-item-click`, { item });
926
- }
927
- #onCellActivate(item) {
928
- if (this.interactionMode !== `manual`) this.selection.replace(item);
929
- this._dispatch(`list-activate`, { item });
930
- }
931
- #currentWindow() {
932
- return computeGridWindow(this.#items.length, this.itemSize, this.#rowHeight(), this.gap, this._viewportWidth, this._viewportHeight, this.orientation === `row` ? this._scrollLeft : this._scrollTop, this.orientation);
933
- }
934
- };
935
- __decorate([property({
936
- type: String,
937
- attribute: `selection-mode`
938
- })], GridListElement.prototype, "selectionMode", void 0);
939
- __decorate([property({
940
- type: String,
941
- attribute: `interaction-mode`
942
- })], GridListElement.prototype, "interactionMode", void 0);
943
- __decorate([property({
944
- type: Number,
945
- attribute: `item-size`
946
- })], GridListElement.prototype, "itemSize", void 0);
947
- __decorate([property({ type: Number })], GridListElement.prototype, "gap", void 0);
948
- __decorate([property({
949
- type: String,
950
- reflect: true
951
- })], GridListElement.prototype, "orientation", void 0);
952
- __decorate([property({
953
- type: String,
954
- attribute: `caption-style`,
955
- reflect: true
956
- })], GridListElement.prototype, "captionStyle", void 0);
957
- __decorate([property({
958
- type: String,
959
- attribute: `caption-align`,
960
- reflect: true
961
- })], GridListElement.prototype, "captionAlign", void 0);
962
- __decorate([property({
963
- type: String,
964
- attribute: `caption-position`,
965
- reflect: true
966
- })], GridListElement.prototype, "captionPosition", void 0);
967
- __decorate([property({
968
- type: String,
969
- attribute: `caption-backdrop`,
970
- reflect: true
971
- })], GridListElement.prototype, "captionBackdrop", void 0);
972
- __decorate([property({
973
- type: Number,
974
- attribute: `caption-height`
975
- })], GridListElement.prototype, "captionHeight", void 0);
976
- __decorate([property({
977
- type: String,
978
- attribute: `item-wrap`
979
- })], GridListElement.prototype, "itemWrap", void 0);
980
- __decorate([property({ attribute: false })], GridListElement.prototype, "filterPredicate", void 0);
981
- __decorate([property({
982
- type: String,
983
- attribute: `search-modes`,
984
- reflect: true
985
- })], GridListElement.prototype, "searchModes", void 0);
986
- __decorate([state()], GridListElement.prototype, "_scrollTop", void 0);
987
- __decorate([state()], GridListElement.prototype, "_scrollLeft", void 0);
988
- __decorate([state()], GridListElement.prototype, "_viewportHeight", void 0);
989
- __decorate([state()], GridListElement.prototype, "_viewportWidth", void 0);
990
- GridListElement = __decorate([safeCustomElement(`ixfx-grid-list`)], GridListElement);
991
- //#endregion
1
+ import { t as GridListElement } from "./grid-list-nRUQOcEL.js";
992
2
  export { GridListElement };
993
-
994
- //# sourceMappingURL=grid-list.js.map