@ixfx/components 0.8.6 → 0.8.8

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 (76) hide show
  1. package/bundle/index.d.ts +570 -389
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +1033 -200
  4. package/bundle/index.js.map +1 -1
  5. package/dist/crumbs.d.ts +1 -1
  6. package/dist/crumbs.js +2 -2
  7. package/dist/{data-grid-D-z7bx5-.d.ts → data-grid-BKa9i86q.d.ts} +15 -2
  8. package/dist/data-grid-BKa9i86q.d.ts.map +1 -0
  9. package/dist/data-grid.d.ts +3 -2
  10. package/dist/data-grid.js +95 -5
  11. package/dist/data-grid.js.map +1 -1
  12. package/dist/{detail-list-_ZJHYEBr.d.ts → detail-list-CHTomlbR.d.ts} +22 -5
  13. package/dist/detail-list-CHTomlbR.d.ts.map +1 -0
  14. package/dist/detail-list.d.ts +4 -3
  15. package/dist/detail-list.js +103 -3
  16. package/dist/detail-list.js.map +1 -1
  17. package/dist/{grid-list-j5C7qtlG.js → grid-list-C8oMxLl-.js} +77 -164
  18. package/dist/grid-list-C8oMxLl-.js.map +1 -0
  19. package/dist/{grid-list-x550wJ0X.d.ts → grid-list-CsZcXxMC.d.ts} +259 -109
  20. package/dist/grid-list-CsZcXxMC.d.ts.map +1 -0
  21. package/dist/grid-list.d.ts +3 -2
  22. package/dist/grid-list.js +1 -1
  23. package/dist/incr-search.d.ts +1 -1
  24. package/dist/{index-h6gxtfHH.d.ts → index-Dgrx-DIA.d.ts} +2 -2
  25. package/dist/{index-h6gxtfHH.d.ts.map → index-Dgrx-DIA.d.ts.map} +1 -1
  26. package/dist/index.d.ts +190 -252
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +8 -7
  29. package/dist/index.js.map +1 -1
  30. package/dist/{interaction-B9AAlsFO.js → interaction-BskmlA9t.js} +1 -1
  31. package/dist/list-drag-types-kpaXz2pZ.d.ts +42 -0
  32. package/dist/list-drag-types-kpaXz2pZ.d.ts.map +1 -0
  33. package/dist/{list-item-animation-DuiwgN3l.js → list-item-animation-BUoEKi5s.js} +29 -10
  34. package/dist/list-item-animation-BUoEKi5s.js.map +1 -0
  35. package/dist/list-native-drag-controller-DrY3qeJM.js +555 -0
  36. package/dist/list-native-drag-controller-DrY3qeJM.js.map +1 -0
  37. package/dist/{menu-HMoxCPY2.js → menu-CMXhIaNZ.js} +3 -3
  38. package/dist/{menu-HMoxCPY2.js.map → menu-CMXhIaNZ.js.map} +1 -1
  39. package/dist/{menu-item-BQDYxk5A.js → menu-item-BYGQzyCe.js} +2 -2
  40. package/dist/{menu-item-BQDYxk5A.js.map → menu-item-BYGQzyCe.js.map} +1 -1
  41. package/dist/menu.js +2 -2
  42. package/dist/miller.d.ts +2 -2
  43. package/dist/miller.js +1 -1
  44. package/dist/narrowed-text.js +1 -1
  45. package/dist/select-horiz.js +1 -1
  46. package/dist/{tree-DTQon838.js → tree-DSMn566I.js} +90 -27
  47. package/dist/tree-DSMn566I.js.map +1 -0
  48. package/dist/{tree-component-F6qZ0Gdv.d.ts → tree-component-nPP5Y-WS.d.ts} +12 -2
  49. package/dist/tree-component-nPP5Y-WS.d.ts.map +1 -0
  50. package/dist/tree.d.ts +3 -2
  51. package/dist/tree.d.ts.map +1 -1
  52. package/dist/tree.js +1 -1
  53. package/dist/{vertical-list-CFuFDygi.d.ts → vertical-list-A6oWfivZ.d.ts} +21 -4
  54. package/dist/vertical-list-A6oWfivZ.d.ts.map +1 -0
  55. package/dist/vertical-list.d.ts +3 -2
  56. package/dist/vertical-list.js +103 -3
  57. package/dist/vertical-list.js.map +1 -1
  58. package/docs-user/README.md +1 -0
  59. package/docs-user/data-grid.md +30 -0
  60. package/docs-user/data-table.md +104 -0
  61. package/docs-user/detail-list.md +29 -0
  62. package/docs-user/grid-list.md +34 -4
  63. package/docs-user/index.json +7 -2
  64. package/docs-user/llms.txt +1 -0
  65. package/docs-user/tree.md +5 -4
  66. package/docs-user/vertical-list.md +30 -1
  67. package/llms.txt +1 -0
  68. package/package.json +1 -1
  69. package/dist/data-grid-D-z7bx5-.d.ts.map +0 -1
  70. package/dist/detail-list-_ZJHYEBr.d.ts.map +0 -1
  71. package/dist/grid-list-j5C7qtlG.js.map +0 -1
  72. package/dist/grid-list-x550wJ0X.d.ts.map +0 -1
  73. package/dist/list-item-animation-DuiwgN3l.js.map +0 -1
  74. package/dist/tree-DTQon838.js.map +0 -1
  75. package/dist/tree-component-F6qZ0Gdv.d.ts.map +0 -1
  76. package/dist/vertical-list-CFuFDygi.d.ts.map +0 -1
package/bundle/index.js CHANGED
@@ -5686,6 +5686,363 @@ __decorate([state()], RadioGroup.prototype, "_icons", void 0);
5686
5686
  __decorate([query(`slot`)], RadioGroup.prototype, "_slot", void 0);
5687
5687
  RadioGroup = __decorate([safeCustomElement(`ixfx-radio-group`)], RadioGroup);
5688
5688
  //#endregion
