@ixfx/components 0.8.7 → 0.8.9

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 (85) hide show
  1. package/bundle/index.d.ts +743 -383
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2093 -222
  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-C588pTgt.d.ts} +15 -2
  8. package/dist/data-grid-C588pTgt.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-IKl76Wnp.d.ts} +22 -5
  13. package/dist/detail-list-IKl76Wnp.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-Cq8ga2WZ.d.ts → grid-list-BUlUnisU.d.ts} +214 -112
  18. package/dist/grid-list-BUlUnisU.d.ts.map +1 -0
  19. package/dist/{grid-list-j5C7qtlG.js → grid-list-D18FNJRH.js} +77 -164
  20. package/dist/grid-list-D18FNJRH.js.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--qnhBWZJ.d.ts} +2 -2
  25. package/dist/{index-DDREWqMm.d.ts.map → index--qnhBWZJ.d.ts.map} +1 -1
  26. package/dist/index.d.ts +212 -253
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +13 -12
  29. package/dist/index.js.map +1 -1
  30. package/dist/{interaction-B9AAlsFO.js → interaction-BskmlA9t.js} +1 -1
  31. package/dist/{labelled-slider-input-BCH9968c.js → labelled-slider-input-aEYDvW2S.js} +2 -2
  32. package/dist/{labelled-slider-input-BCH9968c.js.map → labelled-slider-input-aEYDvW2S.js.map} +1 -1
  33. package/dist/labelled-slider-input.js +1 -1
  34. package/dist/{list-item-animation-DuiwgN3l.js → list-item-animation-BUoEKi5s.js} +29 -10
  35. package/dist/list-item-animation-BUoEKi5s.js.map +1 -0
  36. package/dist/list-native-drag-controller-CFyrkKUj.d.ts +61 -0
  37. package/dist/list-native-drag-controller-CFyrkKUj.d.ts.map +1 -0
  38. package/dist/list-native-drag-controller-DxD6Yjci.js +567 -0
  39. package/dist/list-native-drag-controller-DxD6Yjci.js.map +1 -0
  40. package/dist/{menu-HMoxCPY2.js → menu-CMXhIaNZ.js} +3 -3
  41. package/dist/{menu-HMoxCPY2.js.map → menu-CMXhIaNZ.js.map} +1 -1
  42. package/dist/{menu-item-BQDYxk5A.js → menu-item-BYGQzyCe.js} +2 -2
  43. package/dist/{menu-item-BQDYxk5A.js.map → menu-item-BYGQzyCe.js.map} +1 -1
  44. package/dist/menu.js +2 -2
  45. package/dist/miller.d.ts +2 -2
  46. package/dist/miller.js +1 -1
  47. package/dist/narrowed-text.js +1 -1
  48. package/dist/plots.js +1 -1
  49. package/dist/select-horiz.js +1 -1
  50. package/dist/slider-input-BxURAAmD.d.ts.map +1 -1
  51. package/dist/{slider-input-WR8_RwHg.js → slider-input-Cb5uSV42.js} +78 -11
  52. package/dist/slider-input-Cb5uSV42.js.map +1 -0
  53. package/dist/slider-input.js +1 -1
  54. package/dist/{tree-CozQj01a.js → tree-a14n9y7i.js} +1055 -32
  55. package/dist/tree-a14n9y7i.js.map +1 -0
  56. package/dist/{tree-component-BMpxpdTi.d.ts → tree-component-BW2iCFq9.d.ts} +108 -4
  57. package/dist/tree-component-BW2iCFq9.d.ts.map +1 -0
  58. package/dist/tree.d.ts +102 -4
  59. package/dist/tree.d.ts.map +1 -1
  60. package/dist/tree.js +2 -2
  61. package/dist/{vertical-list-BGn9nyKh.d.ts → vertical-list-CHrXmmQ3.d.ts} +21 -4
  62. package/dist/vertical-list-CHrXmmQ3.d.ts.map +1 -0
  63. package/dist/vertical-list.d.ts +3 -2
  64. package/dist/vertical-list.js +103 -3
  65. package/dist/vertical-list.js.map +1 -1
  66. package/dist/{xy-axis-CrrVdDFe.js → xy-axis-COKw338Q.js} +2 -2
  67. package/dist/xy-axis-COKw338Q.js.map +1 -0
  68. package/dist/xy-axis-DQqd2yXJ.d.ts.map +1 -1
  69. package/docs-user/data-grid.md +30 -0
  70. package/docs-user/detail-list.md +29 -0
  71. package/docs-user/grid-list.md +34 -4
  72. package/docs-user/index.json +2 -2
  73. package/docs-user/tree.md +76 -1
  74. package/docs-user/vertical-list.md +30 -1
  75. package/package.json +1 -1
  76. package/dist/data-grid-CYVPSUwh.d.ts.map +0 -1
  77. package/dist/detail-list-vH56z7WE.d.ts.map +0 -1
  78. package/dist/grid-list-Cq8ga2WZ.d.ts.map +0 -1
  79. package/dist/grid-list-j5C7qtlG.js.map +0 -1
  80. package/dist/list-item-animation-DuiwgN3l.js.map +0 -1
  81. package/dist/slider-input-WR8_RwHg.js.map +0 -1
  82. package/dist/tree-CozQj01a.js.map +0 -1
  83. package/dist/tree-component-BMpxpdTi.d.ts.map +0 -1
  84. package/dist/vertical-list-BGn9nyKh.d.ts.map +0 -1
  85. package/dist/xy-axis-CrrVdDFe.js.map +0 -1
package/bundle/index.js CHANGED
@@ -5686,13 +5686,370 @@ __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;
5692
6049
  /** Pointer distance (px) from an edge that triggers auto-scroll. */
5693
6050
  const EDGE_ZONE = 28;
5694
6051
  /** Max auto-scroll speed in px per frame. */
5695
- const MAX_SCROLL_SPEED = 16;
6052
+ const MAX_SCROLL_SPEED$1 = 16;
5696
6053
  /**
5697
6054
  * Shared pointer-drag (marquee / rubber-band) selection for list components.
5698
6055
  *
@@ -5958,10 +6315,10 @@ var ListDragSelectController = class {
5958
6315
  const py = this.#currentClientY - box.top;
5959
6316
  let dx = 0;
5960
6317
  let dy = 0;
5961
- if (px < EDGE_ZONE) dx = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, px) / EDGE_ZONE));
5962
- else if (px > box.width - EDGE_ZONE) dx = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, box.width - px) / EDGE_ZONE));
5963
- if (py < EDGE_ZONE) dy = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, py) / EDGE_ZONE));
5964
- else if (py > box.height - EDGE_ZONE) dy = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, box.height - py) / EDGE_ZONE));
6318
+ if (px < EDGE_ZONE) dx = -Math.ceil(MAX_SCROLL_SPEED$1 * (1 - Math.max(0, px) / EDGE_ZONE));
6319
+ else if (px > box.width - EDGE_ZONE) dx = Math.ceil(MAX_SCROLL_SPEED$1 * (1 - Math.max(0, box.width - px) / EDGE_ZONE));
6320
+ if (py < EDGE_ZONE) dy = -Math.ceil(MAX_SCROLL_SPEED$1 * (1 - Math.max(0, py) / EDGE_ZONE));
6321
+ else if (py > box.height - EDGE_ZONE) dy = Math.ceil(MAX_SCROLL_SPEED$1 * (1 - Math.max(0, box.height - py) / EDGE_ZONE));
5965
6322
  if (dx === 0 && dy === 0) return;
5966
6323
  const autoScroll = this.#config.autoScroll;
5967
6324
  if (!autoScroll) return;
@@ -6075,25 +6432,251 @@ 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
+ /** `renderRoot` does not exist yet at `hostConnected` for a shadow-DOM host — rebind once it does. */
6525
+ hostUpdated() {
6526
+ this.#bind();
6527
+ }
6528
+ hostDisconnected() {
6529
+ this.#unbind();
6530
+ try {
6531
+ this.#observer?.disconnect();
6532
+ } catch {}
6533
+ this.#dragging = false;
6534
+ }
6535
+ #renderRoot() {
6536
+ return this.#host.renderRoot ?? this.#host;
6537
+ }
6538
+ #bind() {
6539
+ const root = this.#renderRoot();
6540
+ if (root !== this.#boundRoot) {
6541
+ this.#unbind();
6542
+ this.#boundRoot = root;
6543
+ root.addEventListener(`dragstart`, this.#onDragStart, { capture: true });
6544
+ root.addEventListener(`dragend`, this.#onDragEnd, { capture: true });
6545
+ }
6546
+ if (!this.#boundHost) {
6547
+ this.#boundHost = true;
6548
+ this.#host.addEventListener(`dragstart`, this.#onDragStart, { capture: true });
6549
+ this.#host.addEventListener(`dragend`, this.#onDragEnd, { capture: true });
6550
+ }
6551
+ }
6552
+ #unbind() {
6553
+ this.#boundRoot?.removeEventListener(`dragstart`, this.#onDragStart, { capture: true });
6554
+ this.#boundRoot?.removeEventListener(`dragend`, this.#onDragEnd, { capture: true });
6555
+ this.#boundRoot = void 0;
6556
+ if (this.#boundHost) {
6557
+ this.#boundHost = false;
6558
+ this.#host.removeEventListener(`dragstart`, this.#onDragStart, { capture: true });
6559
+ this.#host.removeEventListener(`dragend`, this.#onDragEnd, { capture: true });
6560
+ }
6561
+ }
6562
+ #onDragStart = (event) => {
6563
+ if (!(event instanceof DragEvent)) return;
6564
+ if (this.#seenDragStarts.has(event)) return;
6565
+ let enabled = false;
6566
+ try {
6567
+ enabled = this.#config.isEnabled();
6568
+ } catch {
6569
+ return;
6570
+ }
6571
+ if (!enabled) return;
6572
+ let originKey;
6573
+ try {
6574
+ originKey = this.#config.resolveEventToKey(event.target);
6575
+ } catch {
6576
+ return;
6577
+ }
6578
+ if (originKey) this.#seenDragStarts.add(event);
6579
+ if (!originKey) return;
6580
+ let prepared;
6581
+ try {
6582
+ prepared = this.#config.prepareSelection(originKey);
6583
+ } catch {
6584
+ return;
6585
+ }
6586
+ if (!prepared || prepared.selected.length === 0) return;
6587
+ const { origin, selected } = prepared;
6588
+ let hostStyle;
6589
+ try {
6590
+ hostStyle = getComputedStyle(this.#host);
6591
+ } catch {
6592
+ hostStyle = document.createElement(`div`).style;
6593
+ }
6594
+ let metrics = {};
6595
+ try {
6596
+ metrics = this.#config.getMetrics() ?? {};
6597
+ } catch {
6598
+ metrics = {};
6599
+ }
6600
+ const context = {
6601
+ source: this.#config.source,
6602
+ origin,
6603
+ selected,
6604
+ metrics,
6605
+ hostStyle
6606
+ };
6607
+ let renderer;
6608
+ try {
6609
+ renderer = this.#config.getRenderer();
6610
+ } catch {
6611
+ renderer = void 0;
6612
+ }
6613
+ let preview;
6614
+ try {
6615
+ preview = renderer ? renderer(context) : createDefaultListDragPreview(context);
6616
+ } catch {
6617
+ preview = createDefaultListDragPreview(context);
6618
+ }
6619
+ try {
6620
+ document.body.appendChild(preview.element);
6621
+ } catch {}
6622
+ const detail = {
6623
+ key: origin.key,
6624
+ index: origin.index,
6625
+ item: origin.item,
6626
+ selectedKeys: selected.map((entry) => entry.key),
6627
+ selected,
6628
+ preview
6629
+ };
6630
+ let notCancelled = true;
6631
+ try {
6632
+ notCancelled = this.#config.dispatch(detail);
6633
+ } catch {
6634
+ notCancelled = true;
6635
+ }
6636
+ if (notCancelled) {
6637
+ if (event.dataTransfer) {
6638
+ try {
6639
+ if (event.dataTransfer.effectAllowed === `none` || event.dataTransfer.effectAllowed === `uninitialized`) event.dataTransfer.effectAllowed = `copyMove`;
6640
+ } catch {}
6641
+ try {
6642
+ this.#config.writeDataTransfer?.(event.dataTransfer, detail);
6643
+ } catch {}
6644
+ }
6645
+ const hotspot = preview.hotspot ?? {
6646
+ x: 16,
6647
+ y: 16
6648
+ };
6649
+ try {
6650
+ event.dataTransfer?.setDragImage(preview.element, hotspot.x, hotspot.y);
6651
+ } catch {}
6652
+ } else try {
6653
+ event.preventDefault();
6654
+ } catch {}
6655
+ this.#dragging = true;
6656
+ try {
6657
+ this.#config.onActiveChange(true, origin.key);
6658
+ } catch {}
6659
+ requestAnimationFrame(() => {
6660
+ try {
6661
+ preview.element.remove();
6662
+ } catch {}
6663
+ try {
6664
+ preview.dispose?.();
6665
+ } catch {}
6666
+ });
6667
+ };
6668
+ #onDragEnd = () => {
6669
+ if (!this.#dragging) return;
6670
+ this.#dragging = false;
6671
+ try {
6672
+ this.#config.onActiveChange(false);
6673
+ } catch {}
6674
+ try {
6675
+ this.#config.onEnd();
6676
+ } catch {}
6677
+ };
6678
+ };
6679
+ //#endregion
6097
6680
  //#region src/interaction/list-selection-controller.ts
6098
6681
  /** Whether the interaction mode allows building a multi-item selection. */
