@ixfx/components 0.8.2 → 0.8.3

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 +6 -3
  91. package/docs-user/grid-list.md +4 -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
@@ -1,17 +1,18 @@
1
1
  import { t as TickledController } from "./tickled-controller-Dh1Uh_vG.js";
2
2
  import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
3
- import { t as themeFallbacks } from "./fallbacks-DNI9xt0F.js";
3
+ import { t as themeFallbacks } from "./fallbacks-BcKGq-2F.js";
4
4
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
5
5
  import { c as createElementListSearchAdapter, d as createDataListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-C05WjbZh.js";
6
+ import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-SA4piSBi.js";
6
7
  import { i as listItemFadeKeyframes } from "./list-item-animation-DuiwgN3l.js";
7
- import { t as ListSelectionController } from "./list-selection-controller-DTFI40li.js";
8
8
  import "./interaction-B9AAlsFO.js";
9
9
  import { t as scrollFadeStyles } from "./scroll-fade-CAs1EabA.js";
10
10
  import { LitElement, css, html } from "lit";
11
- import { property, state } from "lit/decorators.js";
11
+ import { property, query, state } from "lit/decorators.js";
12
12
  import { createCommandRegistry } from "@clinth/ui-commands";
13
13
  import { repeat } from "lit/directives/repeat.js";
14
14
  import { createRef, ref } from "lit/directives/ref.js";
15
+ import { classMap } from "lit/directives/class-map.js";
15
16
  //#region src/grid-list/grid-list-source.ts
16
17
  const ITEM_SELECTOR$1 = `:scope > *`;
17
18
  const SYNTHETIC_PREFIX = `gl-auto-`;
@@ -232,6 +233,233 @@ var DataGridListSource = class {
232
233
  }
233
234
  };
234
235
  //#endregion