5689
+ //#region src/interaction/list-drag-preview-default.ts
5690
+ const CARD_DEFAULT = 72;
5691
+ const CARD_MIN = 48;
5692
+ const CARD_MAX = 96;
5693
+ const LIST_WIDTH = 220;
5694
+ const LIST_MAX_ROWS = 5;
5695
+ const MAX_CANVAS_DIM = 1024;
5696
+ function cssVar(style, name, fallback) {
5697
+ try {
5698
+ return style.getPropertyValue(name)?.trim() || fallback;
5699
+ } catch {
5700
+ return fallback;
5701
+ }
5702
+ }
5703
+ function fontOf(style) {
5704
+ try {
5705
+ const font = style.font?.trim();
5706
+ if (font) return font;
5707
+ } catch {}
5708
+ return `12px system-ui, sans-serif`;
5709
+ }
5710
+ /** Caption for a drag entry — element labels win, then string items, then common object fields, then the key. */
5711
+ function captionForEntry(entry) {
5712
+ const el = entry.element ?? (entry.item instanceof Element ? entry.item : void 0);
5713
+ if (el) {
5714
+ const labelled = el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.getAttribute(`data-search-label`) ?? el.querySelector(`.cell`)?.textContent?.trim();
5715
+ if (labelled) return labelled.slice(0, 80);
5716
+ const text = el.textContent?.trim();
5717
+ if (text) return text.slice(0, 80);
5718
+ }
5719
+ const item = entry.item;
5720
+ if (typeof item === `string` && item.length > 0) return item.slice(0, 80);
5721
+ if (item !== null && typeof item === `object` && !(item instanceof Element)) {
5722
+ const record = item;
5723
+ for (const field of [
5724
+ `label`,
5725
+ `name`,
5726
+ `title`,
5727
+ `text`
5728
+ ]) {
5729
+ const value = record[field];
5730
+ if (typeof value === `string` && value.length > 0) return value.slice(0, 80);
5731
+ }
5732
+ }
5733
+ return entry.key;
5734
+ }
5735
+ function glyphFor(caption) {
5736
+ const first = caption.trim().at(0);
5737
+ return first ? first.toLocaleUpperCase() : `?`;
5738
+ }
5739
+ /** A decoded `<img>` ready for `drawImage`, if the entry's element has one. */
5740
+ function decodedImage(entry) {
5741
+ const scope = entry.element ?? (entry.item instanceof Element ? entry.item : void 0);
5742
+ if (!scope || typeof scope.querySelector !== `function`) return void 0;
5743
+ if (scope instanceof HTMLImageElement) {
5744
+ if (scope.complete && scope.naturalWidth > 0) return scope;
5745
+ return;
5746
+ }
5747
+ try {
5748
+ const img = scope.querySelector(`img`);
5749
+ if (img instanceof HTMLImageElement && img.complete && img.naturalWidth > 0) return img;
5750
+ } catch {}
5751
+ }
5752
+ function cardSize(metrics) {
5753
+ if (metrics.itemSize !== void 0 && Number.isFinite(metrics.itemSize)) return Math.max(CARD_MIN, Math.min(CARD_MAX, Math.round(metrics.itemSize)));
5754
+ return CARD_DEFAULT;
5755
+ }
5756
+ function rowHeight(metrics) {
5757
+ const raw = metrics.rowHeight;
5758
+ if (raw !== void 0 && Number.isFinite(raw)) return Math.max(24, Math.min(36, Math.round(raw)));
5759
+ return 28;
5760
+ }
5761
+ function thumbCell(doc, caption, img, size) {
5762
+ const well = doc.createElement(`div`);
5763
+ well.setAttribute(`data-thumb`, ``);
5764
+ well.style.cssText = `width:${size}px;height:${size}px;border-radius:4px;overflow:hidden;background:var(--surface-5, rgba(128,128,128,0.25));flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:${Math.round(size * .4)}px;font-weight:600;`;
5765
+ if (img) try {
5766
+ const clone = img.cloneNode(true);
5767
+ clone.style.cssText = `width:100%;height:100%;object-fit:cover;`;
5768
+ clone.removeAttribute(`id`);
5769
+ well.appendChild(clone);
5770
+ } catch {
5771
+ well.textContent = glyphFor(caption);
5772
+ }
5773
+ else well.textContent = glyphFor(caption);
5774
+ return well;
5775
+ }
5776
+ function buildCardsElement(ctx, captions, images) {
5777
+ const doc = document;
5778
+ const size = cardSize(ctx.metrics);
5779
+ const gap = 6;
5780
+ const pad = 8;
5781
+ const root = doc.createElement(`div`);
5782
+ root.setAttribute(`data-preview`, `cards`);
5783
+ root.setAttribute(`part`, `drag-preview`);
5784
+ root.style.cssText = `position:fixed;top:-9999px;left:-9999px;display:flex;flex-direction:row;gap:${gap}px;padding:${pad}px;background:var(--surface-4, #333);color:var(--surface-4-text, #fff);border:1px solid var(--border-subtle, #555);border-radius:var(--radius-m, 8px);box-shadow:0 4px 16px rgba(0,0,0,0.3);font-family:system-ui,sans-serif;font-size:11px;max-width:320px;z-index:999999;pointer-events:none;`;
5785
+ const n = ctx.selected.length;
5786
+ for (let i = 0; i < n; i++) {
5787
+ const card = doc.createElement(`div`);
5788
+ card.setAttribute(`data-card`, ``);
5789
+ card.style.cssText = `display:flex;flex-direction:column;gap:4px;align-items:center;width:${size}px;`;
5790
+ card.appendChild(thumbCell(doc, captions[i], images[i], Math.round(size * .72)));
5791
+ const label = doc.createElement(`span`);
5792
+ label.setAttribute(`data-caption`, ``);
5793
+ label.style.cssText = `max-width:${size}px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;opacity:0.9;`;
5794
+ label.textContent = captions[i];
5795
+ card.appendChild(label);
5796
+ root.appendChild(card);
5797
+ }
5798
+ return root;
5799
+ }
5800
+ function buildListElement(ctx, captions, images) {
5801
+ const doc = document;
5802
+ const rh = rowHeight(ctx.metrics);
5803
+ const thumb = Math.round(rh * .78);
5804
+ const root = doc.createElement(`div`);
5805
+ root.setAttribute(`data-preview`, `list`);
5806
+ root.setAttribute(`part`, `drag-preview`);
5807
+ root.style.cssText = `position:fixed;top:-9999px;left:-9999px;display:flex;flex-direction:column;gap:4px;padding:8px;background:var(--surface-4, #333);color:var(--surface-4-text, #fff);border:1px solid var(--border-subtle, #555);border-radius:var(--radius-m, 8px);box-shadow:0 4px 16px rgba(0,0,0,0.3);font-family:system-ui,sans-serif;font-size:12px;width:${LIST_WIDTH}px;max-width:${LIST_WIDTH}px;z-index:999999;pointer-events:none;`;
5808
+ const header = doc.createElement(`div`);
5809
+ header.setAttribute(`data-header`, ``);
5810
+ header.style.cssText = `font-weight:600;padding-bottom:4px;border-bottom:1px solid var(--border-subtle, #555);margin-bottom:4px;`;
5811
+ header.textContent = `${ctx.selected.length} items`;
5812
+ root.appendChild(header);
5813
+ for (let i = 0; i < Math.min(LIST_MAX_ROWS, captions.length); i++) {
5814
+ const row = doc.createElement(`div`);
5815
+ row.setAttribute(`data-row`, ``);
5816
+ row.style.cssText = `display:flex;align-items:center;gap:6px;height:${rh}px;overflow:hidden;`;
5817
+ row.appendChild(thumbCell(doc, captions[i], images[i], thumb));
5818
+ const label = doc.createElement(`span`);
5819
+ label.setAttribute(`data-caption`, ``);
5820
+ label.style.cssText = `flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`;
5821
+ label.textContent = captions[i];
5822
+ row.appendChild(label);
5823
+ root.appendChild(row);
5824
+ }
5825
+ if (captions.length > LIST_MAX_ROWS) {
5826
+ const more = doc.createElement(`div`);
5827
+ more.setAttribute(`data-more`, ``);
5828
+ more.style.cssText = `opacity:0.6;text-align:center;`;
5829
+ more.textContent = `+${captions.length - LIST_MAX_ROWS} more`;
5830
+ root.appendChild(more);
5831
+ }
5832
+ return root;
5833
+ }
5834
+ function paletteOf(style) {
5835
+ const bg = cssVar(style, `--surface-4`, `#333333`);
5836
+ const fg = cssVar(style, `--surface-4-text`, `#ffffff`);
5837
+ const subtle = cssVar(style, `--border-subtle`, `#555555`);
5838
+ const accent = cssVar(style, `--accent`, `#4da3ff`);
5839
+ const radiusRaw = cssVar(style, `--radius-m`, `8`);
5840
+ return {
5841
+ bg,
5842
+ fg,
5843
+ subtle,
5844
+ accent,
5845
+ radius: Number.parseFloat(radiusRaw) || 8,
5846
+ font: fontOf(style)
5847
+ };
5848
+ }
5849
+ function fitCanvas(canvas, cssW, cssH) {
5850
+ let scale = Math.max(1, Math.min(3, typeof devicePixelRatio === `number` && devicePixelRatio > 0 ? devicePixelRatio : 1));
5851
+ const maxScaleW = MAX_CANVAS_DIM / Math.max(1, cssW);
5852
+ const maxScaleH = MAX_CANVAS_DIM / Math.max(1, cssH);
5853
+ scale = Math.min(scale, maxScaleW, maxScaleH);
5854
+ canvas.width = Math.max(1, Math.round(cssW * scale));
5855
+ canvas.height = Math.max(1, Math.round(cssH * scale));
5856
+ canvas.style.width = `${cssW}px`;
5857
+ canvas.style.height = `${cssH}px`;
5858
+ return {
5859
+ scale,
5860
+ dpr: scale
5861
+ };
5862
+ }
5863
+ function roundRectPath(context, x, y, w, h, r) {
5864
+ const radius = Math.max(0, Math.min(r, w / 2, h / 2));
5865
+ if (typeof context.roundRect === `function`) {
5866
+ context.beginPath();
5867
+ context.roundRect(x, y, w, h, radius);
5868
+ return;
5869
+ }
5870
+ context.beginPath();
5871
+ context.moveTo(x + radius, y);
5872
+ context.arcTo(x + w, y, x + w, y + h, radius);
5873
+ context.arcTo(x + w, y + h, x, y + h, radius);
5874
+ context.arcTo(x, y + h, x, y, radius);
5875
+ context.arcTo(x, y, x + w, y, radius);
5876
+ context.closePath();
5877
+ }
5878
+ function paintCards(context, scale, palette, captions, images, size, cssW, cssH) {
5879
+ context.save();
5880
+ context.scale(scale, scale);
5881
+ roundRectPath(context, 0, 0, cssW, cssH, palette.radius);
5882
+ context.fillStyle = palette.bg;
5883
+ context.fill();
5884
+ context.strokeStyle = palette.subtle;
5885
+ context.lineWidth = 1;
5886
+ context.stroke();
5887
+ context.clip();
5888
+ const gap = 6;
5889
+ const pad = 8;
5890
+ const wellSize = Math.round(size * .72);
5891
+ context.font = palette.font;
5892
+ context.textBaseline = `middle`;
5893
+ for (let i = 0; i < captions.length; i++) {
5894
+ const x = pad + i * (size + gap);
5895
+ const wellX = x + (size - wellSize) / 2;
5896
+ const wellY = pad;
5897
+ roundRectPath(context, wellX, wellY, wellSize, wellSize, 4);
5898
+ context.fillStyle = palette.subtle;
5899
+ context.globalAlpha = .35;
5900
+ context.fill();
5901
+ context.globalAlpha = 1;
5902
+ const img = images[i];
5903
+ if (img) try {
5904
+ context.save();
5905
+ context.clip();
5906
+ context.drawImage(img, wellX, wellY, wellSize, wellSize);
5907
+ context.restore();
5908
+ } catch {}
5909
+ else {
5910
+ context.fillStyle = palette.fg;
5911
+ context.globalAlpha = .9;
5912
+ context.font = `600 ${Math.round(wellSize * .4)}px system-ui, sans-serif`;
5913
+ context.textAlign = `center`;
5914
+ context.fillText(glyphFor(captions[i]), wellX + wellSize / 2, wellY + wellSize / 2 + 1);
5915
+ context.globalAlpha = 1;
5916
+ context.textAlign = `left`;
5917
+ context.font = palette.font;
5918
+ }
5919
+ context.fillStyle = palette.fg;
5920
+ context.globalAlpha = .9;
5921
+ context.font = palette.font;
5922
+ const label = captions[i].length > 12 ? `${captions[i].slice(0, 11)}…` : captions[i];
5923
+ const textWidth = context.measureText(label).width;
5924
+ context.fillText(label, x + (size - Math.min(size, textWidth)) / 2, wellY + wellSize + 10, size);
5925
+ context.globalAlpha = 1;
5926
+ }
5927
+ context.restore();
5928
+ }
5929
+ function paintList(context, scale, palette, captions, images, rh, cssW, cssH) {
5930
+ context.save();
5931
+ context.scale(scale, scale);
5932
+ roundRectPath(context, 0, 0, cssW, cssH, palette.radius);
5933
+ context.fillStyle = palette.bg;
5934
+ context.fill();
5935
+ context.strokeStyle = palette.subtle;
5936
+ context.lineWidth = 1;
5937
+ context.stroke();
5938
+ context.clip();
5939
+ const pad = 8;
5940
+ context.fillStyle = palette.fg;
5941
+ context.font = `600 ${palette.font}`;
5942
+ context.textBaseline = `middle`;
5943
+ context.fillText(`${captions.length} items`, pad, 16, cssW - 16);
5944
+ context.strokeStyle = palette.subtle;
5945
+ context.beginPath();
5946
+ context.moveTo(pad, 28);
5947
+ context.lineTo(cssW - pad, 28);
5948
+ context.stroke();
5949
+ const thumb = Math.round(rh * .78);
5950
+ const rows = Math.min(LIST_MAX_ROWS, captions.length);
5951
+ context.font = palette.font;
5952
+ for (let i = 0; i < rows; i++) {
5953
+ const y = 32 + i * rh;
5954
+ roundRectPath(context, pad, y + (rh - thumb) / 2, thumb, thumb, 4);
5955
+ context.fillStyle = palette.subtle;
5956
+ context.globalAlpha = .35;
5957
+ context.fill();
5958
+ context.globalAlpha = 1;
5959
+ const img = images[i];
5960
+ if (img) try {
5961
+ context.save();
5962
+ context.clip();
5963
+ context.drawImage(img, pad, y + (rh - thumb) / 2, thumb, thumb);
5964
+ context.restore();
5965
+ } catch {}
5966
+ else {
5967
+ context.fillStyle = palette.fg;
5968
+ context.font = `600 ${Math.round(thumb * .45)}px system-ui, sans-serif`;
5969
+ context.textAlign = `center`;
5970
+ context.fillText(glyphFor(captions[i]), pad + thumb / 2, y + rh / 2 + 1);
5971
+ context.textAlign = `left`;
5972
+ context.font = palette.font;
5973
+ }
5974
+ context.fillStyle = palette.fg;
5975
+ context.globalAlpha = .92;
5976
+ const maxText = cssW - 16 - thumb - 6;
5977
+ context.fillText(captions[i], pad + thumb + 6, y + rh / 2 + 1, Math.max(8, maxText));
5978
+ context.globalAlpha = 1;
5979
+ }
5980
+ if (captions.length > LIST_MAX_ROWS) {
5981
+ context.fillStyle = palette.fg;
5982
+ context.globalAlpha = .6;
5983
+ context.textAlign = `center`;
5984
+ context.fillText(`+${captions.length - LIST_MAX_ROWS} more`, cssW / 2, cssH - pad - 6, cssW - 16);
5985
+ context.textAlign = `left`;
5986
+ context.globalAlpha = 1;
5987
+ }
5988
+ context.restore();
5989
+ }
5990
+ /**
5991
+ * Uniform built-in HTML+canvas drag preview for all four list components.
5992
+ *
5993
+ * - ≤5 selected → a row of small cards (thumbnail well + caption).
5994
+ * - >5 selected → a compact list (`N items` header, up to 5 rows, `+N more`).
5995
+ *
5996
+ * Always returns an offscreen `element` for `DataTransfer.setDragImage()` and,
5997
+ * when a 2D context is available, a DPR-aware painted `canvas` for native
5998
+ * hosts. Colours track the active theme via `ctx.hostStyle` CSS variables.
5999
+ */
6000
+ function createDefaultListDragPreview(ctx) {
6001
+ const entries = ctx.selected;
6002
+ const captions = entries.map((entry) => captionForEntry(entry));
6003
+ const images = entries.map((entry) => decodedImage(entry));
6004
+ const isCards = entries.length <= 5;
6005
+ let element;
6006
+ let cssW;
6007
+ let cssH;
6008
+ if (isCards) {
6009
+ const size = cardSize(ctx.metrics);
6010
+ cssW = entries.length * size + Math.max(0, entries.length - 1) * 6 + 16;
6011
+ cssH = Math.round(size * .72) + 22 + 16;
6012
+ element = buildCardsElement(ctx, captions, images);
6013
+ } else {
6014
+ const rh = rowHeight(ctx.metrics);
6015
+ const rows = Math.min(LIST_MAX_ROWS, entries.length);
6016
+ const more = entries.length > LIST_MAX_ROWS ? 18 : 0;
6017
+ cssW = 236;
6018
+ cssH = 40 + rows * rh + more;
6019
+ element = buildListElement(ctx, captions, images);
6020
+ }
6021
+ let canvas;
6022
+ try {
6023
+ const candidate = document.createElement(`canvas`);
6024
+ const paint = candidate.getContext(`2d`);
6025
+ if (paint) {
6026
+ const { scale } = fitCanvas(candidate, cssW, cssH);
6027
+ const palette = paletteOf(ctx.hostStyle);
6028
+ if (isCards) paintCards(paint, scale, palette, captions, images, cardSize(ctx.metrics), cssW, cssH);
6029
+ else paintList(paint, scale, palette, captions, images, rowHeight(ctx.metrics), cssW, cssH);
6030
+ canvas = candidate;
6031
+ }
6032
+ } catch {
6033
+ canvas = void 0;
6034
+ }
6035
+ return {
6036
+ element,
6037
+ hotspot: {
6038
+ x: 16,
6039
+ y: 16
6040
+ },
6041
+ ...canvas ? { canvas } : {},
6042
+ dispose: () => {}
6043
+ };
6044
+ }
6045
+ //#endregion
5689
6046
  //#region src/interaction/list-drag-select-controller.ts