6099
6682
  function isMultiMode(mode) {
@@ -9165,15 +9748,138 @@ async function collectDataResult(result, signal) {
9165
9748
  return result;
9166
9749
  }
9167
9750
  //#endregion
9168
- //#region src/common/tree-data-model.ts
9751
+ //#region src/common/tree-drop-apply.ts
9169
9752
  function findNode$1(root, key) {
9170
9753
  if (root.item.key === key) return root;
9171
- if (!root.children) return void 0;
9172
- for (const child of root.children) {
9754
+ for (const child of root.children ?? []) {
9173
9755
  const found = findNode$1(child, key);
9174
9756
  if (found) return found;
9175
9757
  }
9176
9758
  }
9759
+ function findParent(root, key, parent = void 0) {
9760
+ for (const child of root.children ?? []) {
9761
+ if (child.item.key === key) return parent === void 0 ? root : parent;
9762
+ const found = findParent(child, key, child);
9763
+ if (found) return found;
9764
+ }
9765
+ }
9766
+ /** True when `key` is `ancestorKey`, or a descendant of it. */
9767
+ function isSelfOrDescendant(root, ancestorKey, key) {
9768
+ const ancestor = findNode$1(root, ancestorKey);
9769
+ return ancestor !== void 0 && findNode$1(ancestor, key) !== void 0;
9770
+ }
9771
+ /** Remove every node whose key is in `keys` (with its whole subtree), in one pass. */
9772
+ function removeKeys(node, keys) {
9773
+ if (!node.children) return node;
9774
+ return {
9775
+ ...node,
9776
+ children: node.children.filter((c) => !keys.has(c.item.key)).map((c) => removeKeys(c, keys))
9777
+ };
9778
+ }
9779
+ function childrenOf(root, parentKey) {
9780
+ if (parentKey === void 0) return root.children ?? [];
9781
+ return findNode$1(root, parentKey)?.children ?? [];
9782
+ }
9783
+ /** Return a new root with `parentKey`'s children replaced (root itself when `parentKey` is undefined). */
9784
+ function withChildren(node, parentKey, children) {
9785
+ if (parentKey === void 0) return {
9786
+ ...node,
9787
+ children
9788
+ };
9789
+ if (node.item.key === parentKey) return {
9790
+ ...node,
9791
+ children
9792
+ };
9793
+ if (!node.children) return node;
9794
+ return {
9795
+ ...node,
9796
+ children: node.children.map((c) => withChildren(c, parentKey, children))
9797
+ };
9798
+ }
9799
+ function clamp$5(n, min, max) {
9800
+ return Math.max(min, Math.min(max, n));
9801
+ }
9802
+ /**
9803
+ * Drop keys whose ancestor is also being dragged — only the top-most selected
9804
+ * nodes move; their subtrees travel with them. Preserves input order.
9805
+ */
9806
+ function pruneDraggedDescendants(root, keys) {
9807
+ const set = new Set(keys);
9808
+ return keys.filter((key) => {
9809
+ let parent = findParent(root, key);
9810
+ while (parent) {
9811
+ if (set.has(parent.item.key)) return false;
9812
+ parent = findParent(root, parent.item.key);
9813
+ }
9814
+ return true;
9815
+ });
9816
+ }
9817
+ /**
9818
+ * Framework-agnostic drop application. Given the current immutable `root` and a
9819
+ * resolved drop context, returns a **new** root with the drop applied, or the
9820
+ * unchanged `root` when the drop is invalid (dragged node into its own subtree,
9821
+ * missing reference, empty payload). Never throws, never mutates.
9822
+ *
9823
+ * The insertion point is re-derived from `target.referenceKey` + `target.position`
9824
+ * against the post-removal tree, so same-parent reorders are off-by-one-safe and
9825
+ * multi-node drops land as siblings in drag order. `target.index` is honoured only
9826
+ * for `position: 'inside'` (0 = first child, large = append).
9827
+ */
9828
+ function applyTreeDrop(root, ctx) {
9829
+ const { target, source } = ctx;
9830
+ let nodes;
9831
+ let removeSet = /* @__PURE__ */ new Set();
9832
+ if (source === `internal`) {
9833
+ const keys = pruneDraggedDescendants(root, [...ctx.draggedKeys]);
9834
+ if (keys.length === 0) return root;
9835
+ if (target.parentKey !== void 0) {
9836
+ for (const key of keys) if (isSelfOrDescendant(root, key, target.parentKey)) return root;
9837
+ }
9838
+ if (target.position !== `inside`) {
9839
+ for (const key of keys) if (isSelfOrDescendant(root, key, target.referenceKey)) return root;
9840
+ }
9841
+ const resolved = keys.map((k) => findNode$1(root, k));
9842
+ if (resolved.includes(void 0)) return root;
9843
+ nodes = resolved;
9844
+ removeSet = new Set(keys);
9845
+ } else {
9846
+ nodes = (ctx.dragged ?? []).map((n) => ({ ...n }));
9847
+ if (nodes.length === 0) return root;
9848
+ }
9849
+ const afterRemoval = removeSet.size > 0 ? removeKeys(root, removeSet) : root;
9850
+ let parentKey;
9851
+ let index;
9852
+ if (target.position === `inside`) {
9853
+ if (findNode$1(afterRemoval, target.referenceKey) === void 0) return root;
9854
+ parentKey = target.referenceKey;
9855
+ const kids = childrenOf(afterRemoval, parentKey);
9856
+ index = clamp$5(target.index, 0, kids.length);
9857
+ } else {
9858
+ parentKey = findParent(afterRemoval, target.referenceKey)?.item.key;
9859
+ const refIdx = childrenOf(afterRemoval, parentKey).findIndex((c) => c.item.key === target.referenceKey);
9860
+ if (refIdx < 0) return root;
9861
+ index = target.position === `before` ? refIdx : refIdx + 1;
9862
+ }
9863
+ const kids = [...childrenOf(afterRemoval, parentKey)];
9864
+ const next = [
9865
+ ...kids.slice(0, index),
9866
+ ...nodes,
9867
+ ...kids.slice(index)
9868
+ ];
9869
+ return withChildren(afterRemoval, parentKey, next);
9870
+ }
9871
+ /** Build a synthetic `TreeDropTarget` for programmatic moves (see `TreeDataModel`). */
9872
+ function dropTargetFor(referenceKey, position, index = position === `inside` ? Number.MAX_SAFE_INTEGER : 0) {
9873
+ return {
9874
+ referenceKey,
9875
+ position,
9876
+ index,
9877
+ parentKey: position === `inside` ? referenceKey : void 0,
9878
+ depth: 0
9879
+ };
9880
+ }
9881
+ //#endregion
9882
+ //#region src/common/tree-data-model.ts
9177
9883
  /** Return a new root with the node at `key` replaced by `replacer(node)`. */
9178
9884
  function mapNode(root, key, replacer) {
9179
9885
  if (root.item.key === key) return replacer(root);
@@ -9198,11 +9904,23 @@ function removeNodeFrom(root, key) {
9198
9904
  children: root.children.filter((c) => c.item.key !== key).map((c) => removeNodeFrom(c, key))
9199
9905
  };
9200
9906
  }
9201
- /** Return a new root with the node at `key` moved to become a child of `newParentKey`. */
9202
- function moveNodeTo(root, key, newParentKey) {
9203
- const target = findNode$1(root, key);
9204
- if (!target) return root;
9205
- return addChildTo(removeNodeFrom(root, key), newParentKey, target);
9907
+ /** Return a new root with `newChild` inserted at `index` among the children of `parentKey`. */
9908
+ function insertChildAt(root, parentKey, newChild, index) {
9909
+ return mapNode(root, parentKey, (parent) => {
9910
+ const children = parent.children ?? [];
9911
+ const clampedIndex = Math.max(0, Math.min(index, children.length));
9912
+ return {
9913
+ item: parent.item,
9914
+ children: [
9915
+ ...children.slice(0, clampedIndex),
9916
+ newChild,
9917
+ ...children.slice(clampedIndex)
9918
+ ]
9919
+ };
9920
+ });
9921
+ }
9922
+ function getRootChildren(root) {
9923
+ return root.children ?? [];
9206
9924
  }
9207
9925
  /**
9208
9926
  * Manages tree data for tree-like components.
@@ -9330,10 +10048,80 @@ var TreeDataModel = class {
9330
10048
  }
9331
10049
  /**
9332
10050
  * Moves the node with the given key to become a child of `newParentKey`.
10051
+ * When `index` is provided, inserts at that position; otherwise appends.
10052
+ * Returns the new root (connected components are notified automatically).
10053
+ */
10054
+ moveNode(key, newParentKey, index) {
10055
+ const next = applyTreeDrop(this.#root, {
10056
+ source: `internal`,
10057
+ draggedKeys: [key],
10058
+ target: dropTargetFor(newParentKey, `inside`, index ?? Number.MAX_SAFE_INTEGER)
10059
+ });
10060
+ if (next !== this.#root) {
10061
+ this.#root = next;
10062
+ this.#notify({
10063
+ type: `root`,
10064
+ root: this.#root
10065
+ });
10066
+ }
10067
+ return this.#root;
10068
+ }
10069
+ /**
10070
+ * Moves the node with the given key to become a sibling of `referenceKey`,
10071
+ * positioned `before` or `after` it. A no-op (returns the unchanged root) when
10072
+ * `key` is `referenceKey`, an ancestor of it, or either is missing.
10073
+ * Returns the new root (connected components are notified automatically).
10074
+ */
10075
+ moveNodeRelative(key, referenceKey, position) {
10076
+ const next = applyTreeDrop(this.#root, {
10077
+ source: `internal`,
10078
+ draggedKeys: [key],
10079
+ target: dropTargetFor(referenceKey, position)
10080
+ });
10081
+ if (next !== this.#root) {
10082
+ this.#root = next;
10083
+ this.#notify({
10084
+ type: `root`,
10085
+ root: this.#root
10086
+ });
10087
+ }
10088
+ return this.#root;
10089
+ }
10090
+ /**
10091
+ * Applies a resolved drop (from `ixfx-tree-list`'s `tree-drop` event or a
10092
+ * hand-built context) to the current root. No-op when the drop is invalid.
10093
+ * Returns the new root (connected components are notified automatically).
10094
+ */
10095
+ applyDrop(ctx) {
10096
+ const next = applyTreeDrop(this.#root, ctx);
10097
+ if (next !== this.#root) {
10098
+ this.#root = next;
10099
+ this.#notify({
10100
+ type: `root`,
10101
+ root: this.#root
10102
+ });
10103
+ }
10104
+ return this.#root;
10105
+ }
10106
+ /**
10107
+ * Inserts `node` as a child of `parentKey` (or at the root level when `parentKey` is undefined).
10108
+ * When `index` is provided, inserts at that position; otherwise appends.
9333
10109
  * Returns the new root (connected components are notified automatically).
9334
10110
  */
9335
- moveNode(key, newParentKey) {
9336
- this.#root = moveNodeTo(this.#root, key, newParentKey);
10111
+ insertNode(parentKey, node, index) {
10112
+ if (parentKey === void 0) {
10113
+ const children = getRootChildren(this.#root);
10114
+ const clampedIndex = Math.max(0, Math.min(index ?? children.length, children.length));
10115
+ this.#root = {
10116
+ ...this.#root,
10117
+ children: [
10118
+ ...children.slice(0, clampedIndex),
10119
+ node,
10120
+ ...children.slice(clampedIndex)
10121
+ ]
10122
+ };
10123
+ } else if (index !== void 0) this.#root = insertChildAt(this.#root, parentKey, node, index);
10124
+ else this.#root = addChildTo(this.#root, parentKey, node);
9337
10125
  this.#notify({
9338
10126
  type: `root`,
9339
10127
  root: this.#root
@@ -13115,6 +13903,7 @@ let DataGridElement = class DataGridElement extends i {
13115
13903
  this.allowGrouping = false;
13116
13904
  this.crossGroupSelection = false;
13117
13905
  this.columnMenu = `auto`;
13906
+ this.itemsDraggable = false;
13118
13907
  this._columnFormatters = /* @__PURE__ */ new Map();
13119
13908
  this._selectedKeys = /* @__PURE__ */ new Set();
13120
13909
  this._scrollTop = 0;
@@ -13132,7 +13921,45 @@ let DataGridElement = class DataGridElement extends i {
13132
13921
  id: k,
13133
13922
  enabled: true
13134
13923
  })),
13135
- onStateChange: () => this.requestUpdate()
13924
+ onStateChange: () => this.requestUpdate(),
13925
+ hoverDisabled: () => this.#nativeDrag.isDragging
13926
+ });
13927
+ this.#nativeDragging = false;
13928
+ this.#postDragSuppressTimer = 0;
13929
+ this.#nativeDrag = new ListNativeDragController(this, {
13930
+ source: `data-grid`,
13931
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this._flat.some((f) => f.type === `row`),
13932
+ resolveEventToKey: (target) => {
13933
+ if (!(target instanceof Element)) return void 0;
13934
+ if (target.closest?.(`.header-cell`)) return void 0;
13935
+ return (target.closest?.(`.row`))?.dataset.key;
13936
+ },
13937
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
13938
+ getMetrics: () => ({ rowHeight: this.rowHeight }),
13939
+ getRenderer: () => this.dragPreviewRenderer,
13940
+ dispatch: (detail) => this.dispatchEvent(new CustomEvent(`list-native-drag-start`, {
13941
+ detail,
13942
+ bubbles: true,
13943
+ composed: true,
13944
+ cancelable: true
13945
+ })),
13946
+ onActiveChange: (active, originKey) => {
13947
+ this.#nativeDragging = active;
13948
+ this.#dragOriginKey = active ? originKey : void 0;
13949
+ this.toggleAttribute(`data-native-dragging`, active);
13950
+ if (active) {
13951
+ const count = this._selectedKeys.size;
13952
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
13953
+ } else this.removeAttribute(`aria-label`);
13954
+ this.requestUpdate();
13955
+ },
13956
+ onEnd: () => {
13957
+ this.#nativeDragging = false;
13958
+ this.#dragOriginKey = void 0;
13959
+ this.toggleAttribute(`data-native-dragging`, false);
13960
+ this.removeAttribute(`aria-label`);
13961
+ this.#startPostDragSuppressTimer();
13962
+ }
13136
13963
  });
13137
13964
  this._sortedRows = [];
13138
13965
  this._flat = [];
