@ixfx/components 0.8.7 → 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 (71) hide show
  1. package/bundle/index.d.ts +565 -395
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +993 -187
  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-CYVPSUwh.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-vH56z7WE.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-Cq8ga2WZ.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-DDREWqMm.d.ts → index-Dgrx-DIA.d.ts} +2 -2
  25. package/dist/{index-DDREWqMm.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-CozQj01a.js → tree-DSMn566I.js} +50 -14
  47. package/dist/tree-DSMn566I.js.map +1 -0
  48. package/dist/{tree-component-BMpxpdTi.d.ts → tree-component-nPP5Y-WS.d.ts} +2 -2
  49. package/dist/{tree-component-BMpxpdTi.d.ts.map → tree-component-nPP5Y-WS.d.ts.map} +1 -1
  50. package/dist/tree.d.ts +2 -2
  51. package/dist/tree.d.ts.map +1 -1
  52. package/dist/tree.js +1 -1
  53. package/dist/{vertical-list-BGn9nyKh.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/data-grid.md +30 -0
  59. package/docs-user/detail-list.md +29 -0
  60. package/docs-user/grid-list.md +34 -4
  61. package/docs-user/index.json +2 -2
  62. package/docs-user/tree.md +1 -1
  63. package/docs-user/vertical-list.md +30 -1
  64. package/package.json +1 -1
  65. package/dist/data-grid-CYVPSUwh.d.ts.map +0 -1
  66. package/dist/detail-list-vH56z7WE.d.ts.map +0 -1
  67. package/dist/grid-list-Cq8ga2WZ.d.ts.map +0 -1
  68. package/dist/grid-list-j5C7qtlG.js.map +0 -1
  69. package/dist/list-item-animation-DuiwgN3l.js.map +0 -1
  70. package/dist/tree-CozQj01a.js.map +0 -1
  71. package/dist/vertical-list-BGn9nyKh.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,25 +6432,239 @@ 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
+ });
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);
6095
6471
  }
6096
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
+ };
6667
+ //#endregion
6097
6668
  //#region src/interaction/list-selection-controller.ts
6098
6669
  /** Whether the interaction mode allows building a multi-item selection. */
6099
6670
  function isMultiMode(mode) {
@@ -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;
@@ -24068,8 +24739,18 @@ let TreeBaseElement = class TreeBaseElement extends i {
24068
24739
  #lastNodeByKey;
24069
24740
  #enteringKeys;
24070
24741
  #leaving;
24071
- #leavingAnimating;
24742
+ #leavingAnimations;
24072
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
+ }
24073
24754
  willUpdate(changedProps) {
24074
24755
  super.willUpdate(changedProps);
24075
24756
  this.#diffVisibleNodes();
@@ -24088,12 +24769,12 @@ let TreeBaseElement = class TreeBaseElement extends i {
24088
24769
  }
24089
24770
  }
24090
24771
  for (const key of [...this.#leaving.keys()]) if (nowKeys.has(key)) {
24772
+ this.#cancelLeavingAnimation(key);
24091
24773
  this.#leaving.delete(key);
24092
- this.#leavingAnimating.delete(key);
24093
24774
  }
24094
24775
  if (this.#leaving.size > MAX_ANIMATED_ROWS) {
24776
+ this.#cancelAllLeavingAnimations();
24095
24777
  this.#leaving.clear();
24096
- this.#leavingAnimating.clear();
24097
24778
  }
24098
24779
  }
24099
24780
  /** True while `node` is a fading ghost row (removed / collapsed, still animating out). */
@@ -24130,18 +24811,38 @@ let TreeBaseElement = class TreeBaseElement extends i {
24130
24811
  if (this.#enteringKeys.size > 0) {
24131
24812
  for (const el of this.renderRoot.querySelectorAll(`.item`)) {
24132
24813
  const key = el.dataset.key;
24133
- 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);
24134
24818
  }
24135
24819
  this.#enteringKeys.clear();
24136
24820
  }
24137
24821
  if (this.#leaving.size > 0) for (const el of this.renderRoot.querySelectorAll(`.item.leaving`)) {
24138
24822
  const key = el.dataset.key;
24139
- if (key === void 0 || this.#leavingAnimating.has(key)) continue;
24140
- this.#leavingAnimating.add(key);
24141
- 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);
24142
24839
  this.#leaving.delete(key);
24143
- this.#leavingAnimating.delete(key);
24144
24840
  this.requestUpdate();
24841
+ this.updateComplete.then(() => {
24842
+ try {
24843
+ animation.cancel();
24844
+ } catch {}
24845
+ });
24145
24846
  });
24146
24847
  }
24147
24848
  const visible = this.root ? this.#getAllNodes() : [];
@@ -24157,6 +24858,9 @@ let TreeBaseElement = class TreeBaseElement extends i {
24157
24858
  this.#clearSelectionInternal();
24158
24859
  this.tickled.clearTickled();
24159
24860
  this._focusedDepth = 0;
24861
+ this.#cancelAllLeavingAnimations();
24862
+ this.#leaving.clear();
24863
+ this.#enteringKeys.clear();
24160
24864
  }