5690
6047
  const DEFAULT_POINTER_TYPES = [`mouse`, `pen`];
5691
6048
  const DEFAULT_THRESHOLD = 4;
@@ -6075,24 +6432,238 @@ function animateListItemIn(el, options = {}) {
6075
6432
  easing
6076
6433
  });
6077
6434
  }
6435
+ function outResult(finished, animation) {
6436
+ return Object.assign(finished, { animation });
6437
+ }
6078
6438
  /**
6079
- * Collapse + fade `el` out. Resolves once the animation finishes (or straight
6080
- * away under reduced motion); the caller detaches the element after. The final
6081
- * collapsed state is held (`fill: forwards`) so there's no flash back to full
6082
- * size in the gap before removal.
6439
+ * Start the collapse + fade-out animation for `el` and return the handle.
6440
+ * Returns `undefined` when no animation was started (reduced motion or zero
6441
+ * size). The caller owns the handle and must `cancel()` it if the removal is
6442
+ * reversed a retained `fill: forwards` frame otherwise keeps overriding the
6443
+ * element's ordinary CSS indefinitely.
6083
6444
  */
6084
- function animateListItemOut(el, options = {}) {
6085
- if (prefersReducedMotion()) return Promise.resolve();
6445
+ function startListItemOutAnimation(el, options = {}) {
6446
+ if (prefersReducedMotion()) return void 0;
6086
6447
  const { axis = `block`, easing = `ease` } = options;
6087
6448
  const duration = resolveDuration(el, options.duration);
6088
6449
  const size = measure(el, axis);
6089
- if (size <= 0) return Promise.resolve();
6450
+ if (size <= 0) return void 0;
6090
6451
  return el.animate([fullFrame(size, axis), collapsedFrame(axis)], {
6091
6452
  duration,
6092
6453
  easing,
6093
6454
  fill: `forwards`
6094
- }).finished.then(() => void 0, () => void 0);
6455
+ });
6095
6456
  }
6457
+ /**
6458
+ * Collapse + fade `el` out. Resolves once the animation finishes (or straight
6459
+ * away under reduced motion); the caller detaches the element after. The final
6460
+ * collapsed state is held (`fill: forwards`) so there's no flash back to full
6461
+ * size in the gap before removal.
6462
+ *
6463
+ * The returned promise carries an `.animation` handle. If the removal is
6464
+ * reversed (the same key becomes visible again), cancel that handle so its
6465
+ * filled final frame stops overriding the reused element's style.
6466
+ */
6467
+ function animateListItemOut(el, options = {}) {
6468
+ const animation = startListItemOutAnimation(el, options);
6469
+ if (!animation) return outResult(Promise.resolve(), void 0);
6470
+ return outResult(animation.finished.then(() => void 0, () => void 0), animation);
6471
+ }
6472
+ //#endregion
6473
+ //#region src/interaction/list-native-drag-controller.ts
6474
+ /**
6475
+ * Shared native item/row drag-out for list components.
6476
+ *
6477
+ * Owns capture-phase `dragstart` / `dragend` listeners on the host and its
6478
+ * render root, a `MutationObserver` that keeps light-DOM / slotted item `draggable`
6479
+ * attributes in sync, and a single post-dispatch disposal on
6480
+ * `requestAnimationFrame`. All DOM / selection knowledge lives in the host
6481
+ * config callbacks.
6482
+ */
6483
+ var ListNativeDragController = class {
6484
+ #host;
6485
+ #config;
6486
+ #dragging = false;
6487
+ #boundRoot;
6488
+ #boundHost = false;
6489
+ #seenDragStarts = /* @__PURE__ */ new WeakSet();
6490
+ #observer;
6491
+ constructor(host, config) {
6492
+ this.#host = host;
6493
+ this.#config = config;
6494
+ host.addController(this);
6495
+ }
6496
+ get isDragging() {
6497
+ return this.#dragging;
6498
+ }
6499
+ /** `"true"` when enabled, else `"false"` — for shadow-row template bindings. */
6500
+ get draggableAttr() {
6501
+ try {
6502
+ return this.#config.isEnabled() ? `true` : `false`;
6503
+ } catch {
6504
+ return `false`;
6505
+ }
6506
+ }
6507
+ /** Re-run the light-DOM `draggable` sync (e.g. after `itemsDraggable` flips). */
6508
+ refreshDraggable() {
6509
+ try {
6510
+ this.#config.syncLightDomDraggable?.();
6511
+ } catch {}
6512
+ }
6513
+ hostConnected() {
6514
+ this.#bind();
6515
+ this.#observer ??= new MutationObserver(() => this.refreshDraggable());
6516
+ try {
6517
+ this.#observer.observe(this.#host, {
6518
+ childList: true,
6519
+ subtree: true
6520
+ });
6521
+ } catch {}
6522
+ this.refreshDraggable();
6523
+ }
6524
+ hostDisconnected() {
6525
+ this.#unbind();
6526
+ try {
6527
+ this.#observer?.disconnect();
6528
+ } catch {}
6529
+ this.#dragging = false;
6530
+ }
6531
+ #renderRoot() {
6532
+ return this.#host.renderRoot ?? this.#host;
6533
+ }
6534
+ #bind() {
6535
+ const root = this.#renderRoot();
6536
+ if (root !== this.#boundRoot) {
6537
+ this.#unbind();
6538
+ this.#boundRoot = root;
6539
+ root.addEventListener(`dragstart`, this.#onDragStart, { capture: true });
6540
+ root.addEventListener(`dragend`, this.#onDragEnd, { capture: true });
6541
+ }
6542
+ if (!this.#boundHost) {
6543
+ this.#boundHost = true;
6544
+ this.#host.addEventListener(`dragstart`, this.#onDragStart, { capture: true });
6545
+ this.#host.addEventListener(`dragend`, this.#onDragEnd, { capture: true });
6546
+ }
6547
+ }
6548
+ #unbind() {
6549
+ this.#boundRoot?.removeEventListener(`dragstart`, this.#onDragStart, { capture: true });
6550
+ this.#boundRoot?.removeEventListener(`dragend`, this.#onDragEnd, { capture: true });
6551
+ this.#boundRoot = void 0;
6552
+ if (this.#boundHost) {
6553
+ this.#boundHost = false;
6554
+ this.#host.removeEventListener(`dragstart`, this.#onDragStart, { capture: true });
6555
+ this.#host.removeEventListener(`dragend`, this.#onDragEnd, { capture: true });
6556
+ }
6557
+ }
6558
+ #onDragStart = (event) => {
6559
+ if (!(event instanceof DragEvent)) return;
6560
+ if (this.#seenDragStarts.has(event)) return;
6561
+ this.#seenDragStarts.add(event);
6562
+ let enabled = false;
6563
+ try {
6564
+ enabled = this.#config.isEnabled();
6565
+ } catch {
6566
+ return;
6567
+ }
6568
+ if (!enabled) return;
6569
+ let originKey;
6570
+ try {
6571
+ originKey = this.#config.resolveEventToKey(event.target);
6572
+ } catch {
6573
+ return;
6574
+ }
6575
+ if (!originKey) return;
6576
+ let prepared;
6577
+ try {
6578
+ prepared = this.#config.prepareSelection(originKey);
6579
+ } catch {
6580
+ return;
6581
+ }
6582
+ if (!prepared || prepared.selected.length === 0) return;
6583
+ const { origin, selected } = prepared;
6584
+ let hostStyle;
6585
+ try {
6586
+ hostStyle = getComputedStyle(this.#host);
6587
+ } catch {
6588
+ hostStyle = document.createElement(`div`).style;
6589
+ }
6590
+ let metrics = {};
6591
+ try {
6592
+ metrics = this.#config.getMetrics() ?? {};
6593
+ } catch {
6594
+ metrics = {};
6595
+ }
6596
+ const context = {
6597
+ source: this.#config.source,
6598
+ origin,
6599
+ selected,
6600
+ metrics,
6601
+ hostStyle
6602
+ };
6603
+ let renderer;
6604
+ try {
6605
+ renderer = this.#config.getRenderer();
6606
+ } catch {
6607
+ renderer = void 0;
6608
+ }
6609
+ let preview;
6610
+ try {
6611
+ preview = renderer ? renderer(context) : createDefaultListDragPreview(context);
6612
+ } catch {
6613
+ preview = createDefaultListDragPreview(context);
6614
+ }
6615
+ try {
6616
+ document.body.appendChild(preview.element);
6617
+ } catch {}
6618
+ const detail = {
6619
+ key: origin.key,
6620
+ index: origin.index,
6621
+ item: origin.item,
6622
+ selectedKeys: selected.map((entry) => entry.key),
6623
+ selected,
6624
+ preview
6625
+ };
6626
+ let notCancelled = true;
6627
+ try {
6628
+ notCancelled = this.#config.dispatch(detail);
6629
+ } catch {
6630
+ notCancelled = true;
6631
+ }
6632
+ if (notCancelled) {
6633
+ const hotspot = preview.hotspot ?? {
6634
+ x: 16,
6635
+ y: 16
6636
+ };
6637
+ try {
6638
+ event.dataTransfer?.setDragImage(preview.element, hotspot.x, hotspot.y);
6639
+ } catch {}
6640
+ } else try {
6641
+ event.preventDefault();
6642
+ } catch {}
6643
+ this.#dragging = true;
6644
+ try {
6645
+ this.#config.onActiveChange(true, origin.key);
6646
+ } catch {}
6647
+ requestAnimationFrame(() => {
6648
+ try {
6649
+ preview.element.remove();
6650
+ } catch {}
6651
+ try {
6652
+ preview.dispose?.();
6653
+ } catch {}
6654
+ });
6655
+ };
6656
+ #onDragEnd = () => {
6657
+ if (!this.#dragging) return;
6658
+ this.#dragging = false;
6659
+ try {
6660
+ this.#config.onActiveChange(false);
6661
+ } catch {}
6662
+ try {
6663
+ this.#config.onEnd();
6664
+ } catch {}
6665
+ };
6666
+ };
6096
6667
  //#endregion