236
+ //#region src/pan-text/pan-text.ts
237
+ const SPEED = 45;
238
+ const END_PADDING = 4;
239
+ const START_DELAY = 300;
240
+ const END_DWELL = 2e3;
241
+ const RESET_DURATION = 400;
242
+ const PAN_EASING = `cubic-bezier(0.25, 0.46, 0.45, 0.94)`;
243
+ let PanTextElement = class PanTextElement extends LitElement {
244
+ constructor(..._args) {
245
+ super(..._args);
246
+ this.direction = `end`;
247
+ this.disabled = false;
248
+ this.mode = `hover`;
249
+ this._isOverflowing = false;
250
+ this._isHovering = false;
251
+ this._loopActive = false;
252
+ this._loopAtEnd = false;
253
+ this._overflow = 0;
254
+ this._resizeObserver = null;
255
+ this._loopTimer = 0;
256
+ this._onMouseEnter = () => {
257
+ this._isHovering = true;
258
+ };
259
+ this._onMouseLeave = () => {
260
+ this._isHovering = false;
261
+ };
262
+ this._measure = () => {
263
+ if (this._isHovering || this._loopAtEnd) return;
264
+ const el = this._textEl;
265
+ if (!el) return;
266
+ this._overflow = Math.max(0, el.scrollWidth - el.clientWidth);
267
+ const overflowing = this._overflow > 0;
268
+ this.classList.toggle(`is-overflowing`, overflowing);
269
+ if (overflowing !== this._isOverflowing) this._isOverflowing = overflowing;
270
+ if (this._loopActive && this.mode === `loop` && !this.disabled && overflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
271
+ if (!overflowing && this._loopAtEnd) {
272
+ this._loopAtEnd = false;
273
+ this.requestUpdate();
274
+ }
275
+ };
276
+ }
277
+ connectedCallback() {
278
+ super.connectedCallback();
279
+ this._resizeObserver = new ResizeObserver(() => this._measure());
280
+ this.addEventListener(`mouseenter`, this._onMouseEnter);
281
+ this.addEventListener(`mouseleave`, this._onMouseLeave);
282
+ }
283
+ disconnectedCallback() {
284
+ super.disconnectedCallback();
285
+ this._clearLoopTimer();
286
+ this._loopActive = false;
287
+ this._loopAtEnd = false;
288
+ this._resizeObserver?.disconnect();
289
+ this._resizeObserver = null;
290
+ this.removeEventListener(`mouseenter`, this._onMouseEnter);
291
+ this.removeEventListener(`mouseleave`, this._onMouseLeave);
292
+ }
293
+ firstUpdated() {
294
+ this._measure();
295
+ this._resizeObserver?.observe(this);
296
+ document.fonts?.ready.then(() => this._measure());
297
+ if (this.mode === `loop`) this.startLoop();
298
+ }
299
+ updated(changed) {
300
+ if (changed.has(`mode`)) {
301
+ if (this.mode === `loop`) this.startLoop();
302
+ else this.stopLoop();
303
+ }
304
+ if (changed.has(`disabled`) && this.disabled) this.stopLoop();
305
+ this._measure();
306
+ }
307
+ /**
308
+ * Whether a loop pan is currently active (between `startLoop()` and `stopLoop()`).
309
+ */
310
+ get looping() {
311
+ return this._loopActive;
312
+ }
313
+ /**
314
+ * Begin looping: pan to the end, dwell there for 2 seconds, reset to the
315
+ * start and pan again, repeating until `stopLoop()` is called. A no-op when
316
+ * `disabled`, when there is nothing to reveal, or under reduced-motion.
317
+ */
318
+ startLoop() {
319
+ if (this.disabled || this._prefersReducedMotion()) return;
320
+ this._loopActive = true;
321
+ this._loopAtEnd = false;
322
+ this.requestUpdate();
323
+ if (this._isOverflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
324
+ }
325
+ /**
326
+ * Stop a loop started with `startLoop()` and snap back to the rest position.
327
+ */
328
+ stopLoop() {
329
+ if (!this._loopActive && !this._loopAtEnd && this._loopTimer === 0) return;
330
+ this._clearLoopTimer();
331
+ this._loopActive = false;
332
+ this._loopAtEnd = false;
333
+ this.requestUpdate();
334
+ }
335
+ _prefersReducedMotion() {
336
+ return globalThis.matchMedia?.(`(prefers-reduced-motion: reduce)`).matches ?? false;
337
+ }
338
+ _panDuration() {
339
+ return Math.max(1.5, (this._overflow + END_PADDING) / SPEED);
340
+ }
341
+ _clearLoopTimer() {
342
+ if (this._loopTimer !== 0) {
343
+ clearTimeout(this._loopTimer);
344
+ this._loopTimer = 0;
345
+ }
346
+ }
347
+ _scheduleLoopPan(delay) {
348
+ this._clearLoopTimer();
349
+ this._loopTimer = window.setTimeout(() => {
350
+ this._loopTimer = 0;
351
+ if (!this._loopActive || this.disabled || !this._isOverflowing) return;
352
+ this._loopAtEnd = true;
353
+ this.requestUpdate();
354
+ const dwell = this._panDuration() * 1e3 + END_DWELL;
355
+ this._loopTimer = window.setTimeout(() => {
356
+ this._loopTimer = 0;
357
+ if (!this._loopActive) return;
358
+ this._loopAtEnd = false;
359
+ this.requestUpdate();
360
+ this._scheduleLoopPan(700);
361
+ }, dwell);
362
+ }, delay);
363
+ }
364
+ _textStyle() {
365
+ if (!this._isOverflowing) return ``;
366
+ const distance = this._overflow + END_PADDING;
367
+ const duration = Math.max(1.5, distance / SPEED);
368
+ const revealEnd = this.direction === `end`;
369
+ const restX = revealEnd ? 0 : -distance;
370
+ const hoverX = revealEnd ? -distance : 0;
371
+ if (this._loopActive && this.mode === `loop`) {
372
+ if (this._loopAtEnd) return [
373
+ `transform: translateX(${hoverX}px)`,
374
+ `transition: transform ${duration}s ${PAN_EASING}`,
375
+ `overflow: visible`,
376
+ `text-overflow: clip`
377
+ ].join(`; `);
378
+ return [
379
+ `transform: translateX(${restX}px)`,
380
+ `transition: transform ${RESET_DURATION}ms ${PAN_EASING}`,
381
+ revealEnd ? `` : `overflow: visible; text-overflow: clip`
382
+ ].filter(Boolean).join(`; `);
383
+ }
384
+ if (this._isHovering) return [
385
+ `transform: translateX(${hoverX}px)`,
386
+ `transition: transform ${duration}s ${PAN_EASING} ${START_DELAY}ms`,
387
+ `overflow: visible`,
388
+ `text-overflow: clip`
389
+ ].join(`; `);
390
+ return [
391
+ `transform: translateX(${restX}px)`,
392
+ `transition: transform 400ms ${PAN_EASING}`,
393
+ revealEnd ? `` : `overflow: visible; text-overflow: clip`
394
+ ].filter(Boolean).join(`; `);
395
+ }
396
+ render() {
397
+ const textClasses = {
398
+ "is-overflowing": this._isOverflowing,
399
+ "is-hovering": this._isHovering
400
+ };
401
+ return html`
402
+ <span class="text ${classMap(textClasses)}" part="text" style=${this._textStyle()}>
403
+ <slot @slotchange=${this._measure}></slot>
404
+ </span>
405
+ `;
406
+ }
407
+ static {
408
+ this.styles = [themeFallbacks, css`
409
+ /* Renders like plain inline text: no colours, border, padding or margin
410
+ of its own — it only adds the clipping needed to pan on hover. */
411
+ :host {
412
+ display: inline-block;
413
+ overflow: hidden;
414
+ white-space: nowrap;
415
+ max-width: 100%;
416
+ box-sizing: border-box;
417
+ font: inherit;
418
+ color: inherit;
419
+ }
420
+
421
+ :host([disabled]) {
422
+ opacity: var(--disabled-opacity, 0.5);
423
+ pointer-events: none;
424
+ }
425
+
426
+ .text {
427
+ display: block;
428
+ overflow: hidden;
429
+ text-overflow: ellipsis;
430
+ white-space: nowrap;
431
+ will-change: transform;
432
+ }
433
+
434
+ @media (prefers-reduced-motion: reduce) {
435
+ .text {
436
+ transition: none !important;
437
+ transform: none !important;
438
+ }
439
+ }
440
+ `];
441
+ }
442
+ };
443
+ __decorate([property({
444
+ type: String,
445
+ attribute: `direction`
446
+ })], PanTextElement.prototype, "direction", void 0);
447
+ __decorate([property({
448
+ type: Boolean,
449
+ attribute: `disabled`
450
+ })], PanTextElement.prototype, "disabled", void 0);
451
+ __decorate([property({
452
+ type: String,
453
+ attribute: `mode`,
454
+ reflect: true
455
+ })], PanTextElement.prototype, "mode", void 0);
456
+ __decorate([state()], PanTextElement.prototype, "_isOverflowing", void 0);
457
+ __decorate([state()], PanTextElement.prototype, "_isHovering", void 0);
458
+ __decorate([state()], PanTextElement.prototype, "_loopActive", void 0);
459
+ __decorate([state()], PanTextElement.prototype, "_loopAtEnd", void 0);
460
+ __decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
461
+ PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
462
+ //#endregion
235
463
  //#region src/grid-list/grid-list.ts
236
464
  const ITEM_SELECTOR = `:scope > *`;
237
465
  const MIN_ITEM_SIZE = 16;
@@ -327,6 +555,7 @@ let GridListElement = class GridListElement extends LitElement {
327
555
  this.captionBackdrop = `scrim`;
328
556
  this.captionHeight = 24;
329
557
  this.itemWrap = `stop`;
558
+ this.itemsDraggable = false;
330
559
  this.items = [];
331
560
  this._scrollTop = 0;
332
561
  this._scrollLeft = 0;
@@ -363,11 +592,56 @@ let GridListElement = class GridListElement extends LitElement {
363
592
  enabled: this.#source.isSelectable(i)
364
593
  })),
365
594
  onTickle: (k) => {
595
+ this.#stopActiveCaptionPan();
366
596
  if (k !== void 0) this.#emitItemEvent(`list-tickle`, k);
367
597
  },
368
598
  onStateChange: () => this._syncItemStates(),
369
599
  itemWrap: this.#itemWrap,
370
- hoverDisabled: () => this.disableHoverTickle
600
+ hoverDisabled: () => this.disableHoverTickle || this.#dragSelect.isDragging
601
+ });
602
+ this.#marqueePreview = /* @__PURE__ */ new Set();
603
+ this.#nativeDragging = false;
604
+ this.#dragOriginKey = void 0;
605
+ this.#postDragSuppressTimer = 0;
606
+ this.#activeCaptionPan = null;
607
+ this.#dragSelect = new ListDragSelectController(this, {
608
+ getSurface: () => this.#viewportRef.value,
609
+ isEnabled: () => this.selection.canDragSelect() && this.#source.count > 0 && !this.hasAttribute(`disable-drag-select`),
610
+ hitTest: (rect) => this.#marqueeHitTest(rect),
611
+ getBaseSelection: () => this.selection.selected,
612
+ onPreview: (preview, rect) => {
613
+ this.#marqueePreview = preview;
614
+ this.#marqueeRect = rect;
615
+ this.requestUpdate();
616
+ },
617
+ onCommit: (finalSet) => {
618
+ this.selection.applyDragSelection(finalSet);
619
+ this.#marqueePreview = /* @__PURE__ */ new Set();
620
+ this.#marqueeRect = void 0;
621
+ this.requestUpdate();
622
+ },
623
+ autoScroll: (dx, dy) => {
624
+ const vp = this.#viewportRef.value;
625
+ if (!vp) return;
626
+ if (dx !== 0) {
627
+ vp.scrollLeft += dx;
628
+ this._scrollLeft = vp.scrollLeft;
629
+ }
630
+ if (dy !== 0) {
631
+ vp.scrollTop += dy;
632
+ this._scrollTop = vp.scrollTop;
633
+ }
634
+ },
635
+ shouldStart: (event) => {
636
+ if (!this.itemsDraggable) return true;
637
+ const cell = event.composedPath()[0]?.closest?.(`.cell`);
638
+ if (!cell) return true;
639
+ const key = cell.dataset.key;
640
+ if (!key) return true;
641
+ const index = this.#source.indexOfKey(key);
642
+ if (index < 0) return true;
643
+ return !this.#source.isSelectable(index);
644
+ }
371
645
  });