24161
24865
  if (changedProps.has(`model`)) {
24162
24866
  this.#modelUnsubscribe?.();
@@ -24207,6 +24911,9 @@ let TreeBaseElement = class TreeBaseElement extends i {
24207
24911
  }
24208
24912
  disconnectedCallback() {
24209
24913
  super.disconnectedCallback();
24914
+ this.#cancelAllLeavingAnimations();
24915
+ this.#leaving.clear();
24916
+ this.#enteringKeys.clear();
24210
24917
  this.#modelUnsubscribe?.();
24211
24918
  this.#modelUnsubscribe = void 0;
24212
24919
  this.#unregisterSearchCommands?.();
@@ -24867,7 +25574,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
24867
25574
  --tree-text: var(--surface-3-text);
24868
25575
  --tree-muted-text: var(--surface-muted-text);
24869
25576
  --tree-indent: 1ch;
24870
- --tree-item-height: 2ch;
25577
+ --tree-item-height: 30px;
24871
25578
  --tree-caret-size: 30px;
24872
25579
  --tree-radius: var(--radius-s);
24873
25580
 
@@ -24900,7 +25607,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
24900
25607
  display: flex;
24901
25608
  align-items: center;
24902
25609
  height: var(--tree-item-height);
24903
- padding-left: calc(var(--tree-indent) * var(--depth, 0) + var(--space-s));
25610
+ padding-left: calc(var(--tree-indent) * var(--depth, 0));
24904
25611
  cursor: pointer;
24905
25612
  user-select: none;
24906
25613
  font-family: var(--font-family);
@@ -25371,6 +26078,7 @@ let VerticalListElement = class VerticalListElement extends i {
25371
26078
  this.selectionMode = `single`;
25372
26079
  this.interactionMode = `standard`;
25373
26080
  this.itemWrap = `stop`;
26081
+ this.itemsDraggable = false;
25374
26082
  this.#customWrappers = /* @__PURE__ */ new Map();
25375
26083
  this.#wrappedElements = /* @__PURE__ */ new Set();
25376
26084
  this.#animatedItems = /* @__PURE__ */ new WeakSet();
@@ -25401,7 +26109,41 @@ let VerticalListElement = class VerticalListElement extends i {
25401
26109
  },
25402
26110
  onStateChange: () => this._syncItemStates(),
25403
26111
  itemWrap: this.itemWrap,
25404
- 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()
25405
26147
  });
25406
26148
  this.#dragSelect = new ListDragSelectController(this, {
25407
26149
  getSurface: () => this.renderRoot.querySelector(`.container`),
@@ -25483,7 +26225,7 @@ let VerticalListElement = class VerticalListElement extends i {
25483
26225
  }
25484
26226
  };
25485
26227
  this._onClick = (e) => {
25486
- if (this.#dragSelect.isDragging) return;
26228
+ if (this.#dragSelect.isDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
25487
26229
  const item = this._findListItem(e.composedPath());
25488
26230
  if (!item) return;
25489
26231
  this._dispatch(`list-item-click`, { item });
@@ -25500,6 +26242,44 @@ let VerticalListElement = class VerticalListElement extends i {
25500
26242
  #itemObserver;
25501
26243
  /** Items that have already played (or been skipped for) their enter animation. */
25502
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
+ }
25503
26283
  /** Live drag-select preview rect (surface-relative); the covered items carry `data-marquee`. */
25504
26284
  #marqueeRect;
25505
26285
  #dragSelect;
@@ -25567,6 +26347,10 @@ let VerticalListElement = class VerticalListElement extends i {
25567
26347
  this.#unregisterSearchCommands?.();
25568
26348
  this.#unregisterSearchCommands = void 0;
25569
26349
  this.#searchRegistry = void 0;
26350
+ if (this.#postDragSuppressTimer) {
26351
+ clearTimeout(this.#postDragSuppressTimer);
26352
+ this.#postDragSuppressTimer = 0;
26353
+ }
25570
26354
  }
25571
26355
  /**
25572
26356
  * Animate in items appended to the light DOM (however they got there —
@@ -25606,6 +26390,10 @@ let VerticalListElement = class VerticalListElement extends i {
25606
26390
  }
25607
26391
  this._syncItemStates();
25608
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
+ }
25609
26397
  }
25610
26398
  /** The most recently selected item, or `undefined` if nothing is selected. */
25611
26399
  get selectedItem() {
@@ -25875,6 +26663,14 @@ let VerticalListElement = class VerticalListElement extends i {
25875
26663
  composed: true
25876
26664
  }));
25877
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
+ }
25878
26674
  _activateItem(item) {
25879
26675
  if (this.interactionMode !== `manual`) this.selection.replace(item);
25880
26676
  this._dispatch(`list-activate`, { item });
@@ -26010,6 +26806,10 @@ let VerticalListElement = class VerticalListElement extends i {
26010
26806
  color: var(--item-text-selected);
26011
26807
  }
26012
26808
 
26809
+ ::slotted([data-drag-origin]) {
26810
+ opacity: 0.6;
26811
+ }
26812
+
26013
26813
  /* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */
26014
26814
  :host([interaction-mode="checked"]) ::slotted([data-selected]) {
26015
26815
  background: transparent;
@@ -26056,6 +26856,12 @@ __decorate([property({
26056
26856
  attribute: `search-modes`,
26057
26857
  reflect: true
26058
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);
26059
26865
  VerticalListElement = __decorate([safeCustomElement(`ixfx-vertical-list`)], VerticalListElement);
26060
26866
  //#endregion
26061
26867
  //#region src/grouped-item-lister/vertical-list-adapter.ts
@@ -42587,6 +43393,6 @@ function init() {
42587
43393
  console.log(`Init`);
42588
43394
  }
42589
43395
  //#endregion
42590
- 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 };
42591
43397
 
42592
43398
  //# sourceMappingURL=index.js.map