6097
6668
  //#region src/interaction/list-selection-controller.ts
6098
6669
  /** Whether the interaction mode allows building a multi-item selection. */
@@ -13115,6 +13686,7 @@ let DataGridElement = class DataGridElement extends i {
13115
13686
  this.allowGrouping = false;
13116
13687
  this.crossGroupSelection = false;
13117
13688
  this.columnMenu = `auto`;
13689
+ this.itemsDraggable = false;
13118
13690
  this._columnFormatters = /* @__PURE__ */ new Map();
13119
13691
  this._selectedKeys = /* @__PURE__ */ new Set();
13120
13692
  this._scrollTop = 0;
@@ -13132,7 +13704,45 @@ let DataGridElement = class DataGridElement extends i {
13132
13704
  id: k,
13133
13705
  enabled: true
13134
13706
  })),
13135
- onStateChange: () => this.requestUpdate()
13707
+ onStateChange: () => this.requestUpdate(),
13708
+ hoverDisabled: () => this.#nativeDrag.isDragging
13709
+ });
13710
+ this.#nativeDragging = false;
13711
+ this.#postDragSuppressTimer = 0;
13712
+ this.#nativeDrag = new ListNativeDragController(this, {
13713
+ source: `data-grid`,
13714
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this._flat.some((f) => f.type === `row`),
13715
+ resolveEventToKey: (target) => {
13716
+ if (!(target instanceof Element)) return void 0;
13717
+ if (target.closest?.(`.header-cell`)) return void 0;
13718
+ return (target.closest?.(`.row`))?.dataset.key;
13719
+ },
13720
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
13721
+ getMetrics: () => ({ rowHeight: this.rowHeight }),
13722
+ getRenderer: () => this.dragPreviewRenderer,
13723
+ dispatch: (detail) => this.dispatchEvent(new CustomEvent(`list-native-drag-start`, {
13724
+ detail,
13725
+ bubbles: true,
13726
+ composed: true,
13727
+ cancelable: true
13728
+ })),
13729
+ onActiveChange: (active, originKey) => {
13730
+ this.#nativeDragging = active;
13731
+ this.#dragOriginKey = active ? originKey : void 0;
13732
+ this.toggleAttribute(`data-native-dragging`, active);
13733
+ if (active) {
13734
+ const count = this._selectedKeys.size;
13735
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
13736
+ } else this.removeAttribute(`aria-label`);
13737
+ this.requestUpdate();
13738
+ },
13739
+ onEnd: () => {
13740
+ this.#nativeDragging = false;
13741
+ this.#dragOriginKey = void 0;
13742
+ this.toggleAttribute(`data-native-dragging`, false);
13743
+ this.removeAttribute(`aria-label`);
13744
+ this.#startPostDragSuppressTimer();
13745
+ }
13136
13746
  });
13137
13747
  this._sortedRows = [];
13138
13748
  this._flat = [];
@@ -13404,6 +14014,7 @@ let DataGridElement = class DataGridElement extends i {
13404
14014
  .row.item.tickled { background: var(--item-bg-tickled); border: var(--item-border-tickled); color: var(--item-text-tickled); }
13405
14015
  .row.item.selected { background: var(--item-bg-selected); border: var(--item-border-selected); color: var(--item-text-selected); }
13406
14016
  .row.item.selected.tickled { background: var(--item-bg-selected-tickled); }
14017
+ .row.item.dragging-origin { opacity: 0.6; }
13407
14018
 
13408
14019
  /* Insertion / removal animation. Only while [reflowing] — the ~0.3s after
13409
14020
  the row set changes — do rows slide (their top transitions) to make or
@@ -13449,6 +14060,43 @@ let DataGridElement = class DataGridElement extends i {
13449
14060
  `
13450
14061
  ];
13451
14062
  }
14063
+ #nativeDragging;
14064
+ #dragOriginKey;
14065
+ #postDragSuppressTimer;
14066
+ #nativeDrag;
14067
+ #prepareNativeDrag(originKey) {
14068
+ const rows = this._flat.filter((f) => f.type === `row`);
14069
+ if (!rows.some((f) => f.key === originKey)) return void 0;
14070
+ if (!this._selectedKeys.has(originKey)) this._replaceSelection(originKey);
14071
+ const selected = [];
14072
+ let displayIndex = 0;
14073
+ for (const entry of rows) {
14074
+ if (!this._selectedKeys.has(entry.key)) {
14075
+ displayIndex++;
14076
+ continue;
14077
+ }
14078
+ const rowEl = this.renderRoot.querySelector(`.row[data-key="${CSS.escape(entry.key)}"]`) ?? void 0;
14079
+ selected.push({
14080
+ key: entry.key,
14081
+ index: displayIndex,
14082
+ item: entry.row,
14083
+ ...rowEl ? { element: rowEl } : {}
14084
+ });
14085
+ displayIndex++;
14086
+ }
14087
+ const origin = selected.find((entry) => entry.key === originKey);
14088
+ if (!origin) return void 0;
14089
+ return {
14090
+ origin,
14091
+ selected
14092
+ };
14093
+ }
14094
+ #startPostDragSuppressTimer() {
14095
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
14096
+ this.#postDragSuppressTimer = window.setTimeout(() => {
14097
+ this.#postDragSuppressTimer = 0;
14098
+ }, 0);
14099
+ }
13452
14100
  #searchRegistry;
13453
14101
  #unregisterSearchCommands;
13454
14102
  #searchKeyByRow;
@@ -13525,6 +14173,10 @@ let DataGridElement = class DataGridElement extends i {
13525
14173
  clearTimeout(this.#rowReflowTimer);
13526
14174
  this.#rowReflowTimer = 0;
13527
14175
  }
14176
+ if (this.#postDragSuppressTimer) {
14177
+ clearTimeout(this.#postDragSuppressTimer);
14178
+ this.#postDragSuppressTimer = 0;
14179
+ }
13528
14180
  }
13529
14181
  /**
13530
14182
  * Dispatch a command to this grid — routes through the command registry so
@@ -13707,6 +14359,7 @@ let DataGridElement = class DataGridElement extends i {
13707
14359
  }));
13708
14360
  }
13709
14361
  _onRowClick(rowKey, col, e) {
14362
+ if (this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
13710
14363
  if (col && col.selectable === false) return;
13711
14364
  if (this.selectionMode === `none` && this.interactionMode !== `checked` && this.interactionMode !== `sticky`) return;
13712
14365
  if (this.interactionMode === `manual`) return;
@@ -14366,8 +15019,9 @@ let DataGridElement = class DataGridElement extends i {
14366
15019
  const selected = this._selectedKeys.has(rowKey);
14367
15020
  const tickled = this._tickled.isTickled(rowKey);
14368
15021
  return b`
14369
- <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
15022
+ <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``} ${this.#nativeDragging && rowKey === this.#dragOriginKey ? `dragging-origin` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
14370
15023
  data-key=${rowKey}
15024
+ draggable=${this.#nativeDrag.draggableAttr}
14371
15025
  @click=${(e) => {
14372
15026
  this._onRowClick(rowKey, null, e);
14373
15027
  this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
@@ -14550,6 +15204,12 @@ __decorate([property({
14550
15204
  attribute: `search-modes`,
14551
15205
  reflect: true
14552
15206
  })], DataGridElement.prototype, "searchModes", void 0);
15207
+ __decorate([property({
15208
+ type: Boolean,
15209
+ attribute: `items-draggable`,
15210
+ reflect: true
15211
+ })], DataGridElement.prototype, "itemsDraggable", void 0);
15212
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "dragPreviewRenderer", void 0);
14553
15213
  __decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
14554
15214
  __decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
14555
15215
  __decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
@@ -14846,6 +15506,7 @@ let DetailListElement = class DetailListElement extends i {
14846
15506
  this.iconProperty = ``;
14847
15507
  this.keyProperty = ``;
14848
15508
  this.itemWrap = `stop`;
15509
+ this.itemsDraggable = false;
14849
15510
  this.#itemData = /* @__PURE__ */ new WeakMap();
14850
15511
  this.#grid = [];
14851
15512
  this.#positions = /* @__PURE__ */ new Map();
@@ -14881,7 +15542,41 @@ let DetailListElement = class DetailListElement extends i {
14881
15542
  },
14882
15543
  onStateChange: () => this._syncItemStates(),
14883
15544
  itemWrap: this.itemWrap,
14884
- hoverDisabled: () => this.#dragSelect.isDragging
15545
+ hoverDisabled: () => this.#dragSelect.isDragging || this.#nativeDrag.isDragging
15546
+ });
15547
+ this.#nativeDragging = false;
15548
+ this.#postDragSuppressTimer = 0;
15549
+ this.#nativeDrag = new ListNativeDragController(this, {
15550
+ source: `detail-list`,
15551
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this._getVisibleItems().length > 0,
15552
+ resolveEventToKey: (target) => {
15553
+ if (!(target instanceof Element)) return void 0;
15554
+ const li = target.closest?.(`li`);
15555
+ if (!li || !this.contains(li)) return void 0;
15556
+ return keyForElement(li);
15557
+ },
15558
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
15559
+ getMetrics: () => ({}),
15560
+ getRenderer: () => this.dragPreviewRenderer,
15561
+ dispatch: (detail) => this._dispatchCancelable(`list-native-drag-start`, detail),
15562
+ onActiveChange: (active, originKey) => {
15563
+ this.#nativeDragging = active;
15564
+ this.toggleAttribute(`data-native-dragging`, active);
15565
+ for (const el of this.querySelectorAll(`li`)) el.toggleAttribute(`data-drag-origin`, active && originKey !== void 0 && keyForElement(el) === originKey);
15566
+ if (active) {
15567
+ const count = this.selection.selected.size;
15568
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
15569
+ } else this.removeAttribute(`aria-label`);
15570
+ this.requestUpdate();
15571
+ },
15572
+ onEnd: () => {
15573
+ this.#nativeDragging = false;
15574
+ this.toggleAttribute(`data-native-dragging`, false);
15575
+ this.removeAttribute(`aria-label`);
15576
+ for (const el of this.querySelectorAll(`li`)) el.removeAttribute(`data-drag-origin`);
15577
+ this.#startPostDragSuppressTimer();
15578
+ },
15579
+ syncLightDomDraggable: () => this._syncNativeDraggable()
14885
15580
  });