372
646
  this.#selectedSnapshot = /* @__PURE__ */ new Set();
373
647
  this.#onScroll = (e) => {
@@ -380,6 +654,32 @@ let GridListElement = class GridListElement extends LitElement {
380
654
  this.#emitViewportChange();
381
655
  });
382
656
  };
657
+ this.#onDragStart = (e, key) => {
658
+ if (!this.itemsDraggable) return;
659
+ const index = this.#source.indexOfKey(key);
660
+ if (index < 0 || !this.#source.isSelectable(index)) return;
661
+ if (!this.selection.selectedKeys.has(key)) this.selection.replace(key);
662
+ const selectedKeys = this.#source.keys().filter((k) => !isSyntheticKey(k)).filter((k) => this.selection.selectedKeys.has(k));
663
+ const detail = {
664
+ key,
665
+ index: this.#source.indexOfKey(key),
666
+ item: this.#source.resolveKeyToData(key) ?? this.#source.resolveKeyToElement(key),
667
+ selectedKeys
668
+ };
669
+ if (!this._dispatch(`grid-list-native-drag-start`, detail, true)) {
670
+ e.preventDefault();
671
+ return;
672
+ }
673
+ this.#nativeDragging = true;
674
+ this.#dragOriginKey = key;
675
+ this.toggleAttribute(`data-native-dragging`, true);
676
+ const count = selectedKeys.length;
677
+ this.setAttribute(`aria-label`, count === 1 ? `Dragging 1 item` : `Dragging ${count} items`);
678
+ };
679
+ this.#onDragEnd = () => {
680
+ this.#clearNativeDragState();
681
+ this.#startPostDragSuppressTimer();
682
+ };
383
683
  this._onKeyDown = (e) => {
384
684
  if ((e.ctrlKey || e.metaKey) && e.key === `f`) return;
385
685
  if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
@@ -449,6 +749,7 @@ let GridListElement = class GridListElement extends LitElement {
449
749
  break;
450
750
  }
451
751
  case `Escape`:
752
+ if (this.#nativeDragging) this.#clearNativeDragState();
452
753
  this.selection.clear();
453
754
  break;
454
755
  default: return;
@@ -550,6 +851,7 @@ let GridListElement = class GridListElement extends LitElement {
550
851
  align-items: center;
551
852
  justify-content: center;
552
853
  overflow: hidden;
854
+ draggable: false;
553
855
  }
554
856
  .thumb ::slotted(*:empty)::after {
555
857
  content: "Empty";
@@ -599,6 +901,18 @@ let GridListElement = class GridListElement extends LitElement {
599
901
  }
600
902
  :host([caption-align="left"]) .caption { text-align: left; }
601
903
  :host([caption-align="right"]) .caption { text-align: right; }
904
+ /* Tickled captions swap their plain label for an ixfx-pan-text in loop
905
+ mode so a truncated label scrolls instead of staying an ellipsis. The
906
+ pan fills the caption band (flex in 'separate', block otherwise) and
907
+ inherits its alignment. */
908
+ .caption ixfx-pan-text {
909
+ display: block;
910
+ flex: 1;
911
+ min-width: 0;
912
+ max-width: 100%;
913
+ font: inherit;
914
+ color: inherit;
915
+ }
602
916
 
603
917
  /* ---- Box modes (hidden / under / overlaid): the cell is one box with a
604
918
  single selection outline ---- */
@@ -742,6 +1056,26 @@ let GridListElement = class GridListElement extends LitElement {
742
1056
  :host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
743
1057
  .cell.entering { animation: none; }
744
1058
  }
1059
+
1060
+ /* Drag-select (marquee) rectangle + covered-cell preview. The preview
1061
+ reuses the selection wash so covered cells read as "about to be
1062
+ selected". Style hooks: part="marquee", --grid-list-marquee-border,
1063
+ --grid-list-marquee-fill. */
1064
+ .marquee {
1065
+ position: absolute;
1066
+ pointer-events: none;
1067
+ z-index: 5;
1068
+ border: 1px solid var(--grid-list-marquee-border, var(--accent));
1069
+ background: var(--grid-list-marquee-fill, color-mix(in srgb, var(--accent) 15%, transparent));
1070
+ }
1071
+ .cell.marquee .thumb::after {
1072
+ opacity: var(--grid-list-thumb-selection-wash-opacity, 0.18);
1073
+ }
1074
+
1075
+ /* Native item drag origin cell. */
1076
+ .cell.dragging-origin {
1077
+ opacity: 0.6;
1078
+ }
745
1079
  `
746
1080
  ];
747
1081
  }
@@ -774,6 +1108,62 @@ let GridListElement = class GridListElement extends LitElement {
774
1108
  */
775
1109
  #sourceElement;
776
1110
  #source;
1111
+ /** Live drag-select preview: covered keys + the surface-relative rect. */
1112
+ #marqueePreview;
1113
+ #marqueeRect;
1114
+ /** Native item drag state: true while a drag is active. */
1115
+ #nativeDragging;
1116
+ /** Key of the cell that originated the native drag. */
1117
+ #dragOriginKey;
1118
+ /** Timer handle for post-drag click suppression. */
1119
+ #postDragSuppressTimer;
1120
+ /**
1121
+ * The single caption pan-text currently scrolling. At most one caption is
1122
+ * a pan-text in loop mode at a time — when tickle moves, the old one is
1123
+ * stopped before the new one starts (see `#activateTickledPan`).
1124
+ */
1125
+ #activeCaptionPan;
1126
+ #dragSelect;
1127
+ /**
1128
+ * Map a surface-relative (viewport-visible) rect to covered item keys using
1129
+ * the same `computeGridWindow` geometry the renderer uses — pure arithmetic,
1130
+ * so virtualized off-screen items are covered without DOM measurement.
1131
+ */
1132
+ #marqueeHitTest(rect) {
1133
+ const count = this.#source.count;
1134
+ if (count === 0) return [];
1135
+ const horizontal = this.orientation === `row`;
1136
+ 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);
1137
+ const out = [];
1138
+ if (horizontal) {
1139
+ const x0 = rect.x + this._scrollLeft;
1140
+ const x1 = x0 + rect.width;
1141
+ const lo = Math.max(0, Math.floor(x0 / win.colStride));
1142
+ const hi = Math.min(count - 1, Math.floor(x1 / win.colStride));
1143
+ for (let i = lo; i <= hi; i++) out.push(i);
1144
+ } else {
1145
+ const x0 = rect.x + this._scrollLeft;
1146
+ const x1 = x0 + rect.width;
1147
+ const y0 = rect.y + this._scrollTop;
1148
+ const y1 = y0 + rect.height;
1149
+ const colLo = Math.max(0, Math.floor(x0 / win.colStride));
1150
+ const colHi = Math.min(win.cols - 1, Math.floor(x1 / win.colStride));
1151
+ const rowLo = Math.max(0, Math.floor(y0 / win.rowStride));
1152
+ const rowHi = Math.floor(y1 / win.rowStride);
1153
+ for (let row = rowLo; row <= rowHi; row++) for (let col = colLo; col <= colHi; col++) {
1154
+ const index = row * win.cols + col;
1155
+ if (index >= 0 && index < count) out.push(index);
1156
+ }
1157
+ }
1158
+ const keys = [];
1159
+ for (const index of out) {
1160
+ const key = this.#source.keyAt(index);
1161
+ if (!key || isSyntheticKey(key)) continue;
1162
+ if (!this.#source.isSelectable(index)) continue;
1163
+ keys.push(key);
1164
+ }
1165
+ return keys;
1166
+ }
777
1167
  #searchMode;
778
1168
  #searchFilterKeys;
779
1169
  /** (Re)build {@link search} for the current mode; a no-op if it already matches. */
@@ -846,6 +1236,7 @@ let GridListElement = class GridListElement extends LitElement {
846
1236
  }
847
1237
  disconnectedCallback() {
848
1238
  super.disconnectedCallback();
1239
+ this.#stopActiveCaptionPan();
849
1240
  this.removeEventListener(`keydown`, this._onKeyDown);
850
1241
  this.#resizeObserver?.disconnect();
851
1242
  this.#resizeObserver = void 0;
@@ -858,6 +1249,7 @@ let GridListElement = class GridListElement extends LitElement {
858
1249
  clearTimeout(this.#reflowTimer);
859
1250
  this.#reflowTimer = 0;
860
1251
  }
1252
+ if (this.#nativeDragging) this.#clearNativeDragState();
861
1253
  }
862
1254
  #onChildrenChanged(records) {
863
1255
  const previousLength = this.#items.length;
@@ -1045,6 +1437,7 @@ let GridListElement = class GridListElement extends LitElement {
1045
1437
  if (Math.abs(vp.scrollLeft - this._scrollLeft) > .5) vp.scrollLeft = this._scrollLeft;
1046
1438
  }
1047
1439
  this.#applyZoomAnchor();
1440
+ this.#activateTickledPan();
1048
1441
  }
1049
1442
  /** Re-anchor the scroll so the item captured in `willUpdate` stays centred. */
1050
1443
  #applyZoomAnchor() {
@@ -1375,15 +1768,19 @@ let GridListElement = class GridListElement extends LitElement {
1375
1768
  element
1376
1769
  });
1377
1770
  }
1378
- _dispatch(name, detail) {
1379
- this.dispatchEvent(new CustomEvent(name, {
1771
+ _dispatch(name, detail, cancelable = false) {
1772
+ return this.dispatchEvent(new CustomEvent(name, {
1380
1773
  detail,
1381
1774
  bubbles: true,
1382
- composed: true
1775
+ composed: true,
1776
+ cancelable
1383
1777
  }));
1384
1778
  }
1385
1779
  /** Selected keys, snapshotted once per render so the cell renderers don't each rebuild the set. */
1386
1780
  #selectedSnapshot;
1781
+ #marqueeStyle(rect) {
1782
+ return `left:${rect.x}px;top:${rect.y}px;width:${Math.max(0, rect.width)}px;height:${Math.max(0, rect.height)}px`;
1783
+ }
1387
1784
  render() {
1388
1785
  const win = this.#currentWindow();
1389
1786
  this.#selectedSnapshot = this.selection.selectedKeys;
@@ -1400,6 +1797,7 @@ let GridListElement = class GridListElement extends LitElement {
1400
1797
  <div class="spacer" style=${spacerStyle}>
1401
1798
  ${this.#source instanceof DataGridListSource ? this.#renderDataCells(win) : this.#renderElementCells(win)}
1402
1799
  </div>
1800
+ ${this.#marqueeRect ? html`<div class="marquee" part="marquee" style=${this.#marqueeStyle(this.#marqueeRect)}></div>` : ``}
1403
1801
  </div>
1404
1802
  ${this.search?.renderField()}
1405
1803
  `;
@@ -1411,7 +1809,10 @@ let GridListElement = class GridListElement extends LitElement {
1411
1809
  #renderDataCell(index, win) {
1412
1810
  const key = this.#source.keyAt(index);
1413
1811
  const selected = this.#selectedSnapshot.has(key);
1812
+ const marquee = this.#marqueePreview.has(key);
1414
1813
  const tickled = this.tickled.isTickled(key);
1814
+ const selectable = this.#source.isSelectable(index);
1815
+ const draggable = this.itemsDraggable && selectable;
1415
1816
  const style = this.captionStyle;
1416
1817
  const separate = style === `separate`;
1417
1818
  const caption = style === `hidden` ? `` : this.#source.labelAt(index);
@@ -1422,24 +1823,69 @@ let GridListElement = class GridListElement extends LitElement {
1422
1823
  const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
1423
1824
  return html`
1424
1825
  <div
1425
- class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
1826
+ class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${marquee ? ` marquee` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
1426
1827
  part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
1427
1828
  role="option"
1428
1829
  aria-selected=${selected ? `true` : `false`}
1429
1830
  style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
1430
1831
  data-index=${index}
1431
1832
  data-key=${key}
1833
+ draggable=${draggable ? `true` : `false`}
1432
1834
  ?data-selected=${selected}
1433
1835
  ?data-tickled=${tickled}
1836
+ ?data-marquee=${marquee}
1434
1837
  @click=${(e) => this.#onCellClick(e, key)}
1435
1838
  @dblclick=${() => this.#onCellActivate(key)}
1436
1839
  @pointerenter=${() => this.tickled.handlePointerEnter(key)}
1840
+ @dragstart=${(e) => this.#onDragStart(e, key)}
1841
+ @dragend=${() => this.#onDragEnd()}
1437
1842
  >
1438
1843
  <div class="content" part="content">${this.#source.renderCellContent(index)}</div>
1439
- ${showCaption ? html`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>` : ``}
1844
+ ${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
1440
1845
  </div>
1441
1846
  `;
1442
1847
  }
1848
+ /**
1849
+ * A tickled cell swaps its plain label for an `ixfx-pan-text` in loop mode
1850
+ * so a truncated label scrolls instead of staying an ellipsis. Unticked
1851
+ * cells keep the cheap plain-text caption.
1852
+ */
1853
+ #renderCaption(caption, captionCss, tickled) {
1854
+ if (tickled) return html`<div class="caption" part="caption" style=${captionCss} title=${caption}><ixfx-pan-text mode="loop">${caption}</ixfx-pan-text></div>`;
1855
+ return html`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>`;
1856
+ }
1857
+ /** Stop the active caption pan, if any, and forget it. */
1858
+ #stopActiveCaptionPan() {
1859
+ const active = this.#activeCaptionPan;
1860
+ this.#activeCaptionPan = null;
1861
+ try {
1862
+ active?.stopLoop?.();
1863
+ } catch {}
1864
+ }
1865
+ /**
1866
+ * Enforce a single scrolling caption: the tickled cell's pan-text loops,
1867
+ * every other pan-text is stopped. The pan-text also auto-starts when
1868
+ * `mode="loop"` is set; this covers the case where the element was already
1869
+ * connected before overflow was measured.
1870
+ */
1871
+ #activateTickledPan() {
1872
+ const tickledCell = this.renderRoot.querySelector(`.cell[data-tickled]`);
1873
+ const pans = [...this.renderRoot.querySelectorAll(`ixfx-pan-text[mode="loop"]`)];
1874
+ const current = tickledCell?.querySelector(`ixfx-pan-text[mode="loop"]`) ?? null;
1875
+ for (const pan of pans) if (pan !== current) try {
1876
+ pan.stopLoop?.();
1877
+ } catch {}
1878
+ const active = this.#activeCaptionPan;
1879
+ if (active && active !== current) try {
1880
+ active.stopLoop?.();
1881
+ } catch {}
1882
+ if (!current) {
1883
+ this.#activeCaptionPan = null;
1884
+ return;
1885
+ }
1886
+ this.#activeCaptionPan = current;
1887
+ current.startLoop?.();
1888
+ }
1443
1889
  #renderElementCells(win) {
1444
1890
  const slice = this.#items.slice(win.startIndex, win.endIndex);
1445
1891
  return repeat(slice, (_el, i) => this.#source.keyAt(win.startIndex + i), (el, i) => this.#renderCell(el, win.startIndex + i, win));
@@ -1447,8 +1893,11 @@ let GridListElement = class GridListElement extends LitElement {
1447
1893
  #renderCell(el, index, win) {
1448
1894
  const key = this.#source.keyAt(index);
1449
1895
  const selected = this.#selectedSnapshot.has(key);
1896
+ const marquee = this.#marqueePreview.has(key);
1450
1897
  const tickled = this.tickled.isTickled(key);
1451
1898
  const entering = this.#entering.has(el);
1899
+ const selectable = this.#source.isSelectable(index);
1900
+ const draggable = this.itemsDraggable && selectable;
1452
1901
  const style = this.captionStyle;
1453
1902
  const separate = style === `separate`;
1454
1903
  const caption = style === `hidden` ? `` : this.#captionFor(el);
@@ -1460,23 +1909,27 @@ let GridListElement = class GridListElement extends LitElement {
1460
1909
  const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
1461
1910
  return html`
1462
1911
  <div
1463
- class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}"
1912
+ class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}${marquee ? ` marquee` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
1464
1913
  part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
1465
1914
  role="option"
1466
1915
  aria-selected=${selected ? `true` : `false`}
1467
1916
  style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
1468
1917
  data-index=${index}
1469
1918
  data-key=${key}
1919
+ draggable=${draggable ? `true` : `false`}
1470
1920
  ?data-selected=${selected}
1471
1921
  ?data-tickled=${tickled}
1922
+ ?data-marquee=${marquee}
1472
1923
  @click=${(e) => this.#onCellClick(e, key)}
1473
1924
  @dblclick=${() => this.#onCellActivate(key)}
1474
1925
  @pointerenter=${() => this.tickled.handlePointerEnter(key)}
1926
+ @dragstart=${(e) => this.#onDragStart(e, key)}
1927
+ @dragend=${() => this.#onDragEnd()}
1475
1928
  >
1476
1929
  <div class="thumb" part="thumb" style=${thumbStyle}>
1477
1930
  ${this.#source.renderCellContent(index)}
1478
1931
  </div>
1479
- ${showCaption ? html`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>` : ``}
1932
+ ${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
1480
1933
  </div>
1481
1934
  `;
1482
1935
  }
@@ -1488,9 +1941,24 @@ let GridListElement = class GridListElement extends LitElement {
1488
1941
  }
1489
1942
  }
1490
1943
  #onCellClick(e, key) {
1944
+ if (this.#dragSelect.isDragging || this.#nativeDragging || this.#postDragSuppressTimer !== 0) return;
1491
1945
  this.selection.handleClick(key, e);
1492
1946
  this.#emitItemEvent(`list-item-click`, key);
1493
1947
  }
1948
+ #onDragStart;
1949
+ #onDragEnd;
1950
+ #startPostDragSuppressTimer() {
1951
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
1952
+ this.#postDragSuppressTimer = window.setTimeout(() => {
1953
+ this.#postDragSuppressTimer = 0;
1954
+ }, 0);
1955
+ }
1956
+ #clearNativeDragState() {
1957
+ this.#nativeDragging = false;
1958
+ this.#dragOriginKey = void 0;
1959
+ this.toggleAttribute(`data-native-dragging`, false);
1960
+ this.removeAttribute(`aria-label`);
1961
+ }
1494
1962
  #onCellActivate(key) {
1495
1963
  if (this.interactionMode !== `manual`) this.selection.replace(key);
1496
1964
  this.#emitItemEvent(`list-activate`, key);
@@ -1548,6 +2016,11 @@ __decorate([property({
1548
2016
  })], GridListElement.prototype, "itemWrap", void 0);
1549
2017
  __decorate([property({ attribute: false })], GridListElement.prototype, "filterPredicate", void 0);
1550
2018
  __decorate([property({ attribute: false })], GridListElement.prototype, "itemFilterPredicate", void 0);
2019
+ __decorate([property({
2020
+ type: Boolean,
2021
+ attribute: `items-draggable`,
2022
+ reflect: true
2023
+ })], GridListElement.prototype, "itemsDraggable", void 0);
1551
2024
  __decorate([property({
1552
2025
  type: String,
1553
2026
  attribute: `search-modes`,
@@ -1561,6 +2034,6 @@ __decorate([state()], GridListElement.prototype, "_viewportHeight", void 0);
1561
2034
  __decorate([state()], GridListElement.prototype, "_viewportWidth", void 0);
1562
2035
  GridListElement = __decorate([safeCustomElement(`ixfx-grid-list`)], GridListElement);
1563
2036
  //#endregion
1564
- export { GridListElement as t };
2037
+ export { PanTextElement as n, GridListElement as t };
1565
2038
 
1566
- //# sourceMappingURL=grid-list-bmBWq6P-.js.map
2039
+ //# sourceMappingURL=grid-list-BXfY3XXX.js.map