@@ -13404,6 +14231,7 @@ let DataGridElement = class DataGridElement extends i {
13404
14231
  .row.item.tickled { background: var(--item-bg-tickled); border: var(--item-border-tickled); color: var(--item-text-tickled); }
13405
14232
  .row.item.selected { background: var(--item-bg-selected); border: var(--item-border-selected); color: var(--item-text-selected); }
13406
14233
  .row.item.selected.tickled { background: var(--item-bg-selected-tickled); }
14234
+ .row.item.dragging-origin { opacity: 0.6; }
13407
14235
 
13408
14236
  /* Insertion / removal animation. Only while [reflowing] — the ~0.3s after
13409
14237
  the row set changes — do rows slide (their top transitions) to make or
@@ -13449,6 +14277,43 @@ let DataGridElement = class DataGridElement extends i {
13449
14277
  `
13450
14278
  ];
13451
14279
  }
14280
+ #nativeDragging;
14281
+ #dragOriginKey;
14282
+ #postDragSuppressTimer;
14283
+ #nativeDrag;
14284
+ #prepareNativeDrag(originKey) {
14285
+ const rows = this._flat.filter((f) => f.type === `row`);
14286
+ if (!rows.some((f) => f.key === originKey)) return void 0;
14287
+ if (!this._selectedKeys.has(originKey)) this._replaceSelection(originKey);
14288
+ const selected = [];
14289
+ let displayIndex = 0;
14290
+ for (const entry of rows) {
14291
+ if (!this._selectedKeys.has(entry.key)) {
14292
+ displayIndex++;
14293
+ continue;
14294
+ }
14295
+ const rowEl = this.renderRoot.querySelector(`.row[data-key="${CSS.escape(entry.key)}"]`) ?? void 0;
14296
+ selected.push({
14297
+ key: entry.key,
14298
+ index: displayIndex,
14299
+ item: entry.row,
14300
+ ...rowEl ? { element: rowEl } : {}
14301
+ });
14302
+ displayIndex++;
14303
+ }
14304
+ const origin = selected.find((entry) => entry.key === originKey);
14305
+ if (!origin) return void 0;
14306
+ return {
14307
+ origin,
14308
+ selected
14309
+ };
14310
+ }
14311
+ #startPostDragSuppressTimer() {
14312
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
14313
+ this.#postDragSuppressTimer = window.setTimeout(() => {
14314
+ this.#postDragSuppressTimer = 0;
14315
+ }, 0);
14316
+ }
13452
14317
  #searchRegistry;
13453
14318
  #unregisterSearchCommands;
13454
14319
  #searchKeyByRow;
@@ -13525,6 +14390,10 @@ let DataGridElement = class DataGridElement extends i {
13525
14390
  clearTimeout(this.#rowReflowTimer);
13526
14391
  this.#rowReflowTimer = 0;
13527
14392
  }
14393
+ if (this.#postDragSuppressTimer) {
14394
+ clearTimeout(this.#postDragSuppressTimer);
14395
+ this.#postDragSuppressTimer = 0;
14396
+ }
13528
14397
  }
13529
14398
  /**
13530
14399
  * Dispatch a command to this grid — routes through the command registry so
@@ -13707,6 +14576,7 @@ let DataGridElement = class DataGridElement extends i {
13707
14576
  }));
13708
14577
  }
13709
14578
  _onRowClick(rowKey, col, e) {
14579
+ if (this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
13710
14580
  if (col && col.selectable === false) return;
13711
14581
  if (this.selectionMode === `none` && this.interactionMode !== `checked` && this.interactionMode !== `sticky`) return;
13712
14582
  if (this.interactionMode === `manual`) return;
@@ -14366,8 +15236,9 @@ let DataGridElement = class DataGridElement extends i {
14366
15236
  const selected = this._selectedKeys.has(rowKey);
14367
15237
  const tickled = this._tickled.isTickled(rowKey);
14368
15238
  return b`
14369
- <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
15239
+ <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
15240
  data-key=${rowKey}
15241
+ draggable=${this.#nativeDrag.draggableAttr}
14371
15242
  @click=${(e) => {
14372
15243
  this._onRowClick(rowKey, null, e);
14373
15244
  this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
@@ -14550,6 +15421,12 @@ __decorate([property({
14550
15421
  attribute: `search-modes`,
14551
15422
  reflect: true
14552
15423
  })], DataGridElement.prototype, "searchModes", void 0);
15424
+ __decorate([property({
15425
+ type: Boolean,
15426
+ attribute: `items-draggable`,
15427
+ reflect: true
15428
+ })], DataGridElement.prototype, "itemsDraggable", void 0);
15429
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "dragPreviewRenderer", void 0);
14553
15430
  __decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
14554
15431
  __decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
14555
15432
  __decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
@@ -14584,6 +15461,7 @@ let DataTableElement = class DataTableElement extends i {
14584
15461
  this.styles = [
14585
15462
  tickledStyles,
14586
15463
  tickledItemStyles,
15464
+ popupSurfaceStyles,
14587
15465
  i$3`
14588
15466
  :host {
14589
15467
  display: block;
@@ -14723,7 +15601,6 @@ let DataTableElement = class DataTableElement extends i {
14723
15601
  width: max-content;
14724
15602
  max-width: 100cqi;
14725
15603
  padding: var(--space-xs);
14726
- background: var(--surface-2);
14727
15604
  visibility: hidden;
14728
15605
  transform: translateY(-4px);
14729
15606
  pointer-events: none;
@@ -14802,7 +15679,7 @@ let DataTableElement = class DataTableElement extends i {
14802
15679
  ?data-full=${r.fullWidth}
14803
15680
  @pointerenter=${() => this.#handlePointerEnter(i)}
14804
15681
  @click=${() => this.#select(i)}
14805
- ><div class="key">${r.label}</div><div class="value"><span class="value-text">${typeof r.value === `boolean` ? r.value ? `Yes` : `No` : r.value}</span>${r.detail === void 0 ? A : b`<div class="detail" part="detail">${r.detail}</div>`}</div></div>`)}</div>`;
15682
+ ><div class="key">${r.label}</div><div class="value"><span class="value-text">${typeof r.value === `boolean` ? r.value ? `Yes` : `No` : r.value}</span>${r.detail === void 0 ? A : b`<div class="detail popup-surface" part="detail">${r.detail}</div>`}</div></div>`)}</div>`;
14806
15683
  }
14807
15684
  };
14808
15685
  __decorate([property({
@@ -14846,6 +15723,7 @@ let DetailListElement = class DetailListElement extends i {
14846
15723
  this.iconProperty = ``;
14847
15724
  this.keyProperty = ``;
14848
15725
  this.itemWrap = `stop`;
15726
+ this.itemsDraggable = false;
14849
15727
  this.#itemData = /* @__PURE__ */ new WeakMap();
14850
15728
  this.#grid = [];
14851
15729
  this.#positions = /* @__PURE__ */ new Map();
@@ -14881,7 +15759,41 @@ let DetailListElement = class DetailListElement extends i {
14881
15759
  },
14882
15760
  onStateChange: () => this._syncItemStates(),
14883
15761
  itemWrap: this.itemWrap,
14884
- hoverDisabled: () => this.#dragSelect.isDragging
15762
+ hoverDisabled: () => this.#dragSelect.isDragging || this.#nativeDrag.isDragging
15763
+ });
15764
+ this.#nativeDragging = false;
15765
+ this.#postDragSuppressTimer = 0;
15766
+ this.#nativeDrag = new ListNativeDragController(this, {
15767
+ source: `detail-list`,
15768
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this._getVisibleItems().length > 0,
15769
+ resolveEventToKey: (target) => {
15770
+ if (!(target instanceof Element)) return void 0;
15771
+ const li = target.closest?.(`li`);
15772
+ if (!li || !this.contains(li)) return void 0;
15773
+ return keyForElement(li);
15774
+ },
15775
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
15776
+ getMetrics: () => ({}),
15777
+ getRenderer: () => this.dragPreviewRenderer,
15778
+ dispatch: (detail) => this._dispatchCancelable(`list-native-drag-start`, detail),
15779
+ onActiveChange: (active, originKey) => {
15780
+ this.#nativeDragging = active;
15781
+ this.toggleAttribute(`data-native-dragging`, active);
15782
+ for (const el of this.querySelectorAll(`li`)) el.toggleAttribute(`data-drag-origin`, active && originKey !== void 0 && keyForElement(el) === originKey);
15783
+ if (active) {
15784
+ const count = this.selection.selected.size;
15785
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
15786
+ } else this.removeAttribute(`aria-label`);
15787
+ this.requestUpdate();
15788
+ },
15789
+ onEnd: () => {
15790
+ this.#nativeDragging = false;
15791
+ this.toggleAttribute(`data-native-dragging`, false);
15792
+ this.removeAttribute(`aria-label`);
15793
+ for (const el of this.querySelectorAll(`li`)) el.removeAttribute(`data-drag-origin`);
15794
+ this.#startPostDragSuppressTimer();
15795
+ },
15796
+ syncLightDomDraggable: () => this._syncNativeDraggable()
14885
15797
  });
14886
15798
  this.#dragSelect = new ListDragSelectController(this, {
14887
15799
  getSurface: () => this.renderRoot.querySelector(`.container`),
@@ -14978,7 +15890,7 @@ let DetailListElement = class DetailListElement extends i {
14978
15890
  }
14979
15891
  };
14980
15892
  this._onClick = (e) => {
14981
- if (this.#dragSelect.isDragging) return;
15893
+ if (this.#dragSelect.isDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
14982
15894
  const item = this._findListItem(e.composedPath());
14983
15895
  if (!item) return;
14984
15896
  this.focus();
@@ -15005,6 +15917,42 @@ let DetailListElement = class DetailListElement extends i {
15005
15917
  #builtFromProvider;
15006
15918
  #providerBuilt;
15007
15919
  #data;
15920
+ #nativeDragging;
15921
+ #postDragSuppressTimer;
15922
+ #nativeDrag;
15923
+ #prepareNativeDrag(originKey) {
15924
+ const visible = this._getVisibleItems();
15925
+ const originEl = visible.find((el) => keyForElement(el) === originKey);
15926
+ if (!originEl || !this._isCheckable(originEl)) return void 0;
15927
+ if (!this.selection.selected.has(originEl)) this.selection.replace(originEl);
15928
+ const selected = [];
15929
+ visible.forEach((el, index) => {
15930
+ if (!this.selection.selected.has(el)) return;
15931
+ const key = keyForElement(el) ?? originKey;
15932
+ selected.push({
15933
+ key,
15934
+ index,
15935
+ item: this.#itemData.get(el) ?? el,
15936
+ element: el
15937
+ });
15938
+ });
15939
+ const origin = selected.find((entry) => entry.key === originKey);
15940
+ if (!origin) return void 0;
15941
+ return {
15942
+ origin,
15943
+ selected
15944
+ };
15945
+ }
15946
+ _syncNativeDraggable() {
15947
+ if (!this.isConnected) return;
15948
+ for (const el of this.querySelectorAll(`li`)) if (el instanceof HTMLElement) el.draggable = this.itemsDraggable && this._isCheckable(el);
15949
+ }
15950
+ #startPostDragSuppressTimer() {
15951
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
15952
+ this.#postDragSuppressTimer = window.setTimeout(() => {
15953
+ this.#postDragSuppressTimer = 0;
15954
+ }, 0);
15955
+ }
15008
15956
  /** Live drag-select preview rect (surface-relative); the covered items carry `data-marquee`. */
15009
15957
  #marqueeRect;
15010
15958
  #dragSelect;
@@ -15107,11 +16055,16 @@ let DetailListElement = class DetailListElement extends i {
15107
16055
  this.#unregisterSearchCommands?.();
15108
16056
  this.#unregisterSearchCommands = void 0;
15109
16057
  this.#searchRegistry = void 0;
16058
+ if (this.#postDragSuppressTimer) {
16059
+ clearTimeout(this.#postDragSuppressTimer);
16060
+ this.#postDragSuppressTimer = 0;
16061
+ }
15110
16062
  }
15111
16063
  firstUpdated() {
15112
16064
  this.#processChildren();
15113
16065
  if (this.filterPredicate) this.#applyFilterPredicate();
15114
16066
  this.#recomputeGeometry();
16067
+ this._syncNativeDraggable();
15115
16068
  }
15116
16069
  updated(changed) {
15117
16070
  super.updated(changed);
@@ -15135,7 +16088,12 @@ let DetailListElement = class DetailListElement extends i {
15135
16088
  if (changed.has(`interactionMode`)) {
15136
16089
  this.selection.clearAnchor();
15137
16090
  this._syncItemStates();
16091
+ this._syncNativeDraggable();
15138
16092
  } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
16093
+ if (changed.has(`itemsDraggable`)) {
16094
+ this._syncNativeDraggable();
16095
+ this.#nativeDrag.refreshDraggable();
16096
+ }
15139
16097
  }
15140
16098
  /** The most recently selected item, or `undefined` if nothing is selected. */
15141
16099
  get selectedItem() {
@@ -15700,6 +16658,14 @@ let DetailListElement = class DetailListElement extends i {
15700
16658
  composed: true
15701
16659
  }));
15702
16660
  }
16661
+ _dispatchCancelable(name, detail) {
16662
+ return this.dispatchEvent(new CustomEvent(name, {
16663
+ detail,
16664
+ bubbles: true,
16665
+ composed: true,
16666
+ cancelable: true
16667
+ }));
16668
+ }
15703
16669
  _activateItem(item) {
15704
16670
  if (this.interactionMode !== `manual`) this.selection.replace(item);
15705
16671
  this._dispatch(`list-activate`, { item });
@@ -15817,6 +16783,10 @@ let DetailListElement = class DetailListElement extends i {
15817
16783
  color: var(--item-text-selected);
15818
16784
  }
15819
16785
 
16786
+ ::slotted([data-drag-origin]) {
16787
+ opacity: 0.6;
16788
+ }
16789
+
15820
16790
  /* checked mode: the injected checkbox communicates selection — suppress bg */
15821
16791
  :host([interaction-mode="checked"]) ::slotted([data-selected]) {
15822
16792
  background: transparent;
@@ -15906,6 +16876,12 @@ __decorate([property({
15906
16876
  attribute: `search-modes`,
15907
16877
  reflect: true
15908
16878
  })], DetailListElement.prototype, "searchModes", void 0);
16879
+ __decorate([property({
16880
+ type: Boolean,
16881
+ attribute: `items-draggable`,
16882
+ reflect: true
16883
+ })], DetailListElement.prototype, "itemsDraggable", void 0);
16884
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "dragPreviewRenderer", void 0);
15909
16885
  __decorate([query(`ul`)], DetailListElement.prototype, "_listEl", void 0);
15910
16886
  DetailListElement = __decorate([safeCustomElement(`ixfx-detail-list`)], DetailListElement);
15911
16887
  //#endregion
@@ -21944,12 +22920,50 @@ let GridListElement = class GridListElement extends i {
21944
22920
  },
21945
22921
  onStateChange: () => this._syncItemStates(),
21946
22922
  itemWrap: this.#itemWrap,
21947
- hoverDisabled: () => this.disableHoverTickle || this.#dragSelect.isDragging
22923
+ hoverDisabled: () => this.disableHoverTickle || this.#dragSelect.isDragging || this.#nativeDrag.isDragging
21948
22924
  });
21949
22925
  this.#marqueePreview = /* @__PURE__ */ new Set();
21950
22926
  this.#nativeDragging = false;
21951
22927
  this.#dragOriginKey = void 0;
21952
22928
  this.#postDragSuppressTimer = 0;