14886
15581
  this.#dragSelect = new ListDragSelectController(this, {
14887
15582
  getSurface: () => this.renderRoot.querySelector(`.container`),
@@ -14978,7 +15673,7 @@ let DetailListElement = class DetailListElement extends i {
14978
15673
  }
14979
15674
  };
14980
15675
  this._onClick = (e) => {
14981
- if (this.#dragSelect.isDragging) return;
15676
+ if (this.#dragSelect.isDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
14982
15677
  const item = this._findListItem(e.composedPath());
14983
15678
  if (!item) return;
14984
15679
  this.focus();
@@ -15005,6 +15700,42 @@ let DetailListElement = class DetailListElement extends i {
15005
15700
  #builtFromProvider;
15006
15701
  #providerBuilt;
15007
15702
  #data;
15703
+ #nativeDragging;
15704
+ #postDragSuppressTimer;
15705
+ #nativeDrag;
15706
+ #prepareNativeDrag(originKey) {
15707
+ const visible = this._getVisibleItems();
15708
+ const originEl = visible.find((el) => keyForElement(el) === originKey);
15709
+ if (!originEl || !this._isCheckable(originEl)) return void 0;
15710
+ if (!this.selection.selected.has(originEl)) this.selection.replace(originEl);
15711
+ const selected = [];
15712
+ visible.forEach((el, index) => {
15713
+ if (!this.selection.selected.has(el)) return;
15714
+ const key = keyForElement(el) ?? originKey;
15715
+ selected.push({
15716
+ key,
15717
+ index,
15718
+ item: this.#itemData.get(el) ?? el,
15719
+ element: el
15720
+ });
15721
+ });
15722
+ const origin = selected.find((entry) => entry.key === originKey);
15723
+ if (!origin) return void 0;
15724
+ return {
15725
+ origin,
15726
+ selected
15727
+ };
15728
+ }
15729
+ _syncNativeDraggable() {
15730
+ if (!this.isConnected) return;
15731
+ for (const el of this.querySelectorAll(`li`)) if (el instanceof HTMLElement) el.draggable = this.itemsDraggable && this._isCheckable(el);
15732
+ }
15733
+ #startPostDragSuppressTimer() {
15734
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
15735
+ this.#postDragSuppressTimer = window.setTimeout(() => {
15736
+ this.#postDragSuppressTimer = 0;
15737
+ }, 0);
15738
+ }
15008
15739
  /** Live drag-select preview rect (surface-relative); the covered items carry `data-marquee`. */
15009
15740
  #marqueeRect;
15010
15741
  #dragSelect;
@@ -15107,11 +15838,16 @@ let DetailListElement = class DetailListElement extends i {
15107
15838
  this.#unregisterSearchCommands?.();
15108
15839
  this.#unregisterSearchCommands = void 0;
15109
15840
  this.#searchRegistry = void 0;
15841
+ if (this.#postDragSuppressTimer) {
15842
+ clearTimeout(this.#postDragSuppressTimer);
15843
+ this.#postDragSuppressTimer = 0;
15844
+ }
15110
15845
  }
15111
15846
  firstUpdated() {
15112
15847
  this.#processChildren();
15113
15848
  if (this.filterPredicate) this.#applyFilterPredicate();
15114
15849
  this.#recomputeGeometry();
15850
+ this._syncNativeDraggable();
15115
15851
  }
15116
15852
  updated(changed) {
15117
15853
  super.updated(changed);
@@ -15135,7 +15871,12 @@ let DetailListElement = class DetailListElement extends i {
15135
15871
  if (changed.has(`interactionMode`)) {
15136
15872
  this.selection.clearAnchor();
15137
15873
  this._syncItemStates();
15874
+ this._syncNativeDraggable();
15138
15875
  } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
15876
+ if (changed.has(`itemsDraggable`)) {
15877
+ this._syncNativeDraggable();
15878
+ this.#nativeDrag.refreshDraggable();
15879
+ }
15139
15880
  }
15140
15881
  /** The most recently selected item, or `undefined` if nothing is selected. */
15141
15882
  get selectedItem() {
@@ -15700,6 +16441,14 @@ let DetailListElement = class DetailListElement extends i {
15700
16441
  composed: true
15701
16442
  }));
15702
16443
  }
16444
+ _dispatchCancelable(name, detail) {
16445
+ return this.dispatchEvent(new CustomEvent(name, {
16446
+ detail,
16447
+ bubbles: true,
16448
+ composed: true,
16449
+ cancelable: true
16450
+ }));
16451
+ }
15703
16452
  _activateItem(item) {
15704
16453
  if (this.interactionMode !== `manual`) this.selection.replace(item);
15705
16454
  this._dispatch(`list-activate`, { item });
@@ -15817,6 +16566,10 @@ let DetailListElement = class DetailListElement extends i {
15817
16566
  color: var(--item-text-selected);
15818
16567
  }
15819
16568
 
16569
+ ::slotted([data-drag-origin]) {
16570
+ opacity: 0.6;
16571
+ }
16572
+
15820
16573
  /* checked mode: the injected checkbox communicates selection — suppress bg */
15821
16574
  :host([interaction-mode="checked"]) ::slotted([data-selected]) {
15822
16575
  background: transparent;
@@ -15906,6 +16659,12 @@ __decorate([property({
15906
16659
  attribute: `search-modes`,
15907
16660
  reflect: true
15908
16661
  })], DetailListElement.prototype, "searchModes", void 0);
16662
+ __decorate([property({
16663
+ type: Boolean,
16664
+ attribute: `items-draggable`,
16665
+ reflect: true
16666
+ })], DetailListElement.prototype, "itemsDraggable", void 0);
16667
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "dragPreviewRenderer", void 0);
15909
16668
  __decorate([query(`ul`)], DetailListElement.prototype, "_listEl", void 0);
15910
16669
  DetailListElement = __decorate([safeCustomElement(`ixfx-detail-list`)], DetailListElement);
15911
16670
  //#endregion
@@ -21944,12 +22703,50 @@ let GridListElement = class GridListElement extends i {
21944
22703
  },
21945
22704
  onStateChange: () => this._syncItemStates(),
21946
22705
  itemWrap: this.#itemWrap,
21947
- hoverDisabled: () => this.disableHoverTickle || this.#dragSelect.isDragging
22706
+ hoverDisabled: () => this.disableHoverTickle || this.#dragSelect.isDragging || this.#nativeDrag.isDragging
21948
22707
  });
21949
22708
  this.#marqueePreview = /* @__PURE__ */ new Set();
21950
22709
  this.#nativeDragging = false;
21951
22710
  this.#dragOriginKey = void 0;
21952
22711
  this.#postDragSuppressTimer = 0;
22712
+ this.#nativeDrag = new ListNativeDragController(this, {
22713
+ source: `grid-list`,
22714
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this.#source.count > 0,
22715
+ resolveEventToKey: (target) => {
22716
+ if (target instanceof Element) {
22717
+ const key = (target.closest?.(`.cell`))?.dataset.key;
22718
+ if (key) return key;
22719
+ const keyed = (target.closest?.(`[data-key]`))?.dataset.key ?? target.dataset?.key ?? keyForElement(target);
22720
+ if (keyed) return keyed;
22721
+ }
22722
+ },
22723
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
22724
+ getMetrics: () => ({
22725
+ itemSize: this.itemSize,
22726
+ captionStyle: this.captionStyle,
22727
+ captionHeight: this.captionHeight,
22728
+ rowHeight: this.#rowHeight()
22729
+ }),
22730
+ getRenderer: () => this.dragPreviewRenderer,
22731
+ dispatch: (detail) => this._dispatch(`list-native-drag-start`, detail, true),
22732
+ onActiveChange: (active, originKey) => {
22733
+ this.#nativeDragging = active;
22734
+ this.#dragOriginKey = active ? originKey : void 0;
22735
+ this.toggleAttribute(`data-native-dragging`, active);
22736
+ if (active) {
22737
+ const count = [...this.selection.selectedKeys].filter((k) => !isSyntheticKey(k)).length;
22738
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
22739
+ } else this.removeAttribute(`aria-label`);
22740
+ this.requestUpdate();
22741
+ },
22742
+ onEnd: () => {
22743
+ this.#clearNativeDragState();
22744
+ this.#startPostDragSuppressTimer();
22745
+ },
22746
+ syncLightDomDraggable: () => {
22747
+ for (const child of this.children) if (child instanceof HTMLElement) child.draggable = this.itemsDraggable;
22748
+ }
22749
+ });
21953
22750
  this.#activeCaptionPan = null;
21954
22751
  this.#dragSelect = new ListDragSelectController(this, {
21955
22752
  getSurface: () => this.#viewportRef.value,
@@ -21995,23 +22792,6 @@ let GridListElement = class GridListElement extends i {
21995
22792
  this.#emitViewportChange();
21996
22793
  });
21997
22794
  };