22929
+ this.#nativeDrag = new ListNativeDragController(this, {
22930
+ source: `grid-list`,
22931
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this.#source.count > 0,
22932
+ resolveEventToKey: (target) => {
22933
+ if (target instanceof Element) {
22934
+ const key = (target.closest?.(`.cell`))?.dataset.key;
22935
+ if (key) return key;
22936
+ const keyed = (target.closest?.(`[data-key]`))?.dataset.key ?? target.dataset?.key ?? keyForElement(target);
22937
+ if (keyed) return keyed;
22938
+ }
22939
+ },
22940
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
22941
+ getMetrics: () => ({
22942
+ itemSize: this.itemSize,
22943
+ captionStyle: this.captionStyle,
22944
+ captionHeight: this.captionHeight,
22945
+ rowHeight: this.#rowHeight()
22946
+ }),
22947
+ getRenderer: () => this.dragPreviewRenderer,
22948
+ dispatch: (detail) => this._dispatch(`list-native-drag-start`, detail, true),
22949
+ onActiveChange: (active, originKey) => {
22950
+ this.#nativeDragging = active;
22951
+ this.#dragOriginKey = active ? originKey : void 0;
22952
+ this.toggleAttribute(`data-native-dragging`, active);
22953
+ if (active) {
22954
+ const count = [...this.selection.selectedKeys].filter((k) => !isSyntheticKey(k)).length;
22955
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
22956
+ } else this.removeAttribute(`aria-label`);
22957
+ this.requestUpdate();
22958
+ },
22959
+ onEnd: () => {
22960
+ this.#clearNativeDragState();
22961
+ this.#startPostDragSuppressTimer();
22962
+ },
22963
+ syncLightDomDraggable: () => {
22964
+ for (const child of this.children) if (child instanceof HTMLElement) child.draggable = this.itemsDraggable;
22965
+ }
22966
+ });
21953
22967
  this.#activeCaptionPan = null;
21954
22968
  this.#dragSelect = new ListDragSelectController(this, {
21955
22969
  getSurface: () => this.#viewportRef.value,
@@ -21995,23 +23009,6 @@ let GridListElement = class GridListElement extends i {
21995
23009
  this.#emitViewportChange();
21996
23010
  });