21998
- this.#onDragStart = (e, key) => {
21999
- this.#doDragStart(key, () => e.preventDefault(), e);
22000
- };
22001
- this.#onSlottedDragStart = (e) => {
22002
- if (!this.itemsDraggable) return;
22003
- let cell = e.composedPath().find((n) => n instanceof Element && n.classList.contains(`cell`));
22004
- if (!cell && e.target instanceof Element) cell = e.target.closest?.(`.cell`);
22005
- if (!cell) return;
22006
- const key = cell.dataset.key;
22007
- if (!key) return;
22008
- if (e instanceof DragEvent) this.#doDragStart(key, () => e.preventDefault(), e);
22009
- else this.#doDragStart(key, () => {});
22010
- };
22011
- this.#onDragEnd = () => {
22012
- this.#clearNativeDragState();
22013
- this.#startPostDragSuppressTimer();
22014
- };
22015
22795
  this._onKeyDown = (e) => {
22016
22796
  if ((e.ctrlKey || e.metaKey) && e.key === `f`) return;
22017
22797
  if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
@@ -22448,6 +23228,35 @@ let GridListElement = class GridListElement extends i {
22448
23228
  #dragOriginKey;
22449
23229
  /** Timer handle for post-drag click suppression. */
22450
23230
  #postDragSuppressTimer;
23231
+ #nativeDrag;
23232
+ /** Origin rule + display-ordered entry resolution for native drag-out (both modes). */
23233
+ #prepareNativeDrag(originKey) {
23234
+ const index = this.#source.indexOfKey(originKey);
23235
+ if (index < 0 || !this.#source.isSelectable(index)) return void 0;
23236
+ if (!this.selection.selectedKeys.has(originKey)) this.selection.replace(originKey);
23237
+ const ordered = this.#source.keys().filter((k) => !isSyntheticKey(k) && this.selection.selectedKeys.has(k));
23238
+ const selected = [];
23239
+ for (const key of ordered) {
23240
+ const entryIndex = this.#source.indexOfKey(key);
23241
+ if (entryIndex < 0) continue;
23242
+ const data = this.#source.resolveKeyToData(key);
23243
+ const element = this.#source.resolveKeyToElement(key);
23244
+ const item = data ?? element;
23245
+ if (item === void 0) continue;
23246
+ selected.push({
23247
+ key,
23248
+ index: entryIndex,
23249
+ item,
23250
+ ...element ? { element } : {}
23251
+ });
23252
+ }
23253
+ const origin = selected.find((entry) => entry.key === originKey);
23254
+ if (!origin) return void 0;
23255
+ return {
23256
+ origin,
23257
+ selected
23258
+ };
23259
+ }
22451
23260
  /**
22452
23261
  * The single caption pan-text currently scrolling. At most one caption is
22453
23262
  * a pan-text in loop mode at a time — when tickle moves, the old one is
@@ -22580,7 +23389,7 @@ let GridListElement = class GridListElement extends i {
22580
23389
  clearTimeout(this.#reflowTimer);
22581
23390
  this.#reflowTimer = 0;
22582
23391
  }
22583
- if (this.#nativeDragging) this.#clearNativeDragState();
23392
+ if (this.#nativeDragging || this.#nativeDrag.isDragging) this.#clearNativeDragState();
22584
23393
  }
22585
23394
  #onChildrenChanged(records) {
22586
23395
  const previousLength = this.#items.length;
@@ -22661,7 +23470,7 @@ let GridListElement = class GridListElement extends i {
22661
23470
  this.#measureViewport(false);
22662
23471
  this.#syncItems();
22663
23472
  this.requestUpdate();
22664
- this.#initSlottedDragInterceptor();
23473
+ this.#nativeDrag.refreshDraggable();
22665
23474
  }
22666
23475
  /** (Re)construct {@link #source} for the current mode and re-apply the filters. */
22667
23476
  #rebuildSource() {
@@ -22751,7 +23560,7 @@ let GridListElement = class GridListElement extends i {
22751
23560
  this.selection.clearAnchor();
22752
23561
  this.#syncAriaMultiselectable();
22753
23562
  }
22754
- if (changed.has(`itemsDraggable`)) this.#syncSlottedDraggable();
23563
+ if (changed.has(`itemsDraggable`)) this.#nativeDrag.refreshDraggable();
22755
23564
  if (changed.has(`orientation`)) {
22756
23565
  const vp = this.#viewportRef.value;
22757
23566
  if (vp) {
@@ -23170,8 +23979,6 @@ let GridListElement = class GridListElement extends i {
23170
23979
  @click=${(e) => this.#onCellClick(e, key)}
23171
23980
  @dblclick=${() => this.#onCellActivate(key)}
23172
23981
  @pointerenter=${() => this.tickled.handlePointerEnter(key)}
23173
- @dragstart=${(e) => this.#onDragStart(e, key)}
23174
- @dragend=${() => this.#onDragEnd()}
23175
23982
  >
23176
23983
  <div class="content" part="content">${this.#source.renderCellContent(index)}</div>
23177
23984
  ${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
@@ -23256,8 +24063,6 @@ let GridListElement = class GridListElement extends i {
23256
24063
  @click=${(e) => this.#onCellClick(e, key)}
23257
24064
  @dblclick=${() => this.#onCellActivate(key)}
23258
24065
  @pointerenter=${() => this.tickled.handlePointerEnter(key)}
23259
- @dragstart=${(e) => this.#onDragStart(e, key)}
23260
- @dragend=${() => this.#onDragEnd()}
23261
24066
  >
23262
24067
  <div class="thumb" part="thumb" style=${thumbStyle}>
23263
24068
  ${this.#source.renderCellContent(index)}
@@ -23274,145 +24079,10 @@ let GridListElement = class GridListElement extends i {
23274
24079
  }
23275
24080
  }
23276
24081
  #onCellClick(e, key) {
23277
- if (this.#dragSelect.isDragging || this.#nativeDragging || this.#postDragSuppressTimer !== 0) return;
24082
+ if (this.#dragSelect.isDragging || this.#nativeDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
23278
24083
  this.selection.handleClick(key, e);
23279
24084
  this.#emitItemEvent(`list-item-click`, key);
23280
24085
  }
23281
- #doDragStart(key, cancelled, dragEvent) {
23282
- if (!this.itemsDraggable) return false;
23283
- const index = this.#source.indexOfKey(key);
23284
- if (index < 0 || !this.#source.isSelectable(index)) return false;
23285
- if (!this.selection.selectedKeys.has(key)) this.selection.replace(key);
23286
- const selectedKeys = this.#source.keys().filter((k) => !isSyntheticKey(k)).filter((k) => this.selection.selectedKeys.has(k));
23287
- const detail = {
23288
- key,
23289
- index: this.#source.indexOfKey(key),
23290
- item: this.#source.resolveKeyToData(key) ?? this.#source.resolveKeyToElement(key),
23291
- selectedKeys
23292
- };
23293
- if (!this._dispatch(`grid-list-native-drag-start`, detail, true)) {
23294
- cancelled();
23295
- return false;
23296
- }
23297
- this.#nativeDragging = true;
23298
- this.#dragOriginKey = key;
23299
- this.toggleAttribute(`data-native-dragging`, true);
23300
- const count = selectedKeys.length;
23301
- this.setAttribute(`aria-label`, count === 1 ? `Dragging 1 item` : `Dragging ${count} items`);
23302
- if (dragEvent && count > 1) this.#setMultiItemDragImage(dragEvent, selectedKeys);
23303
- return true;
23304
- }
23305
- #setMultiItemDragImage(e, selectedKeys) {
23306
- const preview = this.#createMultiItemPreview(selectedKeys);
23307
- document.body.appendChild(preview);
23308
- const rect = preview.getBoundingClientRect();
23309
- e.dataTransfer?.setDragImage(preview, rect.width / 2, rect.height / 2);
23310
- requestAnimationFrame(() => preview.remove());
23311
- }
23312
- #createMultiItemPreview(selectedKeys) {
23313
- const preview = document.createElement(`div`);
23314
- preview.style.cssText = `
23315
- position: fixed;
23316
- top: -9999px;
23317
- left: -9999px;
23318
- display: flex;
23319
- flex-direction: column;
23320
- gap: 4px;
23321
- padding: 8px;
23322
- background: var(--surface-4, #333);
23323
- border: 1px solid var(--border, #555);
23324
- border-radius: 8px;
23325
- box-shadow: 0 4px 16px rgba(0,0,0,0.3);
23326
- font-family: system-ui, sans-serif;
23327
- font-size: 12px;
23328
- color: var(--surface-text, #fff);
23329
- max-width: 200px;
23330
- z-index: 999999;
23331
- `;
23332
- const header = document.createElement(`div`);
23333
- header.style.cssText = `
23334
- font-weight: 600;
23335
- padding-bottom: 4px;
23336
- border-bottom: 1px solid var(--border, #555);
23337
- margin-bottom: 4px;
23338
- `;
23339
- header.textContent = `${selectedKeys.length} items`;
23340
- preview.appendChild(header);
23341
- for (const key of selectedKeys.slice(0, 5)) {
23342
- const row = this.#createPreviewRow(key);
23343
- if (row) preview.appendChild(row);
23344
- }
23345
- if (selectedKeys.length > 5) {
23346
- const more = document.createElement(`div`);
23347
- more.style.cssText = `opacity: 0.6; text-align: center;`;
23348
- more.textContent = `+${selectedKeys.length - 5} more`;
23349
- preview.appendChild(more);
23350
- }
23351
- return preview;
23352
- }
23353
- #createPreviewRow(key) {
23354
- const el = this.#source.resolveKeyToElement(key);
23355
- if (!el) return null;
23356
- const row = document.createElement(`div`);
23357
- row.style.cssText = `
23358
- display: flex;
23359
- align-items: center;
23360
- gap: 6px;
23361
- `;
23362
- const thumb = document.createElement(`div`);
23363
- thumb.style.cssText = `
23364
- width: 32px;
23365
- height: 32px;
23366
- border-radius: 4px;
23367
- overflow: hidden;
23368
- background: var(--surface-5, #444);
23369
- flex-shrink: 0;
23370
- display: flex;
23371
- align-items: center;
23372
- justify-content: center;
23373
- `;
23374
- const slottedImg = el.querySelector(`img`);
23375
- const slottedSvg = el.querySelector(`svg`);
23376
- const slottedPlaceholder = el.querySelector(`.placeholder`);
23377
- if (slottedImg) {
23378
- const img = slottedImg.cloneNode(true);
23379
- img.style.cssText = `width: 100%; height: 100%; object-fit: cover;`;
23380
- thumb.appendChild(img);
23381
- } else if (slottedSvg) {
23382
- const svg = slottedSvg.cloneNode(true);
23383
- svg.style.cssText = `width: 70%; height: 70%; opacity: 0.8;`;
23384
- thumb.appendChild(svg);
23385
- } else if (slottedPlaceholder) thumb.textContent = slottedPlaceholder.textContent || `?`;
23386
- const label = document.createElement(`span`);
23387
- label.style.cssText = `
23388
- flex: 1;
23389
- overflow: hidden;
23390
- text-overflow: ellipsis;
23391
- white-space: nowrap;
23392
- `;
23393
- label.textContent = el.getAttribute(`data-label`) || key;
23394
- row.appendChild(thumb);
23395
- row.appendChild(label);
23396
- return row;
23397
- }
23398
- #onDragStart;
23399
- #onSlottedDragStart;
23400
- #initSlottedDragInterceptor() {
23401
- this.renderRoot.addEventListener(`dragstart`, this.#onSlottedDragStart, { capture: true });
23402
- const observer = new MutationObserver(() => this.#syncSlottedDraggable());
23403
- observer.observe(this, {
23404
- childList: true,
23405
- subtree: true
23406
- });
23407
- this.#slottedDragObserver = observer;
23408
- this.#syncSlottedDraggable();
23409
- }
23410
- #slottedDragObserver;
23411
- #syncSlottedDraggable() {
23412
- const draggable = this.itemsDraggable;
23413
- for (const child of this.children) if (child instanceof HTMLElement) child.draggable = draggable;
23414
- }
23415
- #onDragEnd;
23416
24086
  #startPostDragSuppressTimer() {
23417
24087
  if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
23418
24088
  this.#postDragSuppressTimer = window.setTimeout(() => {
@@ -23487,6 +24157,7 @@ __decorate([property({
23487
24157
  attribute: `items-draggable`,
23488
24158
  reflect: true
23489
24159
  })], GridListElement.prototype, "itemsDraggable", void 0);
24160
+ __decorate([property({ attribute: false })], GridListElement.prototype, "dragPreviewRenderer", void 0);
23490
24161
  __decorate([property({
23491
24162
  type: String,
23492
24163
  attribute: `search-modes`,
@@ -23915,7 +24586,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
23915
24586
  this.#lastNodeByKey = /* @__PURE__ */ new Map();
23916
24587
  this.#enteringKeys = /* @__PURE__ */ new Set();
23917
24588
  this.#leaving = /* @__PURE__ */ new Map();
23918
- this.#leavingAnimating = /* @__PURE__ */ new Set();
24589
+ this.#leavingAnimations = /* @__PURE__ */ new Map();
23919
24590
  this.#treeAnimReady = false;
23920
24591
  this.#onKeyDown = (event) => {
23921
24592
  if (this.search.isOpen) return;
@@ -24026,11 +24697,17 @@ let TreeBaseElement = class TreeBaseElement extends i {
24026
24697
  if (!key) return;
24027
24698
  const node = this.#getAllNodes().find((n) => n.node.item.key === key);
24028
24699
  if (!node) return;
24700
+ const detail = {
24701
+ node: node.node,
24702
+ depth: node.depth
24703
+ };
24704
+ this.dispatchEvent(new CustomEvent(`item-dblclick`, {
24705
+ detail,
24706
+ bubbles: true,
24707
+ composed: true
24708
+ }));
24029
24709
  this.dispatchEvent(new CustomEvent(`activate`, {
24030
- detail: {
24031
- node: node.node,
24032
- depth: node.depth
24033
- },
24710
+ detail,
24034
24711
  bubbles: true,
24035
24712
  composed: true
24036
24713
  }));
@@ -24062,8 +24739,18 @@ let TreeBaseElement = class TreeBaseElement extends i {
24062
24739
  #lastNodeByKey;
24063
24740
  #enteringKeys;
24064
24741
  #leaving;
24065
- #leavingAnimating;
24742
+ #leavingAnimations;
24066
24743
  #treeAnimReady;
24744
+ #cancelLeavingAnimation(key) {
24745
+ this.#leavingAnimations.get(key)?.cancel();
24746
+ this.#leavingAnimations.delete(key);
24747
+ }
24748
+ #cancelAllLeavingAnimations() {
24749
+ for (const animation of this.#leavingAnimations.values()) try {
24750
+ animation.cancel();
24751
+ } catch {}
24752
+ this.#leavingAnimations.clear();
24753
+ }
24067
24754
  willUpdate(changedProps) {
24068
24755
  super.willUpdate(changedProps);
24069
24756
  this.#diffVisibleNodes();
@@ -24082,12 +24769,12 @@ let TreeBaseElement = class TreeBaseElement extends i {
24082
24769
  }
24083
24770
  }
24084
24771
  for (const key of [...this.#leaving.keys()]) if (nowKeys.has(key)) {
24772
+ this.#cancelLeavingAnimation(key);
24085
24773
  this.#leaving.delete(key);
24086
- this.#leavingAnimating.delete(key);
24087
24774
  }
24088
24775
  if (this.#leaving.size > MAX_ANIMATED_ROWS) {
24776
+ this.#cancelAllLeavingAnimations();
24089
24777
  this.#leaving.clear();
24090
- this.#leavingAnimating.clear();
24091
24778
  }
24092
24779
  }
24093
24780
  /** True while `node` is a fading ghost row (removed / collapsed, still animating out). */
@@ -24124,18 +24811,38 @@ let TreeBaseElement = class TreeBaseElement extends i {
24124
24811
  if (this.#enteringKeys.size > 0) {
24125
24812
  for (const el of this.renderRoot.querySelectorAll(`.item`)) {
24126
24813
  const key = el.dataset.key;
24127
- if (key !== void 0 && this.#enteringKeys.has(key)) animateListItemIn(el);
24814
+ if (key === void 0 || !this.#enteringKeys.has(key)) continue;
24815
+ if (this.#leavingAnimations.has(key)) this.#cancelLeavingAnimation(key);
24816
+ else for (const animation of el.getAnimations()) animation.cancel();
24817
+ animateListItemIn(el);
24128
24818
  }
24129
24819
  this.#enteringKeys.clear();
24130
24820
  }
24131
24821
  if (this.#leaving.size > 0) for (const el of this.renderRoot.querySelectorAll(`.item.leaving`)) {
24132
24822
  const key = el.dataset.key;
24133
- if (key === void 0 || this.#leavingAnimating.has(key)) continue;
24134
- this.#leavingAnimating.add(key);
24135
- animateListItemOut(el).then(() => {
24823
+ if (key === void 0 || this.#leavingAnimations.has(key)) continue;
24824
+ const result = animateListItemOut(el);
24825
+ const animation = result.animation;
24826
+ if (!animation) {
24827
+ result.then(() => {
24828
+ if (!this.#leaving.has(key)) return;
24829
+ this.#leaving.delete(key);
24830
+ this.requestUpdate();
24831
+ });
24832
+ continue;
24833
+ }
24834
+ this.#cancelLeavingAnimation(key);
24835
+ this.#leavingAnimations.set(key, animation);
24836
+ result.then(() => {
24837
+ if (this.#leavingAnimations.get(key) !== animation) return;
24838
+ this.#leavingAnimations.delete(key);
24136
24839
  this.#leaving.delete(key);
24137
- this.#leavingAnimating.delete(key);
24138
24840
  this.requestUpdate();
24841
+ this.updateComplete.then(() => {
24842
+ try {
24843
+ animation.cancel();
24844
+ } catch {}
24845
+ });
24139
24846
  });
24140
24847
  }
24141
24848
  const visible = this.root ? this.#getAllNodes() : [];
@@ -24151,6 +24858,9 @@ let TreeBaseElement = class TreeBaseElement extends i {
24151
24858
  this.#clearSelectionInternal();
24152
24859
  this.tickled.clearTickled();
24153
24860
  this._focusedDepth = 0;
24861
+ this.#cancelAllLeavingAnimations();
24862
+ this.#leaving.clear();
24863
+ this.#enteringKeys.clear();
24154
24864
  }
24155
24865
  if (changedProps.has(`model`)) {
24156
24866
  this.#modelUnsubscribe?.();
@@ -24201,6 +24911,9 @@ let TreeBaseElement = class TreeBaseElement extends i {
24201
24911
  }
24202
24912
  disconnectedCallback() {
24203
24913
  super.disconnectedCallback();
24914
+ this.#cancelAllLeavingAnimations();
24915
+ this.#leaving.clear();
24916
+ this.#enteringKeys.clear();
24204
24917
  this.#modelUnsubscribe?.();
24205
24918
  this.#modelUnsubscribe = void 0;
24206
24919
  this.#unregisterSearchCommands?.();
@@ -24379,6 +25092,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
24379
25092
  `;
24380
25093
  }
24381
25094
  _handleItemClick(node, depth, event) {
25095
+ if (event && event.detail >= 2) return;
24382
25096
  if (this.#pendingNode && this._expandedKeys.has(this.#pendingNode.item.key)) this.#abortPending();
24383
25097
  this.tickled.handlePointerEnter(node.item.key);
24384
25098
  this._focusedDepth = depth;
@@ -24392,7 +25106,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
24392
25106
  }));
24393
25107
  switch (this.interactionMode) {
24394
25108
  case `implicit`:
24395
- this.#handleImplicitClick(node, depth);
25109
+ this.#handleImplicitClick(node, depth, event);
24396
25110
  break;
24397
25111
  case `standard`:
24398
25112
  this.#handleStandardClick(node, depth, event);
@@ -24407,8 +25121,20 @@ let TreeBaseElement = class TreeBaseElement extends i {
24407
25121
  }
24408
25122
  this.requestUpdate();
24409
25123
  }
24410
- #handleImplicitClick(node, depth) {
24411
- if (this.#canSelect(node)) {
25124
+ /**
25125
+ * True when a click landed on the expand/collapse caret rather than the node
25126
+ * label. Caret clicks should only toggle expansion, never select or navigate.
25127
+ * The caret and label are siblings in the same shadow root, so `closest` on
25128
+ * the event target resolves reliably. See tree-fix.md.
25129
+ */
25130
+ #clickedOnCaret(node, event) {
25131
+ if (node.item.isLeaf === true) return false;
25132
+ const target = event?.target;
25133
+ if (!(target instanceof Element)) return false;
25134
+ return target.closest(`.tree-caret`) !== null;
25135
+ }
25136
+ #handleImplicitClick(node, depth, event) {
25137
+ if (!this.#clickedOnCaret(node, event) && this.#canSelect(node)) {
24412
25138
  const prev = new Set(this._selectedNodes);
24413
25139
  this._selectedNodes.clear();
24414
25140
  this._selectedNodes.add(node);
@@ -24453,7 +25179,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
24453
25179
  }
24454
25180
  return;
24455
25181
  }
24456
- if (this.#canSelect(node)) {
25182
+ if (!this.#clickedOnCaret(node, event) && this.#canSelect(node)) {
24457
25183
  const prev = new Set(this._selectedNodes);
24458
25184
  this._selectedNodes.clear();
24459
25185
  this._selectedNodes.add(node);
@@ -24848,8 +25574,8 @@ let TreeBaseElement = class TreeBaseElement extends i {
24848
25574
  --tree-text: var(--surface-3-text);
24849
25575
  --tree-muted-text: var(--surface-muted-text);
24850
25576
  --tree-indent: 1ch;
24851
- --tree-item-height: 28px;
24852
- --tree-caret-size: 10px;
25577
+ --tree-item-height: 30px;
25578
+ --tree-caret-size: 30px;
24853
25579
  --tree-radius: var(--radius-s);
24854
25580
 
24855
25581
  position: relative;
@@ -24895,10 +25621,14 @@ let TreeBaseElement = class TreeBaseElement extends i {
24895
25621
  display: inline-flex;
24896
25622
  align-items: center;
24897
25623
  justify-content: center;
24898
- width: var(--tree-caret-size);
25624
+ /* Deliberately narrower than the icon: Lucide chevrons fill only ~half
25625
+ their viewBox, so letting the glyph overflow a slim box closes the
25626
+ visual gap to the label without shifting the indent. */
25627
+ width: calc(var(--tree-caret-size) * 0.5);
24899
25628
  height: var(--tree-caret-size);
24900
25629
  flex-shrink: 0;
24901
- margin-right: var(--space-xs);
25630
+ margin-right: 1px;
25631
+ overflow: visible;
24902
25632
  }
24903
25633
 
24904
25634
  .caret-icon {
@@ -25095,10 +25825,14 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
25095
25825
  display: inline-flex;
25096
25826
  align-items: center;
25097
25827
  justify-content: center;
25098
- width: var(--tree-caret-size);
25828
+ /* Deliberately narrower than the icon: Lucide chevrons fill only ~half
25829
+ their viewBox, so letting the glyph overflow a slim box closes the
25830
+ visual gap to the label without shifting the indent. */
25831
+ width: calc(var(--tree-caret-size) * 0.5);
25099
25832
  height: var(--tree-caret-size);
25100
25833
  flex-shrink: 0;
25101
- margin-right: var(--space-xs);
25834
+ margin-right: 1px;
25835
+ overflow: visible;
25102
25836
  }
25103
25837
 
25104
25838
  .caret-icon {
@@ -25116,7 +25850,7 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
25116
25850
  flex-shrink: 0;
25117
25851
  width: var(--icon-size, 1em);
25118
25852
  height: var(--icon-size, 1em);
25119
- margin-right: var(--icon-spacing, 0.3em);
25853
+ margin-right: var(--icon-spacing, 0px);
25120
25854
  }
25121
25855
 
25122
25856
  .item:not(.has-icon-at-depth) .tree-icon {
@@ -25344,6 +26078,7 @@ let VerticalListElement = class VerticalListElement extends i {
25344
26078
  this.selectionMode = `single`;
25345
26079
  this.interactionMode = `standard`;
25346
26080
  this.itemWrap = `stop`;
26081
+ this.itemsDraggable = false;
25347
26082
  this.#customWrappers = /* @__PURE__ */ new Map();
25348
26083
  this.#wrappedElements = /* @__PURE__ */ new Set();
25349
26084
  this.#animatedItems = /* @__PURE__ */ new WeakSet();
@@ -25374,7 +26109,41 @@ let VerticalListElement = class VerticalListElement extends i {
25374
26109
  },
25375
26110
  onStateChange: () => this._syncItemStates(),
25376
26111
  itemWrap: this.itemWrap,
25377
- hoverDisabled: () => this.#dragSelect.isDragging
26112
+ hoverDisabled: () => this.#dragSelect.isDragging || this.#nativeDrag.isDragging
26113
+ });
26114
+ this.#nativeDragging = false;
26115
+ this.#postDragSuppressTimer = 0;
26116
+ this.#nativeDrag = new ListNativeDragController(this, {
26117
+ source: `vertical-list`,
26118
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this._getVisibleItems().length > 0,
26119
+ resolveEventToKey: (target) => {
26120
+ if (!(target instanceof Element)) return void 0;
26121
+ const item = target.closest?.(ITEM_SELECTOR);
26122
+ if (!item || !this.contains(item) || this.#wrappedElements.has(item)) return void 0;
26123
+ return keyForElement(item);
26124
+ },
26125
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
26126
+ getMetrics: () => ({}),
26127
+ getRenderer: () => this.dragPreviewRenderer,
26128
+ dispatch: (detail) => this._dispatchCancelable(`list-native-drag-start`, detail),
26129
+ onActiveChange: (active, originKey) => {
26130
+ this.#nativeDragging = active;
26131
+ this.toggleAttribute(`data-native-dragging`, active);
26132
+ for (const el of this._getVisibleItems()) el.toggleAttribute(`data-drag-origin`, active && originKey !== void 0 && keyForElement(el) === originKey);
26133
+ if (active) {
26134
+ const count = this.selection.selected.size;
26135
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
26136
+ } else this.removeAttribute(`aria-label`);
26137
+ this.requestUpdate();
26138
+ },
26139
+ onEnd: () => {
26140
+ this.#nativeDragging = false;
26141
+ this.toggleAttribute(`data-native-dragging`, false);
26142
+ this.removeAttribute(`aria-label`);
26143
+ for (const el of this._getVisibleItems()) el.removeAttribute(`data-drag-origin`);
26144
+ this.#startPostDragSuppressTimer();
26145
+ },
26146
+ syncLightDomDraggable: () => this._syncNativeDraggable()
25378
26147
  });
25379
26148
  this.#dragSelect = new ListDragSelectController(this, {
25380
26149
  getSurface: () => this.renderRoot.querySelector(`.container`),
@@ -25456,7 +26225,7 @@ let VerticalListElement = class VerticalListElement extends i {
25456
26225
  }
25457
26226
  };
25458
26227
  this._onClick = (e) => {
25459
- if (this.#dragSelect.isDragging) return;
26228
+ if (this.#dragSelect.isDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
25460
26229
  const item = this._findListItem(e.composedPath());
25461
26230
  if (!item) return;
25462
26231
  this._dispatch(`list-item-click`, { item });
@@ -25473,6 +26242,44 @@ let VerticalListElement = class VerticalListElement extends i {
25473
26242
  #itemObserver;
25474
26243
  /** Items that have already played (or been skipped for) their enter animation. */
25475
26244
  #animatedItems;
26245
+ #nativeDragging;
26246
+ #postDragSuppressTimer;
26247
+ #nativeDrag;
26248
+ #prepareNativeDrag(originKey) {
26249
+ const visible = this._getVisibleItems();
26250
+ const originEl = visible.find((el) => keyForElement(el) === originKey);
26251
+ if (!originEl || !this._isCheckable(originEl)) return void 0;
26252
+ if (!this.selection.selected.has(originEl)) this.selection.replace(originEl);
26253
+ const selected = [];
26254
+ visible.forEach((el, index) => {
26255
+ if (!this.selection.selected.has(el)) return;
26256
+ selected.push({
26257
+ key: keyForElement(el) ?? originKey,
26258
+ index,
26259
+ item: el,
26260
+ element: el
26261
+ });
26262
+ });
26263
+ const origin = selected.find((entry) => entry.key === originKey);
26264
+ if (!origin) return void 0;
26265
+ return {
26266
+ origin,
26267
+ selected
26268
+ };
26269
+ }
26270
+ _syncNativeDraggable() {
26271
+ if (!this.isConnected) return;
26272
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
26273
+ if (this.#wrappedElements.has(el)) continue;
26274
+ if (el instanceof HTMLElement) el.draggable = this.itemsDraggable && this._isCheckable(el);
26275
+ }
26276
+ }
26277
+ #startPostDragSuppressTimer() {
26278
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
26279
+ this.#postDragSuppressTimer = window.setTimeout(() => {
26280
+ this.#postDragSuppressTimer = 0;
26281
+ }, 0);
26282
+ }
25476
26283
  /** Live drag-select preview rect (surface-relative); the covered items carry `data-marquee`. */
25477
26284
  #marqueeRect;
25478
26285
  #dragSelect;
@@ -25540,6 +26347,10 @@ let VerticalListElement = class VerticalListElement extends i {
25540
26347
  this.#unregisterSearchCommands?.();
25541
26348
  this.#unregisterSearchCommands = void 0;
25542
26349
  this.#searchRegistry = void 0;
26350
+ if (this.#postDragSuppressTimer) {
26351
+ clearTimeout(this.#postDragSuppressTimer);
26352
+ this.#postDragSuppressTimer = 0;
26353
+ }
25543
26354
  }
25544
26355
  /**
25545
26356
  * Animate in items appended to the light DOM (however they got there —
@@ -25579,6 +26390,10 @@ let VerticalListElement = class VerticalListElement extends i {
25579
26390
  }
25580
26391
  this._syncItemStates();
25581
26392
  } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
26393
+ if (changed.has(`itemsDraggable`) || changed.has(`interactionMode`)) {
26394
+ this._syncNativeDraggable();
26395
+ this.#nativeDrag.refreshDraggable();
26396
+ }
25582
26397
  }
25583
26398
  /** The most recently selected item, or `undefined` if nothing is selected. */
25584
26399
  get selectedItem() {
@@ -25848,6 +26663,14 @@ let VerticalListElement = class VerticalListElement extends i {
25848
26663
  composed: true
25849
26664
  }));
25850
26665
  }
26666
+ _dispatchCancelable(name, detail) {
26667
+ return this.dispatchEvent(new CustomEvent(name, {
26668
+ detail,
26669
+ bubbles: true,
26670
+ composed: true,
26671
+ cancelable: true
26672
+ }));
26673
+ }
25851
26674
  _activateItem(item) {
25852
26675
  if (this.interactionMode !== `manual`) this.selection.replace(item);
25853
26676
  this._dispatch(`list-activate`, { item });
@@ -25983,6 +26806,10 @@ let VerticalListElement = class VerticalListElement extends i {
25983
26806
  color: var(--item-text-selected);
25984
26807
  }
25985
26808
 
26809
+ ::slotted([data-drag-origin]) {
26810
+ opacity: 0.6;
26811
+ }
26812
+
25986
26813
  /* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */
25987
26814
  :host([interaction-mode="checked"]) ::slotted([data-selected]) {
25988
26815
  background: transparent;
@@ -26029,6 +26856,12 @@ __decorate([property({
26029
26856
  attribute: `search-modes`,
26030
26857
  reflect: true
26031
26858
  })], VerticalListElement.prototype, "searchModes", void 0);
26859
+ __decorate([property({
26860
+ type: Boolean,
26861
+ attribute: `items-draggable`,
26862
+ reflect: true
26863
+ })], VerticalListElement.prototype, "itemsDraggable", void 0);
26864
+ __decorate([property({ attribute: false })], VerticalListElement.prototype, "dragPreviewRenderer", void 0);
26032
26865
  VerticalListElement = __decorate([safeCustomElement(`ixfx-vertical-list`)], VerticalListElement);
26033
26866
  //#endregion
26034
26867
  //#region src/grouped-item-lister/vertical-list-adapter.ts
@@ -42560,6 +43393,6 @@ function init() {
42560
43393
  console.log(`Init`);
42561
43394
  }
42562
43395
  //#endregion
42563
- export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createDataListSearchAdapter, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
43396
+ export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
42564
43397
 
42565
43398
  //# sourceMappingURL=index.js.map