21997
23011
  };
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
23012
  this._onKeyDown = (e) => {
22016
23013
  if ((e.ctrlKey || e.metaKey) && e.key === `f`) return;
22017
23014
  if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
@@ -22448,6 +23445,35 @@ let GridListElement = class GridListElement extends i {
22448
23445
  #dragOriginKey;
22449
23446
  /** Timer handle for post-drag click suppression. */
22450
23447
  #postDragSuppressTimer;
23448
+ #nativeDrag;
23449
+ /** Origin rule + display-ordered entry resolution for native drag-out (both modes). */
23450
+ #prepareNativeDrag(originKey) {
23451
+ const index = this.#source.indexOfKey(originKey);
23452
+ if (index < 0 || !this.#source.isSelectable(index)) return void 0;
23453
+ if (!this.selection.selectedKeys.has(originKey)) this.selection.replace(originKey);
23454
+ const ordered = this.#source.keys().filter((k) => !isSyntheticKey(k) && this.selection.selectedKeys.has(k));
23455
+ const selected = [];
23456
+ for (const key of ordered) {
23457
+ const entryIndex = this.#source.indexOfKey(key);
23458
+ if (entryIndex < 0) continue;
23459
+ const data = this.#source.resolveKeyToData(key);
23460
+ const element = this.#source.resolveKeyToElement(key);
23461
+ const item = data ?? element;
23462
+ if (item === void 0) continue;
23463
+ selected.push({
23464
+ key,
23465
+ index: entryIndex,
23466
+ item,
23467
+ ...element ? { element } : {}
23468
+ });
23469
+ }
23470
+ const origin = selected.find((entry) => entry.key === originKey);
23471
+ if (!origin) return void 0;
23472
+ return {
23473
+ origin,
23474
+ selected
23475
+ };
23476
+ }
22451
23477
  /**
22452
23478
  * The single caption pan-text currently scrolling. At most one caption is
22453
23479
  * a pan-text in loop mode at a time — when tickle moves, the old one is
@@ -22580,7 +23606,7 @@ let GridListElement = class GridListElement extends i {
22580
23606
  clearTimeout(this.#reflowTimer);
22581
23607
  this.#reflowTimer = 0;
22582
23608
  }
22583
- if (this.#nativeDragging) this.#clearNativeDragState();
23609
+ if (this.#nativeDragging || this.#nativeDrag.isDragging) this.#clearNativeDragState();
22584
23610
  }
22585
23611
  #onChildrenChanged(records) {
22586
23612
  const previousLength = this.#items.length;
@@ -22661,7 +23687,7 @@ let GridListElement = class GridListElement extends i {
22661
23687
  this.#measureViewport(false);
22662
23688
  this.#syncItems();
22663
23689
  this.requestUpdate();
22664
- this.#initSlottedDragInterceptor();
23690
+ this.#nativeDrag.refreshDraggable();
22665
23691
  }
22666
23692
  /** (Re)construct {@link #source} for the current mode and re-apply the filters. */
22667
23693
  #rebuildSource() {
@@ -22751,7 +23777,7 @@ let GridListElement = class GridListElement extends i {
22751
23777
  this.selection.clearAnchor();
22752
23778
  this.#syncAriaMultiselectable();
22753
23779
  }
22754
- if (changed.has(`itemsDraggable`)) this.#syncSlottedDraggable();
23780
+ if (changed.has(`itemsDraggable`)) this.#nativeDrag.refreshDraggable();
22755
23781
  if (changed.has(`orientation`)) {
22756
23782
  const vp = this.#viewportRef.value;
22757
23783
  if (vp) {
@@ -23170,8 +24196,6 @@ let GridListElement = class GridListElement extends i {
23170
24196
  @click=${(e) => this.#onCellClick(e, key)}
23171
24197
  @dblclick=${() => this.#onCellActivate(key)}
23172
24198
  @pointerenter=${() => this.tickled.handlePointerEnter(key)}
23173
- @dragstart=${(e) => this.#onDragStart(e, key)}
23174
- @dragend=${() => this.#onDragEnd()}
23175
24199
  >
23176
24200
  <div class="content" part="content">${this.#source.renderCellContent(index)}</div>
23177
24201
  ${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
@@ -23256,8 +24280,6 @@ let GridListElement = class GridListElement extends i {
23256
24280
  @click=${(e) => this.#onCellClick(e, key)}
23257
24281
  @dblclick=${() => this.#onCellActivate(key)}
23258
24282
  @pointerenter=${() => this.tickled.handlePointerEnter(key)}
23259
- @dragstart=${(e) => this.#onDragStart(e, key)}
23260
- @dragend=${() => this.#onDragEnd()}
23261
24283
  >
23262
24284
  <div class="thumb" part="thumb" style=${thumbStyle}>
23263
24285
  ${this.#source.renderCellContent(index)}
@@ -23274,145 +24296,10 @@ let GridListElement = class GridListElement extends i {
23274
24296
  }
23275
24297
  }
23276
24298
  #onCellClick(e, key) {
23277
- if (this.#dragSelect.isDragging || this.#nativeDragging || this.#postDragSuppressTimer !== 0) return;
24299
+ if (this.#dragSelect.isDragging || this.#nativeDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
23278
24300
  this.selection.handleClick(key, e);
23279
24301
  this.#emitItemEvent(`list-item-click`, key);
23280
24302
  }
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
24303
  #startPostDragSuppressTimer() {
23417
24304
  if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
23418
24305
  this.#postDragSuppressTimer = window.setTimeout(() => {
@@ -23487,6 +24374,7 @@ __decorate([property({
23487
24374
  attribute: `items-draggable`,
23488
24375
  reflect: true
23489
24376
  })], GridListElement.prototype, "itemsDraggable", void 0);
24377
+ __decorate([property({ attribute: false })], GridListElement.prototype, "dragPreviewRenderer", void 0);
23490
24378
  __decorate([property({
23491
24379
  type: String,
23492
24380
  attribute: `search-modes`,
@@ -23881,6 +24769,360 @@ __decorate([property({
23881
24769
  })], GroupedItemListerElement.prototype, "crossGroupSelection", void 0);
23882
24770
  GroupedItemListerElement = __decorate([safeCustomElement(`ixfx-grouped-item-lister`)], GroupedItemListerElement);
23883
24771
  //#endregion
24772
+ //#region src/tree/tree-drag-registry.ts
24773
+ const TREE_DRAG_MIME = `application/x-ixfx-tree`;
24774
+ const registry = /* @__PURE__ */ new Map();
24775
+ const weakRefs = /* @__PURE__ */ new Map();
24776
+ /**
24777
+ * The source id of the drag currently in flight, if any. There is only ever one
24778
+ * native drag at a time, so a drop target can resolve the dragged nodes during
24779
+ * `dragover` — when `DataTransfer.getData()` is unreadable (protected mode) — by
24780
+ * looking this up in the registry.
24781
+ */
24782
+ let currentSourceId;
24783
+ let sourceIdCounter = 0;
24784
+ function treeDragGenerateSourceId() {
24785
+ return `tree-drag-${++sourceIdCounter}-${Date.now()}`;
24786
+ }
24787
+ function treeDragRegister(sourceId, element, nodes, keys) {
24788
+ registry.set(sourceId, {
24789
+ sourceId,
24790
+ nodes,
24791
+ keys
24792
+ });
24793
+ weakRefs.set(sourceId, new WeakRef(element));
24794
+ currentSourceId = sourceId;
24795
+ }
24796
+ function treeDragUnregister(sourceId) {
24797
+ registry.delete(sourceId);
24798
+ weakRefs.delete(sourceId);
24799
+ if (currentSourceId === sourceId) currentSourceId = void 0;
24800
+ }
24801
+ /** The in-flight drag's `{ keys, nodes, element }`, or `undefined` when nothing is dragging. */
24802
+ function treeDragCurrent() {
24803
+ if (currentSourceId === void 0) return void 0;
24804
+ const entry = registry.get(currentSourceId);
24805
+ if (!entry) return void 0;
24806
+ return {
24807
+ sourceId: currentSourceId,
24808
+ keys: entry.keys,
24809
+ nodes: entry.nodes,
24810
+ element: weakRefs.get(currentSourceId)?.deref()
24811
+ };
24812
+ }
24813
+ //#endregion
24814
+ //#region src/tree/tree-drop-controller.ts
24815
+ const EDGE_ZONE_PX$1 = 24;
24816
+ const MAX_SCROLL_SPEED = 16;
24817
+ function clamp$4(value, min, max) {
24818
+ return Math.max(min, Math.min(max, value));
24819
+ }
24820
+ var TreeDropController = class {
24821
+ #host;
24822
+ #config;
24823
+ #active = false;
24824
+ #current;
24825
+ #source = `internal`;
24826
+ #dragKeys = [];
24827
+ #dragNodes = [];
24828
+ #expandTimer;
24829
+ #expandPendingKey;
24830
+ #scrollRaf = 0;
24831
+ #pointerX = 0;
24832
+ #pointerY = 0;
24833
+ #boundContainer;
24834
+ constructor(host, config) {
24835
+ this.#host = host;
24836
+ this.#config = config;
24837
+ host.addController(this);
24838
+ }
24839
+ /** A drag is currently over this tree. */
24840
+ get isDragging() {
24841
+ return this.#active;
24842
+ }
24843
+ /** A drag is over this tree but the current position is not a valid drop. */
24844
+ get isRejected() {
24845
+ return this.#active && this.#current === void 0;
24846
+ }
24847
+ get dropState() {
24848
+ return this.#current;
24849
+ }
24850
+ hostConnected() {
24851
+ this.#bind();
24852
+ }
24853
+ /** Bind once the shadow `#container` exists (it does not yet at `hostConnected`). */
24854
+ hostUpdated() {
24855
+ this.#bind();
24856
+ }
24857
+ #bind() {
24858
+ const container = this.#config.getScrollContainer();
24859
+ if (!container || container === this.#boundContainer) return;
24860
+ this.#unbind();
24861
+ this.#boundContainer = container;
24862
+ container.addEventListener(`dragenter`, this.#onDragEnter);
24863
+ container.addEventListener(`dragover`, this.#onDragOver);
24864
+ container.addEventListener(`dragleave`, this.#onDragLeave);
24865
+ container.addEventListener(`drop`, this.#onDrop);
24866
+ }
24867
+ #unbind() {
24868
+ const c = this.#boundContainer;
24869
+ if (!c) return;
24870
+ c.removeEventListener(`dragenter`, this.#onDragEnter);
24871
+ c.removeEventListener(`dragover`, this.#onDragOver);
24872
+ c.removeEventListener(`dragleave`, this.#onDragLeave);
24873
+ c.removeEventListener(`drop`, this.#onDrop);
24874
+ this.#boundContainer = void 0;
24875
+ }
24876
+ hostDisconnected() {
24877
+ this.#unbind();
24878
+ this.#clearExpand();
24879
+ this.#stopScroll();
24880
+ this.#active = false;
24881
+ this.#current = void 0;
24882
+ }
24883
+ #onDragEnter = (event) => {
24884
+ if (this.#config.getDndMode() === `none` || !event.dataTransfer) return;
24885
+ if (this.#config.resolveDrag(event.dataTransfer, `hover`) === void 0) return;
24886
+ event.preventDefault();
24887
+ if (!this.#active) {
24888
+ this.#active = true;
24889
+ this.#config.onActiveChange(true);
24890
+ }
24891
+ };
24892
+ #onDragOver = (event) => {
24893
+ if (this.#config.getDndMode() === `none` || !event.dataTransfer) return;
24894
+ this.#pointerX = event.clientX;
24895
+ this.#pointerY = event.clientY;
24896
+ const drag = this.#config.resolveDrag(event.dataTransfer, `hover`);
24897
+ if (!drag) {
24898
+ this.#reject(event);
24899
+ return;
24900
+ }
24901
+ if (!this.#active) {
24902
+ this.#active = true;
24903
+ this.#config.onActiveChange(true);
24904
+ }
24905
+ this.#source = drag.source;
24906
+ this.#dragKeys = drag.keys;
24907
+ this.#dragNodes = drag.nodes;
24908
+ const rows = this.#config.getVisibleRows();
24909
+ this.#handleExpandHover(this.#rowAtPointer(rows, event.clientY));
24910
+ this.#kickScroll();
24911
+ const target = this.#resolveIntent(event, rows, drag.source);
24912
+ if (!target) {
24913
+ this.#reject(event, false);
24914
+ return;
24915
+ }
24916
+ const ctx = {
24917
+ source: drag.source,
24918
+ dragged: drag.nodes.length > 0 ? drag.nodes : void 0,
24919
+ draggedKeys: drag.keys,
24920
+ target,
24921
+ dataTransfer: event.dataTransfer
24922
+ };
24923
+ if (!this.#config.vet(ctx)) {
24924
+ this.#reject(event, false);
24925
+ return;
24926
+ }
24927
+ event.preventDefault();
24928
+ event.dataTransfer.dropEffect = drag.source === `internal` ? `move` : `copy`;
24929
+ this.#setDropState(target);
24930
+ };
24931
+ /** Not a valid drop here: keep the drag active for feedback but accept nothing. */
24932
+ #reject(event, clearHover = true) {
24933
+ if (event.dataTransfer) event.dataTransfer.dropEffect = `none`;
24934
+ this.#setDropState(void 0);
24935
+ if (clearHover) this.#clearExpand();
24936
+ }
24937
+ #onDragLeave = (event) => {
24938
+ const container = this.#config.getScrollContainer();
24939
+ const related = event.relatedTarget;
24940
+ if (container && related && container.contains(related)) return;
24941
+ this.#endHover();
24942
+ };
24943
+ #onDrop = (event) => {
24944
+ if (!event.dataTransfer) {
24945
+ this.#endHover();
24946
+ return;
24947
+ }
24948
+ event.preventDefault();
24949
+ this.#stopScroll();
24950
+ this.#clearExpand();
24951
+ const finish = (dropped) => {
24952
+ this.#current = void 0;
24953
+ this.#active = false;
24954
+ this.#config.onDropStateChange(void 0);
24955
+ this.#config.onActiveChange(false);
24956
+ this.#config.onDragEnd(dropped);
24957
+ };
24958
+ if (this.#config.getDndMode() === `none`) {
24959
+ finish(false);
24960
+ return;
24961
+ }
24962
+ const drag = this.#config.resolveDrag(event.dataTransfer, `drop`);
24963
+ if (!drag) {
24964
+ finish(false);
24965
+ return;
24966
+ }
24967
+ this.#dragKeys = drag.keys;
24968
+ this.#dragNodes = drag.nodes;
24969
+ const target = this.#resolveIntent(event, this.#config.getVisibleRows(), drag.source);
24970
+ if (!target) {
24971
+ finish(false);
24972
+ return;
24973
+ }
24974
+ const ctx = {
24975
+ source: drag.source,
24976
+ dragged: drag.nodes.length > 0 ? drag.nodes : void 0,
24977
+ draggedKeys: drag.keys,
24978
+ target,
24979
+ dataTransfer: event.dataTransfer
24980
+ };
24981
+ if (!this.#config.vet(ctx)) {
24982
+ finish(false);
24983
+ return;
24984
+ }
24985
+ const detail = {
24986
+ ...ctx,
24987
+ external: drag.source === `external` ? drag.nodes : void 0
24988
+ };
24989
+ finish(this.#config.commitDrop(detail));
24990
+ };
24991
+ #endHover() {
24992
+ this.#clearExpand();
24993
+ this.#stopScroll();
24994
+ if (this.#current !== void 0) {
24995
+ this.#current = void 0;
24996
+ this.#config.onDropStateChange(void 0);
24997
+ }
24998
+ if (this.#active) {
24999
+ this.#active = false;
25000
+ this.#config.onActiveChange(false);
25001
+ }
25002
+ }
25003
+ #rowAtPointer(rows, clientY) {
25004
+ if (rows.length === 0) return void 0;
25005
+ if (clientY < rows[0].rect.top) return rows[0];
25006
+ if (clientY > rows[rows.length - 1].rect.bottom) return rows[rows.length - 1];
25007
+ return rows.find((r) => clientY >= r.rect.top && clientY <= r.rect.bottom) ?? rows[rows.length - 1];
25008
+ }
25009
+ #parentRow(rows, index) {
25010
+ const target = rows[index];
25011
+ for (let i = index - 1; i >= 0; i--) if (rows[i].depth === target.depth - 1) return rows[i];
25012
+ }
25013
+ #resolveIntent(event, rows, source) {
25014
+ if (rows.length === 0) return void 0;
25015
+ const row = this.#rowAtPointer(rows, event.clientY);
25016
+ if (!row) return void 0;
25017
+ const rowIndex = rows.indexOf(row);
25018
+ const f = clamp$4((event.clientY - row.rect.top) / row.rect.height, 0, 1);
25019
+ let position = f < .25 ? `before` : f > .75 ? `after` : `inside`;
25020
+ const isLeaf = row.node.item.isLeaf === true;
25021
+ if (position === `inside` && isLeaf) position = `after`;
25022
+ let insideFromExpandedAfter = false;
25023
+ if (position === `after` && !isLeaf && this.#config.isExpanded(row.key)) {
25024
+ position = `inside`;
25025
+ insideFromExpandedAfter = true;
25026
+ }
25027
+ let parentKey;
25028
+ let referenceKey;
25029
+ let depth;
25030
+ let index;
25031
+ if (position === `inside`) {
25032
+ parentKey = row.key;
25033
+ referenceKey = row.key;
25034
+ depth = row.depth + 1;
25035
+ index = insideFromExpandedAfter ? 0 : row.node.children?.length ?? 0;
25036
+ } else {
25037
+ const indent = this.#config.getIndentPx() || 1;
25038
+ const treeLeft = rows[0].rect.left;
25039
+ const effDepth = clamp$4(Math.round((event.clientX - treeLeft) / indent), 0, row.depth);
25040
+ let anchorIndex = rowIndex;
25041
+ for (let up = row.depth - effDepth; up > 0; up--) {
25042
+ const parent = this.#parentRow(rows, anchorIndex);
25043
+ if (!parent) break;
25044
+ anchorIndex = rows.indexOf(parent);
25045
+ }
25046
+ const anchor = rows[anchorIndex];
25047
+ referenceKey = anchor.key;
25048
+ depth = anchor.depth;
25049
+ const parent = this.#parentRow(rows, anchorIndex);
25050
+ parentKey = parent?.key;
25051
+ const refIdx = (parent ? parent.node.children ?? [] : rows.filter((r) => r.depth === 0).map((r) => r.node)).findIndex((n) => n.item.key === anchor.key);
25052
+ index = (refIdx < 0 ? 0 : refIdx) + (position === `after` ? 1 : 0);
25053
+ }
25054
+ if (source === `internal`) {
25055
+ for (const key of this.#dragKeys) {
25056
+ if (key === parentKey || key === referenceKey) return void 0;
25057
+ if (parentKey !== void 0 && this.#config.isSelfOrDescendant(key, parentKey)) return void 0;
25058
+ if (position !== `inside` && this.#config.isSelfOrDescendant(key, referenceKey)) return void 0;
25059
+ }
25060
+ if (this.#dragKeys.length > 0 && !this.#config.isReorderAllowed()) {
25061
+ if (!this.#dragKeys.some((k) => this.#config.getParentKey(k) !== parentKey)) return void 0;
25062
+ }
25063
+ }
25064
+ return {
25065
+ parentKey,
25066
+ index,
25067
+ referenceKey,
25068
+ position,
25069
+ depth
25070
+ };
25071
+ }
25072
+ #handleExpandHover(row) {
25073
+ if (!row || row.node.item.isLeaf === true || this.#config.isExpanded(row.key)) {
25074
+ this.#clearExpand();
25075
+ return;
25076
+ }
25077
+ if (row.key === this.#expandPendingKey) return;
25078
+ this.#clearExpand();
25079
+ const key = this.#expandPendingKey = row.key;
25080
+ this.#expandTimer = setTimeout(() => {
25081
+ this.#expandTimer = void 0;
25082
+ if (this.#expandPendingKey === key) {
25083
+ this.#expandPendingKey = void 0;
25084
+ this.#config.expandKey(key);
25085
+ }
25086
+ }, Math.max(0, this.#config.getExpandHoverMs()));
25087
+ }
25088
+ #clearExpand() {
25089
+ if (this.#expandTimer !== void 0) {
25090
+ clearTimeout(this.#expandTimer);
25091
+ this.#expandTimer = void 0;
25092
+ }
25093
+ this.#expandPendingKey = void 0;
25094
+ }
25095
+ #kickScroll() {
25096
+ if (this.#scrollRaf === 0) this.#scrollRaf = requestAnimationFrame(this.#scrollTick);
25097
+ }
25098
+ #scrollTick = () => {
25099
+ this.#scrollRaf = 0;
25100
+ if (!this.#active) return;
25101
+ const container = this.#config.getScrollContainer();
25102
+ if (!container) return;
25103
+ const rect = container.getBoundingClientRect();
25104
+ const y = this.#pointerY - rect.top;
25105
+ let dy = 0;
25106
+ if (y < EDGE_ZONE_PX$1) dy = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, y) / EDGE_ZONE_PX$1));
25107
+ else if (y > rect.height - EDGE_ZONE_PX$1) dy = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, rect.height - y) / EDGE_ZONE_PX$1));
25108
+ if (dy !== 0 && this.#pointerX >= rect.left && this.#pointerX <= rect.right) {
25109
+ container.scrollTop += dy;
25110
+ this.#kickScroll();
25111
+ }
25112
+ };
25113
+ #stopScroll() {
25114
+ if (this.#scrollRaf !== 0) {
25115
+ cancelAnimationFrame(this.#scrollRaf);
25116
+ this.#scrollRaf = 0;
25117
+ }
25118
+ }
25119
+ #setDropState(state) {
25120
+ const unchanged = state === void 0 ? this.#current === void 0 : state.referenceKey === this.#current?.referenceKey && state.position === this.#current?.position && state.depth === this.#current?.depth && state.index === this.#current?.index;
25121
+ this.#current = state;
25122
+ if (!unchanged || this.#active) this.#config.onDropStateChange(state);
25123
+ }
25124
+ };
25125
+ //#endregion
23884
25126
  //#region src/tree/tree-base.ts
23885
25127
  /** Above this many rows entering / leaving at once, skip the animation entirely. */
23886
25128
  const MAX_ANIMATED_ROWS = 50;
@@ -23893,6 +25135,10 @@ let TreeBaseElement = class TreeBaseElement extends i {
23893
25135
  this._selectedNodes = /* @__PURE__ */ new Set();
23894
25136
  this.disableKeyboardNavigation = false;
23895
25137
  this.registry = void 0;
25138
+ this.itemDnd = `none`;
25139
+ this.dndExpandHoverMs = 500;
25140
+ this.reorderable = true;
25141
+ this.applyInternalDrop = true;
23896
25142
  this._expandedKeys = /* @__PURE__ */ new Set();
23897
25143
  this.tickled = new TickledController(this, {
23898
25144
  getItems: () => {
@@ -23901,6 +25147,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
23901
25147
  enabled: true
23902
25148
  }));
23903
25149
  },
25150
+ hoverDisabled: () => this.#drop?.isDragging === true || this.#nativeDrag?.isDragging === true,
23904
25151
  onStateChange: () => {
23905
25152
  this.setAttribute(`data-input-mode`, this.tickled.inputMode);
23906
25153
  }
@@ -23915,8 +25162,14 @@ let TreeBaseElement = class TreeBaseElement extends i {
23915
25162
  this.#lastNodeByKey = /* @__PURE__ */ new Map();
23916
25163
  this.#enteringKeys = /* @__PURE__ */ new Set();
23917
25164
  this.#leaving = /* @__PURE__ */ new Map();
23918
- this.#leavingAnimating = /* @__PURE__ */ new Set();
25165
+ this.#leavingAnimations = /* @__PURE__ */ new Map();
23919
25166
  this.#treeAnimReady = false;
25167
+ this.#dragSourceId = ``;
25168
+ this.#nativeDragKeys = /* @__PURE__ */ new Set();
25169
+ this.#internalDropHandled = false;
25170
+ this.#onContainerScroll = () => {
25171
+ this.#rowsCache = void 0;
25172
+ };
23920
25173
  this.#onKeyDown = (event) => {
23921
25174
  if (this.search.isOpen) return;
23922
25175
  const nodes = this.#getAllNodes();
@@ -24068,8 +25321,40 @@ let TreeBaseElement = class TreeBaseElement extends i {
24068
25321
  #lastNodeByKey;
24069
25322
  #enteringKeys;
24070
25323
  #leaving;
24071
- #leavingAnimating;
25324
+ #leavingAnimations;
24072
25325
  #treeAnimReady;
25326
+ #dragSourceId;
25327
+ #postDragSuppressTimer;
25328
+ #dropState;
25329
+ #drop;
25330
+ #nativeDrag;
25331
+ /** Keys of the nodes currently being dragged out (for `.dragging` styling). */
25332
+ #nativeDragKeys;
25333
+ /** Set by `commitDrop` for a same-tree internal drop, read by the native `onEnd` to avoid a duplicate `tree-drag-end`. */
25334
+ #internalDropHandled;
25335
+ /** Per-drag cache of measured rows; invalidated on scroll / update. */
25336
+ #rowsCache;
25337
+ #onContainerScroll;
25338
+ get nativeDrag() {
25339
+ return this.#nativeDrag;
25340
+ }
25341
+ get dropState() {
25342
+ return this.#dropState;
25343
+ }
25344
+ /** Keys of the nodes currently being dragged out of this tree. */
25345
+ get nativeDragKeys() {
25346
+ return this.#nativeDragKeys;
25347
+ }
25348
+ #cancelLeavingAnimation(key) {
25349
+ this.#leavingAnimations.get(key)?.cancel();
25350
+ this.#leavingAnimations.delete(key);
25351
+ }
25352
+ #cancelAllLeavingAnimations() {
25353
+ for (const animation of this.#leavingAnimations.values()) try {
25354
+ animation.cancel();
25355
+ } catch {}
25356
+ this.#leavingAnimations.clear();
25357
+ }
24073
25358
  willUpdate(changedProps) {
24074
25359
  super.willUpdate(changedProps);
24075
25360
  this.#diffVisibleNodes();
@@ -24088,12 +25373,12 @@ let TreeBaseElement = class TreeBaseElement extends i {
24088
25373
  }
24089
25374
  }
24090
25375
  for (const key of [...this.#leaving.keys()]) if (nowKeys.has(key)) {
25376
+ this.#cancelLeavingAnimation(key);
24091
25377
  this.#leaving.delete(key);
24092
- this.#leavingAnimating.delete(key);
24093
25378
  }
24094
25379
  if (this.#leaving.size > MAX_ANIMATED_ROWS) {
25380
+ this.#cancelAllLeavingAnimations();
24095
25381
  this.#leaving.clear();
24096
- this.#leavingAnimating.clear();
24097
25382
  }
24098
25383
  }
24099
25384
  /** True while `node` is a fading ghost row (removed / collapsed, still animating out). */
@@ -24130,18 +25415,38 @@ let TreeBaseElement = class TreeBaseElement extends i {
24130
25415
  if (this.#enteringKeys.size > 0) {
24131
25416
  for (const el of this.renderRoot.querySelectorAll(`.item`)) {
24132
25417
  const key = el.dataset.key;
24133
- if (key !== void 0 && this.#enteringKeys.has(key)) animateListItemIn(el);
25418
+ if (key === void 0 || !this.#enteringKeys.has(key)) continue;
25419
+ if (this.#leavingAnimations.has(key)) this.#cancelLeavingAnimation(key);
25420
+ else for (const animation of el.getAnimations()) animation.cancel();
25421
+ animateListItemIn(el);
24134
25422
  }
24135
25423
  this.#enteringKeys.clear();
24136
25424
  }
24137
25425
  if (this.#leaving.size > 0) for (const el of this.renderRoot.querySelectorAll(`.item.leaving`)) {
24138
25426
  const key = el.dataset.key;
24139
- if (key === void 0 || this.#leavingAnimating.has(key)) continue;
24140
- this.#leavingAnimating.add(key);
24141
- animateListItemOut(el).then(() => {
25427
+ if (key === void 0 || this.#leavingAnimations.has(key)) continue;
25428
+ const result = animateListItemOut(el);
25429
+ const animation = result.animation;
25430
+ if (!animation) {
25431
+ result.then(() => {
25432
+ if (!this.#leaving.has(key)) return;
25433
+ this.#leaving.delete(key);
25434
+ this.requestUpdate();
25435
+ });
25436
+ continue;
25437
+ }
25438
+ this.#cancelLeavingAnimation(key);
25439
+ this.#leavingAnimations.set(key, animation);
25440
+ result.then(() => {
25441
+ if (this.#leavingAnimations.get(key) !== animation) return;
25442
+ this.#leavingAnimations.delete(key);
24142
25443
  this.#leaving.delete(key);
24143
- this.#leavingAnimating.delete(key);
24144
25444
  this.requestUpdate();
25445
+ this.updateComplete.then(() => {
25446
+ try {
25447
+ animation.cancel();
25448
+ } catch {}
25449
+ });
24145
25450
  });
24146
25451
  }
24147
25452
  const visible = this.root ? this.#getAllNodes() : [];
@@ -24151,12 +25456,16 @@ let TreeBaseElement = class TreeBaseElement extends i {
24151
25456
  this.#treeAnimReady = true;
24152
25457
  }
24153
25458
  updated(changedProps) {
25459
+ this.#rowsCache = void 0;
24154
25460
  if (changedProps.has(`root`) && this.root) {
24155
25461
  this.#abortPending();
24156
25462
  this._expandedKeys.clear();
24157
25463
  this.#clearSelectionInternal();
24158
25464
  this.tickled.clearTickled();
24159
25465
  this._focusedDepth = 0;
25466
+ this.#cancelAllLeavingAnimations();
25467
+ this.#leaving.clear();
25468
+ this.#enteringKeys.clear();
24160
25469
  }
24161
25470
  if (changedProps.has(`model`)) {
24162
25471
  this.#modelUnsubscribe?.();
@@ -24204,13 +25513,307 @@ let TreeBaseElement = class TreeBaseElement extends i {
24204
25513
  });
24205
25514
  this.registry = registry;
24206
25515
  this.#syncSearchCommands();
25516
+ this.#initDnd();
25517
+ }
25518
+ #initDnd() {
25519
+ this.#dragSourceId = treeDragGenerateSourceId();
25520
+ this.#nativeDrag = new ListNativeDragController(this, {
25521
+ source: `tree-list`,
25522
+ isEnabled: () => this.itemDnd !== `none` && this.#anyRowDraggable(),
25523
+ resolveEventToKey: (target) => {
25524
+ if (!(target instanceof Element)) return void 0;
25525
+ return target.closest(`li[data-key]`)?.getAttribute(`data-key`) ?? void 0;
25526
+ },
25527
+ prepareSelection: (originKey) => {
25528
+ const allNodes = this.#getAllNodes();
25529
+ const originEntry = allNodes.find((n) => n.node.item.key === originKey);
25530
+ if (!originEntry) return void 0;
25531
+ const selectedNodes = new Set(this._selectedNodes);
25532
+ if (!selectedNodes.has(originEntry.node)) selectedNodes.clear();
25533
+ selectedNodes.add(originEntry.node);
25534
+ const orderedKeys = allNodes.filter((n) => selectedNodes.has(n.node)).map((n) => n.node.item.key);
25535
+ const topKeys = new Set(this.root ? pruneDraggedDescendants(this.root, orderedKeys) : orderedKeys);
25536
+ const selected = allNodes.filter((n) => topKeys.has(n.node.item.key)).map((n, i) => ({
25537
+ key: n.node.item.key,
25538
+ index: i,
25539
+ item: n.node,
25540
+ element: this.shadowRoot?.querySelector(`li[data-key="${CSS.escape(n.node.item.key)}"]`) ?? void 0
25541
+ }));
25542
+ const origin = selected.find((e) => e.key === originKey) ?? {
25543
+ key: originKey,
25544
+ index: 0,
25545
+ item: originEntry.node,
25546
+ element: void 0
25547
+ };
25548
+ if (!selected.some((e) => e.key === origin.key)) selected.unshift(origin);
25549
+ return {
25550
+ origin,
25551
+ selected
25552
+ };
25553
+ },
25554
+ getMetrics: () => ({ rowHeight: this.#getItemHeight() }),
25555
+ getRenderer: () => this.dragPreviewRenderer,
25556
+ dispatch: (detail) => {
25557
+ const keys = detail.selected.map((e) => e.key);
25558
+ const nodes = detail.selected.map((e) => e.item);
25559
+ const evt = new CustomEvent(`list-native-drag-start`, {
25560
+ detail,
25561
+ bubbles: true,
25562
+ composed: true,
25563
+ cancelable: true
25564
+ });
25565
+ const notCancelled = this.dispatchEvent(evt);
25566
+ if (notCancelled) {
25567
+ this.#nativeDragKeys = new Set(keys);
25568
+ this.#internalDropHandled = false;
25569
+ if (this.itemDnd === `internal` || this.itemDnd === `both`) treeDragRegister(this.#dragSourceId, this, nodes, keys);
25570
+ this.dispatchEvent(new CustomEvent(`tree-drag-start`, {
25571
+ detail: {
25572
+ nodes,
25573
+ keys
25574
+ },
25575
+ bubbles: true,
25576
+ composed: true
25577
+ }));
25578
+ }
25579
+ return notCancelled;
25580
+ },
25581
+ writeDataTransfer: (dataTransfer, detail) => {
25582
+ if (this.itemDnd !== `internal` && this.itemDnd !== `both`) return;
25583
+ const keys = detail.selected.map((e) => e.key);
25584
+ const nodes = detail.selected.map((e) => e.item);
25585
+ try {
25586
+ dataTransfer.setData(TREE_DRAG_MIME, JSON.stringify({
25587
+ sourceId: this.#dragSourceId,
25588
+ keys,
25589
+ nodes
25590
+ }));
25591
+ dataTransfer.setData(`text/plain`, nodes.map((n) => n.item.label).join(`\n`));
25592
+ } catch {}
25593
+ },
25594
+ onActiveChange: (active) => {
25595
+ this.toggleAttribute(`data-native-dragging`, active);
25596
+ if (!active) this.#nativeDragKeys = /* @__PURE__ */ new Set();
25597
+ this.requestUpdate();
25598
+ },
25599
+ onEnd: () => {
25600
+ treeDragUnregister(this.#dragSourceId);
25601
+ this.#nativeDragKeys = /* @__PURE__ */ new Set();
25602
+ this.#postDragSuppressTimer = setTimeout(() => {
25603
+ this.#postDragSuppressTimer = void 0;
25604
+ }, 150);
25605
+ if (!this.#internalDropHandled) this.dispatchEvent(new CustomEvent(`tree-drag-end`, {
25606
+ detail: { dropped: false },
25607
+ bubbles: true,
25608
+ composed: true
25609
+ }));
25610
+ this.#internalDropHandled = false;
25611
+ }
25612
+ });
25613
+ this.#drop = new TreeDropController(this, {
25614
+ getDndMode: () => this.itemDnd,
25615
+ getExpandHoverMs: () => this.dndExpandHoverMs,
25616
+ getVisibleRows: () => this.#getVisibleRows(),
25617
+ getIndentPx: () => this.#getIndentPx(),
25618
+ getScrollContainer: () => this.#container(),
25619
+ isSelfOrDescendant: (ancestorKey, key) => this.#isSelfOrDescendant(ancestorKey, key),
25620
+ isExpanded: (key) => this._expandedKeys.has(key),
25621
+ isReorderAllowed: () => this.reorderable,
25622
+ getParentKey: (key) => this.#findParentByKey(key)?.item.key,
25623
+ resolveDrag: (dataTransfer, phase) => this.#resolveDrag(dataTransfer, phase),
25624
+ vet: (ctx) => {
25625
+ if (this.canDrop && !this.canDrop(ctx)) return false;
25626
+ const evt = new CustomEvent(`tree-drag-over`, {
25627
+ detail: ctx,
25628
+ bubbles: true,
25629
+ composed: true,
25630
+ cancelable: true
25631
+ });
25632
+ return this.dispatchEvent(evt);
25633
+ },
25634
+ expandKey: (key) => this.#springLoadExpand(key),
25635
+ onDropStateChange: (state) => {
25636
+ this.#dropState = state;
25637
+ this.toggleAttribute(`data-drop-rejected`, this.#drop.isDragging && state === void 0);
25638
+ this.requestUpdate();
25639
+ },
25640
+ onActiveChange: (active) => {
25641
+ if (active) {
25642
+ this.#rowsCache = void 0;
25643
+ this.#container()?.addEventListener(`scroll`, this.#onContainerScroll, { passive: true });
25644
+ } else {
25645
+ this.#container()?.removeEventListener(`scroll`, this.#onContainerScroll);
25646
+ this.#rowsCache = void 0;
25647
+ this.toggleAttribute(`data-drop-rejected`, false);
25648
+ }
25649
+ this.requestUpdate();
25650
+ },
25651
+ commitDrop: (detail) => {
25652
+ if (!this.dispatchEvent(new CustomEvent(`tree-drop`, {
25653
+ detail,
25654
+ bubbles: true,
25655
+ composed: true,
25656
+ cancelable: true
25657
+ }))) return false;
25658
+ if (this.applyInternalDrop && detail.source === `internal` && this.model) this.model.applyDrop({
25659
+ source: `internal`,
25660
+ draggedKeys: detail.draggedKeys,
25661
+ target: detail.target
25662
+ });
25663
+ this.#internalDropHandled = true;
25664
+ return true;
25665
+ },
25666
+ onDragEnd: (dropped) => {
25667
+ this.#dropState = void 0;
25668
+ this.toggleAttribute(`data-drop-rejected`, false);
25669
+ this.requestUpdate();
25670
+ this.dispatchEvent(new CustomEvent(`tree-drag-end`, {
25671
+ detail: { dropped },
25672
+ bubbles: true,
25673
+ composed: true
25674
+ }));
25675
+ }
25676
+ });
25677
+ }
25678
+ #container() {
25679
+ return this.shadowRoot?.querySelector(`#container`) ?? void 0;
25680
+ }
25681
+ #getIndentPx() {
25682
+ const parsed = Number.parseInt(getComputedStyle(this).getPropertyValue(`--tree-indent`), 10);
25683
+ return Number.isNaN(parsed) ? 20 : parsed;
25684
+ }
25685
+ #getItemHeight() {
25686
+ const parsed = Number.parseInt(getComputedStyle(this).getPropertyValue(`--tree-item-height`), 10);
25687
+ return Number.isNaN(parsed) ? 30 : parsed;
25688
+ }
25689
+ /** Measured rows, cached for the duration of a drag and invalidated on scroll / update. */
25690
+ #getVisibleRows() {
25691
+ if (this.#rowsCache) return this.#rowsCache;
25692
+ const rows = [];
25693
+ for (const { node, depth } of this.#getAllNodes()) {
25694
+ const li = this.shadowRoot?.querySelector(`li[data-key="${CSS.escape(node.item.key)}"]`);
25695
+ if (li) rows.push({
25696
+ key: node.item.key,
25697
+ node,
25698
+ depth,
25699
+ rect: li.getBoundingClientRect()
25700
+ });
25701
+ }
25702
+ this.#rowsCache = rows;
25703
+ return rows;
25704
+ }
25705
+ /** Route spring-load through the real expand path so lazy children load and `expand` fires with the right depth. */
25706
+ #springLoadExpand(key) {
25707
+ if (this._expandedKeys.has(key)) return;
25708
+ const entry = this.#getAllNodes().find((n) => n.node.item.key === key);
25709
+ if (!entry || entry.node.item.isLeaf === true) return;
25710
+ this.#toggleExpand(entry.node, entry.depth);
25711
+ this.#rowsCache = void 0;
25712
+ this.requestUpdate();
25713
+ }
25714
+ #isSelfOrDescendant(ancestorKey, key) {
25715
+ if (ancestorKey === key) return true;
25716
+ let node = this.#getAllNodes().find((n) => n.node.item.key === key)?.node;
25717
+ let parent = node ? this.#findParentByKey(node.item.key) : void 0;
25718
+ while (parent) {
25719
+ if (parent.item.key === ancestorKey) return true;
25720
+ node = parent;
25721
+ parent = this.#findParentByKey(node.item.key);
25722
+ }
25723
+ return false;
25724
+ }
25725
+ #findParentByKey(key) {
25726
+ for (const { node } of this.#getAllNodes()) if (node.children?.some((c) => c.item.key === key)) return node;
25727
+ }
25728
+ /**
25729
+ * Decide whether a DataTransfer is acceptable and resolve the dragged set.
25730
+ * During `hover` the DataTransfer is read-protected, so an internal drag is
25731
+ * recovered from the module-level drag registry (there is only one at a time).
25732
+ */
25733
+ #resolveDrag(dataTransfer, phase) {
25734
+ const mode = this.itemDnd;
25735
+ if (mode === `none`) return void 0;
25736
+ const canInternal = mode === `internal` || mode === `both`;
25737
+ const canExternal = mode === `external` || mode === `both`;
25738
+ let types = [];
25739
+ try {
25740
+ types = Array.from(dataTransfer.types);
25741
+ } catch {}
25742
+ const current = treeDragCurrent();
25743
+ const looksInternal = types.includes(TREE_DRAG_MIME) || current !== void 0;
25744
+ if (canInternal && looksInternal) {
25745
+ if (current && current.keys.length > 0) {
25746
+ const keys = this.root ? pruneDraggedDescendants(this.root, current.keys) : current.keys;
25747
+ return {
25748
+ source: `internal`,
25749
+ keys,
25750
+ nodes: keys.map((k) => this.#getAllNodes().find((n) => n.node.item.key === k)?.node).filter((n) => n !== void 0)
25751
+ };
25752
+ }
25753
+ if (phase === `drop`) try {
25754
+ const raw = dataTransfer.getData(TREE_DRAG_MIME);
25755
+ if (raw) {
25756
+ const payload = JSON.parse(raw);
25757
+ return {
25758
+ source: `internal`,
25759
+ keys: payload.keys ?? [],
25760
+ nodes: payload.nodes ?? []
25761
+ };
25762
+ }
25763
+ } catch {}
25764
+ return {
25765
+ source: `internal`,
25766
+ keys: [],
25767
+ nodes: []
25768
+ };
25769
+ }
25770
+ if (canExternal && !looksInternal) {
25771
+ if (this.acceptDropTypes !== void 0) {
25772
+ const accepted = this.acceptDropTypes.trim().split(/\s+/).filter(Boolean);
25773
+ const present = Array.from(dataTransfer.types);
25774
+ if (accepted.length > 0 && !accepted.some((t) => present.includes(t))) return void 0;
25775
+ }
25776
+ if (phase === `drop`) {
25777
+ const nodes = this.parseExternalDrop?.(dataTransfer);
25778
+ if (!nodes || nodes.length === 0) return void 0;
25779
+ return {
25780
+ source: `external`,
25781
+ keys: nodes.map((n) => n.item.key),
25782
+ nodes
25783
+ };
25784
+ }
25785
+ return this.parseExternalDrop ? {
25786
+ source: `external`,
25787
+ keys: [],
25788
+ nodes: []
25789
+ } : void 0;
25790
+ }
25791
+ }
25792
+ #anyRowDraggable() {
25793
+ if (this.itemDnd === `none`) return false;
25794
+ const allNodes = this.#getAllNodes();
25795
+ if (allNodes.length === 0) return false;
25796
+ if (!this.canDrag) return true;
25797
+ return allNodes.some(({ node }) => this.canDrag({
25798
+ nodes: [node],
25799
+ keys: [node.item.key]
25800
+ }));
24207
25801
  }
24208
25802
  disconnectedCallback() {
24209
25803
  super.disconnectedCallback();
25804
+ this.#cancelAllLeavingAnimations();
25805
+ this.#leaving.clear();
25806
+ this.#enteringKeys.clear();
24210
25807
  this.#modelUnsubscribe?.();
24211
25808
  this.#modelUnsubscribe = void 0;
24212
25809
  this.#unregisterSearchCommands?.();
24213
25810
  this.#unregisterSearchCommands = void 0;
25811
+ if (this.#postDragSuppressTimer !== void 0) {
25812
+ clearTimeout(this.#postDragSuppressTimer);
25813
+ this.#postDragSuppressTimer = void 0;
25814
+ }
25815
+ this.#container()?.removeEventListener(`scroll`, this.#onContainerScroll);
25816
+ treeDragUnregister(this.#dragSourceId);
24214
25817
  if (this.#ownedRegistry) {
24215
25818
  this.#ownedRegistry.unregister(`select-all`);
24216
25819
  this.#ownedRegistry.unregister(`deselect-all`);
@@ -24385,6 +25988,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
24385
25988
  `;
24386
25989
  }
24387
25990
  _handleItemClick(node, depth, event) {
25991
+ if (this.#drop?.isDragging || this.#nativeDrag?.isDragging || this.#postDragSuppressTimer !== void 0) return;
24388
25992
  if (event && event.detail >= 2) return;
24389
25993
  if (this.#pendingNode && this._expandedKeys.has(this.#pendingNode.item.key)) this.#abortPending();
24390
25994
  this.tickled.handlePointerEnter(node.item.key);
@@ -24819,6 +26423,23 @@ let TreeBaseElement = class TreeBaseElement extends i {
24819
26423
  </ul>
24820
26424
  `;
24821
26425
  }
26426
+ /** Shared drag-and-drop render state for a row, used by every `_renderNode` variant. */
26427
+ _dndRowState(node) {
26428
+ const draggable = this.itemDnd !== `none` && (this.root?.children?.length ?? 0) > 0 && (this.itemDnd === `internal` || this.itemDnd === `both` || this.itemDnd === `external`) && (this.canDrag?.({
26429
+ nodes: [node],
26430
+ keys: [node.item.key]
26431
+ }) ?? true);
26432
+ const dragging = this.#nativeDragKeys.has(node.item.key);
26433
+ const state = this.#dropState;
26434
+ const dropTarget = state !== void 0 && state.referenceKey === node.item.key;
26435
+ return {
26436
+ draggable,
26437
+ dragging,
26438
+ dropTarget,
26439
+ dropPosition: dropTarget ? state.position : void 0,
26440
+ dropDepth: dropTarget ? state.depth : void 0
26441
+ };
26442
+ }
24822
26443
  _renderNode(node, depth) {
24823
26444
  const selected = this._isSelected(node);
24824
26445
  const tickled = this._isFocused(node);
@@ -24827,9 +26448,10 @@ let TreeBaseElement = class TreeBaseElement extends i {
24827
26448
  const isPending = this.#pendingNode === node;
24828
26449
  const isFiltered = this._isFilteredOut(node, depth);
24829
26450
  const isLeaving = this._isLeaving(node);
26451
+ const dnd = this._dndRowState(node);
24830
26452
  return b`
24831
26453
  <li
24832
- class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${isFiltered ? `filtered` : ``} ${isLeaving ? `leaving` : ``}"
26454
+ class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${isFiltered ? `filtered` : ``} ${isLeaving ? `leaving` : ``} ${dnd.dragging ? `dragging` : ``} ${dnd.dropTarget && dnd.dropPosition === `inside` ? `drop-into` : ``}"
24833
26455
  role="treeitem"
24834
26456
  aria-selected=${selected}
24835
26457
  aria-expanded=${isBranch ? isExpanded : A}
@@ -24837,6 +26459,8 @@ let TreeBaseElement = class TreeBaseElement extends i {
24837
26459
  aria-level=${depth + 1}
24838
26460
  data-key=${node.item.key}
24839
26461
  style=${`--depth: ${depth}`}
26462
+ draggable=${dnd.draggable ? `true` : A}
26463
+ part=${dnd.dropTarget ? `drop-target` : A}
24840
26464
  @click=${(e) => this._handleItemClick(node, depth, e)}
24841
26465
  @pointerenter=${() => {
24842
26466
  this.tickled.handlePointerEnter(node.item.key);
@@ -24850,6 +26474,11 @@ let TreeBaseElement = class TreeBaseElement extends i {
24850
26474
  }));
24851
26475
  }}
24852
26476
  >
26477
+ ${dnd.dropTarget && (dnd.dropPosition === `before` || dnd.dropPosition === `after`) ? b`<div
26478
+ class="drop-line drop-line-${dnd.dropPosition}"
26479
+ style="--drop-depth: ${dnd.dropDepth ?? depth}"
26480
+ part="drop-indicator"
26481
+ ></div>` : A}
24853
26482
  ${this.renderNodeContent(node, depth)}
24854
26483
  ${isPending ? this.renderLoadingIndicator() : A}
24855
26484
  </li>
@@ -24867,9 +26496,13 @@ let TreeBaseElement = class TreeBaseElement extends i {
24867
26496
  --tree-text: var(--surface-3-text);
24868
26497
  --tree-muted-text: var(--surface-muted-text);
24869
26498
  --tree-indent: 1ch;
24870
- --tree-item-height: 2ch;
26499
+ --tree-item-height: 30px;
24871
26500
  --tree-caret-size: 30px;
24872
26501
  --tree-radius: var(--radius-s);
26502
+ --tree-drop-line: var(--accent);
26503
+ --tree-drop-nub-size: 5px;
26504
+ --tree-drop-into-outline: 2px solid var(--accent);
26505
+ --tree-drop-into-bg: color-mix(in srgb, var(--accent) 14%, transparent);
24873
26506
 
24874
26507
  position: relative;
24875
26508
  display: block;
@@ -24897,10 +26530,11 @@ let TreeBaseElement = class TreeBaseElement extends i {
24897
26530
  }
24898
26531
 
24899
26532
  .item {
26533
+ position: relative; /* anchor for the .drop-line before/after indicator */
24900
26534
  display: flex;
24901
26535
  align-items: center;
24902
26536
  height: var(--tree-item-height);
24903
- padding-left: calc(var(--tree-indent) * var(--depth, 0) + var(--space-s));
26537
+ padding-left: calc(var(--tree-indent) * var(--depth, 0));
24904
26538
  cursor: pointer;
24905
26539
  user-select: none;
24906
26540
  font-family: var(--font-family);
@@ -24972,11 +26606,49 @@ let TreeBaseElement = class TreeBaseElement extends i {
24972
26606
  overflow: hidden;
24973
26607
  }
24974
26608
 
24975
- ::highlight(incr-search-highlight) {
24976
- background: var(--search-highlight-bg, var(--accent));
24977
- color: var(--search-highlight-color, var(--accent-text, #fff));
24978
- border-radius: 2px;
24979
- padding: 0 1px;
26609
+ /* Drag and drop styles */
26610
+ .item.dragging {
26611
+ opacity: 0.4;
26612
+ }
26613
+
26614
+ .item.drop-into {
26615
+ outline: var(--tree-drop-into-outline);
26616
+ outline-offset: -2px;
26617
+ background: var(--tree-drop-into-bg);
26618
+ }
26619
+
26620
+ .drop-line {
26621
+ position: absolute;
26622
+ left: calc(var(--tree-indent) * var(--drop-depth, 0) + var(--space-xs));
26623
+ right: var(--space-xs);
26624
+ height: 2px;
26625
+ background: var(--tree-drop-line);
26626
+ pointer-events: none;
26627
+ z-index: 10;
26628
+ }
26629
+
26630
+ /* Nub at the left end of the insertion line (VS Code style). */
26631
+ .drop-line::before {
26632
+ content: "";
26633
+ position: absolute;
26634
+ left: calc(-1 * var(--tree-drop-nub-size) / 2);
26635
+ top: calc(50% - var(--tree-drop-nub-size) / 2);
26636
+ width: var(--tree-drop-nub-size);
26637
+ height: var(--tree-drop-nub-size);
26638
+ border-radius: 50%;
26639
+ background: var(--tree-drop-line);
26640
+ }
26641
+
26642
+ .drop-line-before {
26643
+ top: -1px;
26644
+ }
26645
+
26646
+ .drop-line-after {
26647
+ bottom: -1px;
26648
+ }
26649
+
26650
+ :host([data-drop-rejected]) {
26651
+ cursor: not-allowed;
24980
26652
  }
24981
26653
  `
24982
26654
  ];
@@ -25010,6 +26682,31 @@ __decorate([property({
25010
26682
  attribute: `search-modes`,
25011
26683
  reflect: true
25012
26684
  })], TreeBaseElement.prototype, "searchModes", void 0);
26685
+ __decorate([property({
26686
+ reflect: true,
26687
+ attribute: `item-dnd`
26688
+ })], TreeBaseElement.prototype, "itemDnd", void 0);
26689
+ __decorate([property({
26690
+ type: String,
26691
+ attribute: `accept-drop-types`
26692
+ })], TreeBaseElement.prototype, "acceptDropTypes", void 0);
26693
+ __decorate([property({
26694
+ type: Number,
26695
+ attribute: `dnd-expand-hover-ms`
26696
+ })], TreeBaseElement.prototype, "dndExpandHoverMs", void 0);
26697
+ __decorate([property({
26698
+ reflect: true,
26699
+ attribute: `reorderable`,
26700
+ converter: {
26701
+ fromAttribute: (value) => value !== `false`,
26702
+ toAttribute: (value) => value ? null : `false`
26703
+ }
26704
+ })], TreeBaseElement.prototype, "reorderable", void 0);
26705
+ __decorate([property({ attribute: false })], TreeBaseElement.prototype, "dragPreviewRenderer", void 0);
26706
+ __decorate([property({ attribute: false })], TreeBaseElement.prototype, "canDrag", void 0);
26707
+ __decorate([property({ attribute: false })], TreeBaseElement.prototype, "canDrop", void 0);
26708
+ __decorate([property({ attribute: false })], TreeBaseElement.prototype, "parseExternalDrop", void 0);
26709
+ __decorate([property({ attribute: false })], TreeBaseElement.prototype, "applyInternalDrop", void 0);
25013
26710
  __decorate([state()], TreeBaseElement.prototype, "_expandedKeys", void 0);
25014
26711
  __decorate([state()], TreeBaseElement.prototype, "_focusedDepth", void 0);
25015
26712
  TreeBaseElement = __decorate([safeCustomElement(`ixfx-tree-base`)], TreeBaseElement);
@@ -25091,9 +26788,10 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
25091
26788
  const hasIconAtDepth = this.#depthsWithIcons.has(depth);
25092
26789
  const isLeaving = this._isLeaving(node);
25093
26790
  const isFiltered = this._isFilteredOut(node, depth);
26791
+ const dnd = this._dndRowState(node);
25094
26792
  return b`
25095
26793
  <li
25096
- class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``} ${isFiltered ? `filtered` : ``}"
26794
+ class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``} ${isFiltered ? `filtered` : ``} ${dnd.dragging ? `dragging` : ``} ${dnd.dropTarget && dnd.dropPosition === `inside` ? `drop-into` : ``}"
25097
26795
  data-key=${node.item.key}
25098
26796
  role="treeitem"
25099
26797
  aria-selected=${selected}
@@ -25101,9 +26799,16 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
25101
26799
  aria-hidden=${isLeaving ? `true` : A}
25102
26800
  aria-level=${depth + 1}
25103
26801
  style=${`--depth: ${depth}`}
26802
+ draggable=${dnd.draggable ? `true` : A}
26803
+ part=${dnd.dropTarget ? `drop-target` : A}
25104
26804
  @click=${(e) => this._handleItemClick(node, depth, e)}
25105
26805
  @pointerenter=${() => this.tickled.handlePointerEnter(node.item.key)}
25106
26806
  >
26807
+ ${dnd.dropTarget && (dnd.dropPosition === `before` || dnd.dropPosition === `after`) ? b`<div
26808
+ class="drop-line drop-line-${dnd.dropPosition}"
26809
+ style="--drop-depth: ${dnd.dropDepth ?? depth}"
26810
+ part="drop-indicator"
26811
+ ></div>` : A}
25107
26812
  ${this.renderNodeContent(node, depth)}
25108
26813
  ${isPending ? this.renderLoadingIndicator() : A}
25109
26814
  </li>
@@ -25371,6 +27076,7 @@ let VerticalListElement = class VerticalListElement extends i {
25371
27076
  this.selectionMode = `single`;
25372
27077
  this.interactionMode = `standard`;
25373
27078
  this.itemWrap = `stop`;
27079
+ this.itemsDraggable = false;
25374
27080
  this.#customWrappers = /* @__PURE__ */ new Map();
25375
27081
  this.#wrappedElements = /* @__PURE__ */ new Set();
25376
27082
  this.#animatedItems = /* @__PURE__ */ new WeakSet();
@@ -25401,7 +27107,41 @@ let VerticalListElement = class VerticalListElement extends i {
25401
27107
  },
25402
27108
  onStateChange: () => this._syncItemStates(),
25403
27109
  itemWrap: this.itemWrap,
25404
- hoverDisabled: () => this.#dragSelect.isDragging
27110
+ hoverDisabled: () => this.#dragSelect.isDragging || this.#nativeDrag.isDragging
27111
+ });
27112
+ this.#nativeDragging = false;
27113
+ this.#postDragSuppressTimer = 0;
27114
+ this.#nativeDrag = new ListNativeDragController(this, {
27115
+ source: `vertical-list`,
27116
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this._getVisibleItems().length > 0,
27117
+ resolveEventToKey: (target) => {
27118
+ if (!(target instanceof Element)) return void 0;
27119
+ const item = target.closest?.(ITEM_SELECTOR);
27120
+ if (!item || !this.contains(item) || this.#wrappedElements.has(item)) return void 0;
27121
+ return keyForElement(item);
27122
+ },
27123
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
27124
+ getMetrics: () => ({}),
27125
+ getRenderer: () => this.dragPreviewRenderer,
27126
+ dispatch: (detail) => this._dispatchCancelable(`list-native-drag-start`, detail),
27127
+ onActiveChange: (active, originKey) => {
27128
+ this.#nativeDragging = active;
27129
+ this.toggleAttribute(`data-native-dragging`, active);
27130
+ for (const el of this._getVisibleItems()) el.toggleAttribute(`data-drag-origin`, active && originKey !== void 0 && keyForElement(el) === originKey);
27131
+ if (active) {
27132
+ const count = this.selection.selected.size;
27133
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
27134
+ } else this.removeAttribute(`aria-label`);
27135
+ this.requestUpdate();
27136
+ },
27137
+ onEnd: () => {
27138
+ this.#nativeDragging = false;
27139
+ this.toggleAttribute(`data-native-dragging`, false);
27140
+ this.removeAttribute(`aria-label`);
27141
+ for (const el of this._getVisibleItems()) el.removeAttribute(`data-drag-origin`);
27142
+ this.#startPostDragSuppressTimer();
27143
+ },
27144
+ syncLightDomDraggable: () => this._syncNativeDraggable()
25405
27145
  });
25406
27146
  this.#dragSelect = new ListDragSelectController(this, {
25407
27147
  getSurface: () => this.renderRoot.querySelector(`.container`),
@@ -25483,7 +27223,7 @@ let VerticalListElement = class VerticalListElement extends i {
25483
27223
  }
25484
27224
  };
25485
27225
  this._onClick = (e) => {
25486
- if (this.#dragSelect.isDragging) return;
27226
+ if (this.#dragSelect.isDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
25487
27227
  const item = this._findListItem(e.composedPath());
25488
27228
  if (!item) return;
25489
27229
  this._dispatch(`list-item-click`, { item });
@@ -25500,6 +27240,44 @@ let VerticalListElement = class VerticalListElement extends i {
25500
27240
  #itemObserver;
25501
27241
  /** Items that have already played (or been skipped for) their enter animation. */
25502
27242
  #animatedItems;
27243
+ #nativeDragging;
27244
+ #postDragSuppressTimer;
27245
+ #nativeDrag;
27246
+ #prepareNativeDrag(originKey) {
27247
+ const visible = this._getVisibleItems();
27248
+ const originEl = visible.find((el) => keyForElement(el) === originKey);
27249
+ if (!originEl || !this._isCheckable(originEl)) return void 0;
27250
+ if (!this.selection.selected.has(originEl)) this.selection.replace(originEl);
27251
+ const selected = [];
27252
+ visible.forEach((el, index) => {
27253
+ if (!this.selection.selected.has(el)) return;
27254
+ selected.push({
27255
+ key: keyForElement(el) ?? originKey,
27256
+ index,
27257
+ item: el,
27258
+ element: el
27259
+ });
27260
+ });
27261
+ const origin = selected.find((entry) => entry.key === originKey);
27262
+ if (!origin) return void 0;
27263
+ return {
27264
+ origin,
27265
+ selected
27266
+ };
27267
+ }
27268
+ _syncNativeDraggable() {
27269
+ if (!this.isConnected) return;
27270
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
27271
+ if (this.#wrappedElements.has(el)) continue;
27272
+ if (el instanceof HTMLElement) el.draggable = this.itemsDraggable && this._isCheckable(el);
27273
+ }
27274
+ }
27275
+ #startPostDragSuppressTimer() {
27276
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
27277
+ this.#postDragSuppressTimer = window.setTimeout(() => {
27278
+ this.#postDragSuppressTimer = 0;
27279
+ }, 0);
27280
+ }
25503
27281
  /** Live drag-select preview rect (surface-relative); the covered items carry `data-marquee`. */
25504
27282
  #marqueeRect;
25505
27283
  #dragSelect;
@@ -25567,6 +27345,10 @@ let VerticalListElement = class VerticalListElement extends i {
25567
27345
  this.#unregisterSearchCommands?.();
25568
27346
  this.#unregisterSearchCommands = void 0;
25569
27347
  this.#searchRegistry = void 0;
27348
+ if (this.#postDragSuppressTimer) {
27349
+ clearTimeout(this.#postDragSuppressTimer);
27350
+ this.#postDragSuppressTimer = 0;
27351
+ }
25570
27352
  }
25571
27353
  /**
25572
27354
  * Animate in items appended to the light DOM (however they got there —
@@ -25606,6 +27388,10 @@ let VerticalListElement = class VerticalListElement extends i {
25606
27388
  }
25607
27389
  this._syncItemStates();
25608
27390
  } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
27391
+ if (changed.has(`itemsDraggable`) || changed.has(`interactionMode`)) {
27392
+ this._syncNativeDraggable();
27393
+ this.#nativeDrag.refreshDraggable();
27394
+ }
25609
27395
  }
25610
27396
  /** The most recently selected item, or `undefined` if nothing is selected. */
25611
27397
  get selectedItem() {
@@ -25875,6 +27661,14 @@ let VerticalListElement = class VerticalListElement extends i {
25875
27661
  composed: true
25876
27662
  }));
25877
27663
  }
27664
+ _dispatchCancelable(name, detail) {
27665
+ return this.dispatchEvent(new CustomEvent(name, {
27666
+ detail,
27667
+ bubbles: true,
27668
+ composed: true,
27669
+ cancelable: true
27670
+ }));
27671
+ }
25878
27672
  _activateItem(item) {
25879
27673
  if (this.interactionMode !== `manual`) this.selection.replace(item);
25880
27674
  this._dispatch(`list-activate`, { item });
@@ -26010,6 +27804,10 @@ let VerticalListElement = class VerticalListElement extends i {
26010
27804
  color: var(--item-text-selected);
26011
27805
  }
26012
27806
 
27807
+ ::slotted([data-drag-origin]) {
27808
+ opacity: 0.6;
27809
+ }
27810
+
26013
27811
  /* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */
26014
27812
  :host([interaction-mode="checked"]) ::slotted([data-selected]) {
26015
27813
  background: transparent;
@@ -26056,6 +27854,12 @@ __decorate([property({
26056
27854
  attribute: `search-modes`,
26057
27855
  reflect: true
26058
27856
  })], VerticalListElement.prototype, "searchModes", void 0);
27857
+ __decorate([property({
27858
+ type: Boolean,
27859
+ attribute: `items-draggable`,
27860
+ reflect: true
27861
+ })], VerticalListElement.prototype, "itemsDraggable", void 0);
27862
+ __decorate([property({ attribute: false })], VerticalListElement.prototype, "dragPreviewRenderer", void 0);
26059
27863
  VerticalListElement = __decorate([safeCustomElement(`ixfx-vertical-list`)], VerticalListElement);
26060
27864
  //#endregion
26061
27865
  //#region src/grouped-item-lister/vertical-list-adapter.ts
@@ -28542,6 +30346,77 @@ function valueToRelative(value, min, max) {
28542
30346
  const HANDLE_HIT_RADIUS = .06;
28543
30347
  const DEFAULT_VALUE_MIN_FRACTION = .01;
28544
30348
  const CHANGE_DEBOUNCE_MS = 150;
30349
+ /**
30350
+ * Evaluate a simple arithmetic expression (decimal numbers, `+ - * /` and
30351
+ * parentheses). Returns `undefined` when the string contains anything else.
30352
+ * Used for CSS `calc()`-derived values — a safe substitute for `new Function`.
30353
+ */
30354
+ function evaluateArithmetic(source) {
30355
+ let pos = 0;
30356
+ const skipSpaces = () => {
30357
+ while (pos < source.length) {
30358
+ const c = source[pos];
30359
+ if (c !== ` ` && c !== `\t` && c !== `\n` && c !== `\r`) break;
30360
+ pos++;
30361
+ }
30362
+ };
30363
+ function parseNumber() {
30364
+ skipSpaces();
30365
+ const match = /^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/i.exec(source.slice(pos));
30366
+ if (!match) return void 0;
30367
+ pos += match[0].length;
30368
+ return Number.parseFloat(match[0]);
30369
+ }
30370
+ function parseFactor() {
30371
+ skipSpaces();
30372
+ const c = source[pos];
30373
+ if (c === `+` || c === `-`) {
30374
+ pos++;
30375
+ const value = parseFactor();
30376
+ return value === void 0 ? void 0 : c === `-` ? -value : value;
30377
+ }
30378
+ if (c === `(`) {
30379
+ pos++;
30380
+ const value = parseSum();
30381
+ if (value === void 0) return void 0;
30382
+ skipSpaces();
30383
+ if (source[pos] !== `)`) return void 0;
30384
+ pos++;
30385
+ return value;
30386
+ }
30387
+ return parseNumber();
30388
+ }
30389
+ function parseProduct() {
30390
+ let value = parseFactor();
30391
+ if (value === void 0) return void 0;
30392
+ while (true) {
30393
+ skipSpaces();
30394
+ const op = source[pos];
30395
+ if (op !== `*` && op !== `/`) return value;
30396
+ pos++;
30397
+ const rhs = parseFactor();
30398
+ if (rhs === void 0) return void 0;
30399
+ value = op === `*` ? value * rhs : value / rhs;
30400
+ }
30401
+ }
30402
+ function parseSum() {
30403
+ let value = parseProduct();
30404
+ if (value === void 0) return void 0;
30405
+ while (true) {
30406
+ skipSpaces();
30407
+ const op = source[pos];
30408
+ if (op !== `+` && op !== `-`) return value;
30409
+ pos++;
30410
+ const rhs = parseProduct();
30411
+ if (rhs === void 0) return void 0;
30412
+ value = op === `+` ? value + rhs : value - rhs;
30413
+ }
30414
+ }
30415
+ const value = parseSum();
30416
+ if (value === void 0) return void 0;
30417
+ skipSpaces();
30418
+ return pos === source.length ? value : void 0;
30419
+ }
28545
30420
  let SliderInputElement = class SliderInputElement extends WheelNudgeMixin(i) {
28546
30421
  #lanes;
28547
30422
  /** Raw pole values: independent of which is "low" or "high". */
@@ -29455,14 +31330,10 @@ let SliderInputElement = class SliderInputElement extends WheelNudgeMixin(i) {
29455
31330
  const parsed = Number.parseFloat(val.replace(/[^0-9.-]/g, ``));
29456
31331
  return Number.isNaN(parsed) ? defaultValue : parsed;
29457
31332
  }
29458
- const evalStr = val.replace(/var\(--value-relative\)/g, valueRelative);
29459
- try {
29460
- const result = new Function(`return ${evalStr.replace(/calc\s*\(([^)]+)\)/g, `$1`)}`)();
29461
- return typeof result === `number` && !Number.isNaN(result) ? result : defaultValue;
29462
- } catch {
29463
- const parsed = Number.parseFloat(val.replace(/[^0-9.-]/g, ``));
29464
- return Number.isNaN(parsed) ? defaultValue : parsed;
29465
- }
31333
+ const result = evaluateArithmetic(val.replace(/var\(--value-relative\)/g, valueRelative).replace(/calc\s*\(([^)]+)\)/g, `$1`));
31334
+ if (result !== void 0) return Number.isNaN(result) ? defaultValue : result;
31335
+ const parsed = Number.parseFloat(val.replace(/[^0-9.-]/g, ``));
31336
+ return Number.isNaN(parsed) ? defaultValue : parsed;
29466
31337
  }
29467
31338
  const parsed = Number.parseFloat(val);
29468
31339
  return Number.isNaN(parsed) ? defaultValue : parsed;
@@ -29539,10 +31410,10 @@ let SliderInputElement = class SliderInputElement extends WheelNudgeMixin(i) {
29539
31410
  proposedHigh = clamp$1(proposedHigh, this.min, this.max);
29540
31411
  }
29541
31412
  const lowKey = this.#lengthDragLowKey ?? this.#lowKey();
29542
- this.#lengthDragHighKey ?? this.#highKey();
31413
+ const highKey = this.#lengthDragHighKey ?? this.#highKey();
29543
31414
  const previous = this.#poles();
29544
31415
  const newPoleA = lowKey === `a` ? proposedLow : proposedHigh;
29545
- const newPoleB = lowKey === `b` ? proposedLow : proposedHigh;
31416
+ const newPoleB = highKey === `b` ? proposedHigh : proposedLow;
29546
31417
  this.#poleA = newPoleA;
29547
31418
  this.#poleB = newPoleB;
29548
31419
  if (this.valueMode === `triple` && this.#poleC !== void 0) {
@@ -33161,7 +35032,7 @@ function getScaleSteps(palette) {
33161
35032
  function resolveCssVariable(el, prop) {
33162
35033
  const style = getComputedStyle(el);
33163
35034
  let value = style.getPropertyValue(prop).trim();
33164
- const varRegex = /var\((--[\w-]+)(?:,\s*(.*))?\)/g;
35035
+ const varRegex = /var\((--[\w-]+)(?:,(.*))?\)/g;
33165
35036
  for (let i = 0; i < 5 && value.includes(`var(`); i++) value = value.replace(varRegex, (_, varName, fallback) => {
33166
35037
  const resolved = style.getPropertyValue(varName).trim();
33167
35038
  if (resolved) return resolved;
@@ -42587,6 +44458,6 @@ function init() {
42587
44458
  console.log(`Init`);
42588
44459
  }
42589
44460
  //#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 };
44461
+ 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, TreeDropController, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, applyTreeDrop, 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, dropTargetFor, 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, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
42591
44462
 
42592
44463
  //# sourceMappingURL=index.js.map