@ixfx/components 0.8.6 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/bundle/index.d.ts +570 -389
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +1033 -200
  4. package/bundle/index.js.map +1 -1
  5. package/dist/crumbs.d.ts +1 -1
  6. package/dist/crumbs.js +2 -2
  7. package/dist/{data-grid-D-z7bx5-.d.ts → data-grid-BKa9i86q.d.ts} +15 -2
  8. package/dist/data-grid-BKa9i86q.d.ts.map +1 -0
  9. package/dist/data-grid.d.ts +3 -2
  10. package/dist/data-grid.js +95 -5
  11. package/dist/data-grid.js.map +1 -1
  12. package/dist/{detail-list-_ZJHYEBr.d.ts → detail-list-CHTomlbR.d.ts} +22 -5
  13. package/dist/detail-list-CHTomlbR.d.ts.map +1 -0
  14. package/dist/detail-list.d.ts +4 -3
  15. package/dist/detail-list.js +103 -3
  16. package/dist/detail-list.js.map +1 -1
  17. package/dist/{grid-list-j5C7qtlG.js → grid-list-C8oMxLl-.js} +77 -164
  18. package/dist/grid-list-C8oMxLl-.js.map +1 -0
  19. package/dist/{grid-list-x550wJ0X.d.ts → grid-list-CsZcXxMC.d.ts} +259 -109
  20. package/dist/grid-list-CsZcXxMC.d.ts.map +1 -0
  21. package/dist/grid-list.d.ts +3 -2
  22. package/dist/grid-list.js +1 -1
  23. package/dist/incr-search.d.ts +1 -1
  24. package/dist/{index-h6gxtfHH.d.ts → index-Dgrx-DIA.d.ts} +2 -2
  25. package/dist/{index-h6gxtfHH.d.ts.map → index-Dgrx-DIA.d.ts.map} +1 -1
  26. package/dist/index.d.ts +190 -252
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +8 -7
  29. package/dist/index.js.map +1 -1
  30. package/dist/{interaction-B9AAlsFO.js → interaction-BskmlA9t.js} +1 -1
  31. package/dist/list-drag-types-kpaXz2pZ.d.ts +42 -0
  32. package/dist/list-drag-types-kpaXz2pZ.d.ts.map +1 -0
  33. package/dist/{list-item-animation-DuiwgN3l.js → list-item-animation-BUoEKi5s.js} +29 -10
  34. package/dist/list-item-animation-BUoEKi5s.js.map +1 -0
  35. package/dist/list-native-drag-controller-DrY3qeJM.js +555 -0
  36. package/dist/list-native-drag-controller-DrY3qeJM.js.map +1 -0
  37. package/dist/{menu-HMoxCPY2.js → menu-CMXhIaNZ.js} +3 -3
  38. package/dist/{menu-HMoxCPY2.js.map → menu-CMXhIaNZ.js.map} +1 -1
  39. package/dist/{menu-item-BQDYxk5A.js → menu-item-BYGQzyCe.js} +2 -2
  40. package/dist/{menu-item-BQDYxk5A.js.map → menu-item-BYGQzyCe.js.map} +1 -1
  41. package/dist/menu.js +2 -2
  42. package/dist/miller.d.ts +2 -2
  43. package/dist/miller.js +1 -1
  44. package/dist/narrowed-text.js +1 -1
  45. package/dist/select-horiz.js +1 -1
  46. package/dist/{tree-DTQon838.js → tree-DSMn566I.js} +90 -27
  47. package/dist/tree-DSMn566I.js.map +1 -0
  48. package/dist/{tree-component-F6qZ0Gdv.d.ts → tree-component-nPP5Y-WS.d.ts} +12 -2
  49. package/dist/tree-component-nPP5Y-WS.d.ts.map +1 -0
  50. package/dist/tree.d.ts +3 -2
  51. package/dist/tree.d.ts.map +1 -1
  52. package/dist/tree.js +1 -1
  53. package/dist/{vertical-list-CFuFDygi.d.ts → vertical-list-A6oWfivZ.d.ts} +21 -4
  54. package/dist/vertical-list-A6oWfivZ.d.ts.map +1 -0
  55. package/dist/vertical-list.d.ts +3 -2
  56. package/dist/vertical-list.js +103 -3
  57. package/dist/vertical-list.js.map +1 -1
  58. package/docs-user/README.md +1 -0
  59. package/docs-user/data-grid.md +30 -0
  60. package/docs-user/data-table.md +104 -0
  61. package/docs-user/detail-list.md +29 -0
  62. package/docs-user/grid-list.md +34 -4
  63. package/docs-user/index.json +7 -2
  64. package/docs-user/llms.txt +1 -0
  65. package/docs-user/tree.md +5 -4
  66. package/docs-user/vertical-list.md +30 -1
  67. package/llms.txt +1 -0
  68. package/package.json +1 -1
  69. package/dist/data-grid-D-z7bx5-.d.ts.map +0 -1
  70. package/dist/detail-list-_ZJHYEBr.d.ts.map +0 -1
  71. package/dist/grid-list-j5C7qtlG.js.map +0 -1
  72. package/dist/grid-list-x550wJ0X.d.ts.map +0 -1
  73. package/dist/list-item-animation-DuiwgN3l.js.map +0 -1
  74. package/dist/tree-DTQon838.js.map +0 -1
  75. package/dist/tree-component-F6qZ0Gdv.d.ts.map +0 -1
  76. package/dist/vertical-list-CFuFDygi.d.ts.map +0 -1
@@ -1,3 +1,3 @@
1
1
  import "./tickled-controller-Dh1Uh_vG.js";
2
2
  import "./tickled-styles-BrAHEj1q.js";
3
- import "./list-item-animation-DuiwgN3l.js";
3
+ import "./list-item-animation-BUoEKi5s.js";
@@ -0,0 +1,42 @@
1
+ //#region src/interaction/list-drag-types.d.ts
2
+ interface ListDragEntry<T> {
3
+ readonly key: string;
4
+ readonly index: number;
5
+ readonly item: T | Element;
6
+ readonly element?: Element;
7
+ }
8
+ type ListDragSource = `grid-list` | `detail-list` | `vertical-list` | `data-grid`;
9
+ interface ListDragPreviewContext<T> {
10
+ readonly source: ListDragSource;
11
+ readonly origin: ListDragEntry<T>;
12
+ readonly selected: ReadonlyArray<ListDragEntry<T>>;
13
+ readonly metrics: {
14
+ readonly itemSize?: number;
15
+ readonly captionStyle?: string;
16
+ readonly captionHeight?: number;
17
+ readonly rowHeight?: number;
18
+ };
19
+ readonly hostStyle: CSSStyleDeclaration;
20
+ }
21
+ interface ListDragPreview {
22
+ readonly element: HTMLElement;
23
+ readonly hotspot?: Readonly<{
24
+ x: number;
25
+ y: number;
26
+ }>;
27
+ readonly canvas?: HTMLCanvasElement;
28
+ readonly dispose?: () => void;
29
+ }
30
+ type ListDragPreviewRenderer<T> = (context: ListDragPreviewContext<T>) => ListDragPreview;
31
+ interface ListNativeDragStartDetail<T = unknown> {
32
+ readonly key: string;
33
+ readonly index: number;
34
+ readonly item: T | Element;
35
+ readonly selectedKeys: readonly string[];
36
+ readonly selected: ReadonlyArray<ListDragEntry<T>>;
37
+ readonly preview?: ListDragPreview;
38
+ }
39
+ type ListNativeDragStartEvent<T = unknown> = CustomEvent<ListNativeDragStartDetail<T>>;
40
+ //#endregion
41
+ export { ListDragSource as a, ListDragPreviewRenderer as i, ListDragPreview as n, ListNativeDragStartDetail as o, ListDragPreviewContext as r, ListNativeDragStartEvent as s, ListDragEntry as t };
42
+ //# sourceMappingURL=list-drag-types-kpaXz2pZ.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-drag-types-kpaXz2pZ.d.ts","names":[],"sources":["../src/interaction/list-drag-types.ts"],"mappings":";UAAiB,cAAc;WACpB;WACA;WACA,MAAM,IAAI;WACV,UAAU;;KAGT;UAEK,uBAAuB;WAC7B,QAAQ;WACR,QAAQ,cAAc;WACtB,UAAU,cAAc,cAAc;WACtC;aACE;aACA;aACA;aACA;;WAEF,WAAW;;UAGL;WACN,SAAS;WACT,UAAU;IAAW;IAAW;;WAChC,SAAS;WACT;;KAGC,wBAAwB,MAAM,SAAS,uBAAuB,OAAO;UAEhE,0BAA0B;WAChC;WACA;WACA,MAAM,IAAI;WACV;WACA,UAAU,cAAc,cAAc;WACtC,UAAU;;KAGT,yBAAyB,eAAe,YAAY,0BAA0B"}
@@ -88,25 +88,44 @@ function animateListItemIn(el, options = {}) {
88
88
  easing
89
89
  });
90
90
  }
91
+ function outResult(finished, animation) {
92
+ return Object.assign(finished, { animation });
93
+ }
91
94
  /**
92
- * Collapse + fade `el` out. Resolves once the animation finishes (or straight
93
- * away under reduced motion); the caller detaches the element after. The final
94
- * collapsed state is held (`fill: forwards`) so there's no flash back to full
95
- * size in the gap before removal.
95
+ * Start the collapse + fade-out animation for `el` and return the handle.
96
+ * Returns `undefined` when no animation was started (reduced motion or zero
97
+ * size). The caller owns the handle and must `cancel()` it if the removal is
98
+ * reversed a retained `fill: forwards` frame otherwise keeps overriding the
99
+ * element's ordinary CSS indefinitely.
96
100
  */
97
- function animateListItemOut(el, options = {}) {
98
- if (prefersReducedMotion()) return Promise.resolve();
101
+ function startListItemOutAnimation(el, options = {}) {
102
+ if (prefersReducedMotion()) return void 0;
99
103
  const { axis = `block`, easing = `ease` } = options;
100
104
  const duration = resolveDuration(el, options.duration);
101
105
  const size = measure(el, axis);
102
- if (size <= 0) return Promise.resolve();
106
+ if (size <= 0) return void 0;
103
107
  return el.animate([fullFrame(size, axis), collapsedFrame(axis)], {
104
108
  duration,
105
109
  easing,
106
110
  fill: `forwards`
107
- }).finished.then(() => void 0, () => void 0);
111
+ });
112
+ }
113
+ /**
114
+ * Collapse + fade `el` out. Resolves once the animation finishes (or straight
115
+ * away under reduced motion); the caller detaches the element after. The final
116
+ * collapsed state is held (`fill: forwards`) so there's no flash back to full
117
+ * size in the gap before removal.
118
+ *
119
+ * The returned promise carries an `.animation` handle. If the removal is
120
+ * reversed (the same key becomes visible again), cancel that handle so its
121
+ * filled final frame stops overriding the reused element's style.
122
+ */
123
+ function animateListItemOut(el, options = {}) {
124
+ const animation = startListItemOutAnimation(el, options);
125
+ if (!animation) return outResult(Promise.resolve(), void 0);
126
+ return outResult(animation.finished.then(() => void 0, () => void 0), animation);
108
127
  }
109
128
  //#endregion
110
- export { prefersReducedMotion as a, listItemFadeKeyframes as i, animateListItemIn as n, animateListItemOut as r, LIST_ITEM_MOTION_MS as t };
129
+ export { prefersReducedMotion as a, listItemFadeKeyframes as i, animateListItemIn as n, startListItemOutAnimation as o, animateListItemOut as r, LIST_ITEM_MOTION_MS as t };
111
130
 
112
- //# sourceMappingURL=list-item-animation-DuiwgN3l.js.map
131
+ //# sourceMappingURL=list-item-animation-BUoEKi5s.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-item-animation-BUoEKi5s.js","names":[],"sources":["../src/interaction/list-item-animation.ts"],"sourcesContent":["import { css } from 'lit';\n\n/**\n * Shared insertion / removal animation for the list & tree components.\n *\n * Two flavours, because the components split into two layout families:\n *\n * - **Flow layouts** (`ixfx-vertical-list`, `ixfx-detail-list`, `ixfx-tree-*`) —\n * rows sit in normal flow, so a new row can collapse in from zero height and a\n * leaving row can collapse back to zero, and the rows around it slide to make\n * or close the gap for free. Use {@link animateListItemIn} /\n * {@link animateListItemOut} (Web Animations API, exact measured sizes).\n *\n * - **Virtualised layouts** (`ixfx-grid-list`, `ixfx-data-grid`) — rows are\n * absolutely positioned (`transform` / `top`), so collapsing a row's box moves\n * nothing. Those components instead fade the entering / leaving row with the\n * {@link listItemFadeKeyframes} keyframes and slide the survivors by\n * transitioning their position for a short window after the change.\n *\n * Everything here is a no-op under `prefers-reduced-motion: reduce`.\n */\n\n/** Default duration (ms) for both flavours; overridable per call / per component. */\nexport const LIST_ITEM_MOTION_MS = 200;\n\nexport function prefersReducedMotion(): boolean {\n return (\n typeof window !== `undefined`\n && typeof window.matchMedia === `function`\n && window.matchMedia(`(prefers-reduced-motion: reduce)`).matches\n );\n}\n\n/**\n * Opacity keyframes for the virtualised components. Each one adds its own\n * one-line rule binding `.<row>.ixfx-item-entering` / `.ixfx-item-leaving` to\n * these, plus a `prefers-reduced-motion` guard on its own selectors.\n */\nexport const listItemFadeKeyframes = css`\n @keyframes ixfx-list-item-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @keyframes ixfx-list-item-out {\n from { opacity: 1; }\n to { opacity: 0; }\n }\n`;\n\nexport interface ListItemMotionOptions {\n /**\n * Animation duration in ms. When omitted, the element's\n * `--ixfx-list-item-move-duration` custom property is used if set (accepts\n * `ms` or `s`), otherwise {@link LIST_ITEM_MOTION_MS}.\n */\n duration?: number;\n /** Which axis collapses — `block` (height, default) or `inline` (width). */\n axis?: `block` | `inline`;\n easing?: string;\n}\n\nfunction measure(el: HTMLElement, axis: `block` | `inline`): number {\n const rect = el.getBoundingClientRect();\n return axis === `inline` ? rect.width : rect.height;\n}\n\nfunction resolveDuration(el: HTMLElement, explicit?: number): number {\n if (explicit !== undefined)\n return explicit;\n const raw = getComputedStyle(el).getPropertyValue(`--ixfx-list-item-move-duration`).trim();\n if (raw) {\n const n = Number.parseFloat(raw);\n if (!Number.isNaN(n))\n return raw.endsWith(`ms`) ? n : raw.endsWith(`s`) ? n * 1000 : n;\n }\n return LIST_ITEM_MOTION_MS;\n}\n\nfunction collapsedFrame(axis: `block` | `inline`): Keyframe {\n const prop = axis === `inline` ? `width` : `height`;\n const minProp = axis === `inline` ? `minWidth` : `minHeight`;\n return { [prop]: `0px`, [minProp]: `0px`, opacity: 0, overflow: `hidden` };\n}\n\nfunction fullFrame(size: number, axis: `block` | `inline`): Keyframe {\n const prop = axis === `inline` ? `width` : `height`;\n const minProp = axis === `inline` ? `minWidth` : `minHeight`;\n return { [prop]: `${size}px`, [minProp]: `0px`, opacity: 1, overflow: `hidden` };\n}\n\n/**\n * Collapse + fade `el` in from nothing to its natural size. Call it just after\n * the element has been inserted (it must already be laid out at full size so it\n * can be measured). No-op under reduced motion.\n */\nexport function animateListItemIn(el: HTMLElement, options: ListItemMotionOptions = {}): Animation | undefined {\n if (prefersReducedMotion())\n return undefined;\n const { axis = `block`, easing = `ease` } = options;\n const duration = resolveDuration(el, options.duration);\n const size = measure(el, axis);\n if (size <= 0)\n return undefined;\n return el.animate([collapsedFrame(axis), fullFrame(size, axis)], { duration, easing });\n}\n\n/**\n * Result of {@link animateListItemOut}: a promise that resolves once the\n * animation finishes, carrying the underlying `Animation` handle (if any) so\n * callers can `cancel()` superseded work. Awaiting / `.then()` behaves like\n * the previous `Promise<void>` return type.\n */\nexport type ListItemOutResult = Promise<void> & {\n /** The running collapse animation, or `undefined` under reduced motion / zero size. */\n animation: Animation | undefined;\n};\n\nfunction outResult(finished: Promise<void>, animation: Animation | undefined): ListItemOutResult {\n return Object.assign(finished, { animation });\n}\n\n/**\n * Start the collapse + fade-out animation for `el` and return the handle.\n * Returns `undefined` when no animation was started (reduced motion or zero\n * size). The caller owns the handle and must `cancel()` it if the removal is\n * reversed — a retained `fill: forwards` frame otherwise keeps overriding the\n * element's ordinary CSS indefinitely.\n */\nexport function startListItemOutAnimation(el: HTMLElement, options: ListItemMotionOptions = {}): Animation | undefined {\n if (prefersReducedMotion())\n return undefined;\n const { axis = `block`, easing = `ease` } = options;\n const duration = resolveDuration(el, options.duration);\n const size = measure(el, axis);\n if (size <= 0)\n return undefined;\n return el.animate([fullFrame(size, axis), collapsedFrame(axis)], {\n duration,\n easing,\n fill: `forwards`,\n });\n}\n\n/**\n * Collapse + fade `el` out. Resolves once the animation finishes (or straight\n * away under reduced motion); the caller detaches the element after. The final\n * collapsed state is held (`fill: forwards`) so there's no flash back to full\n * size in the gap before removal.\n *\n * The returned promise carries an `.animation` handle. If the removal is\n * reversed (the same key becomes visible again), cancel that handle so its\n * filled final frame stops overriding the reused element's style.\n */\nexport function animateListItemOut(el: HTMLElement, options: ListItemMotionOptions = {}): ListItemOutResult {\n const animation = startListItemOutAnimation(el, options);\n if (!animation)\n return outResult(Promise.resolve(), undefined);\n const finished = animation.finished.then(() => undefined, () => undefined);\n return outResult(finished, animation);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,sBAAsB;AAEnC,SAAgB,uBAAgC;CAC9C,OACE,OAAO,WAAW,eACf,OAAO,OAAO,eAAe,cAC7B,OAAO,WAAW,kCAAkC,CAAC,CAAC;AAE7D;;;;;;AAOA,MAAa,wBAAwB,GAAG;;;;;;;;;;AAuBxC,SAAS,QAAQ,IAAiB,MAAkC;CAClE,MAAM,OAAO,GAAG,sBAAsB;CACtC,OAAO,SAAS,WAAW,KAAK,QAAQ,KAAK;AAC/C;AAEA,SAAS,gBAAgB,IAAiB,UAA2B;CACnE,IAAI,aAAa,KAAA,GACf,OAAO;CACT,MAAM,MAAM,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,gCAAgC,CAAC,CAAC,KAAK;CACzF,IAAI,KAAK;EACP,MAAM,IAAI,OAAO,WAAW,GAAG;EAC/B,IAAI,CAAC,OAAO,MAAM,CAAC,GACjB,OAAO,IAAI,SAAS,IAAI,IAAI,IAAI,IAAI,SAAS,GAAG,IAAI,IAAI,MAAO;CACnE;CACA,OAAA;AACF;AAEA,SAAS,eAAe,MAAoC;CAC1D,MAAM,OAAO,SAAS,WAAW,UAAU;CAC3C,MAAM,UAAU,SAAS,WAAW,aAAa;CACjD,OAAO;GAAG,OAAO;GAAQ,UAAU;EAAO,SAAS;EAAG,UAAU;CAAS;AAC3E;AAEA,SAAS,UAAU,MAAc,MAAoC;CACnE,MAAM,OAAO,SAAS,WAAW,UAAU;CAC3C,MAAM,UAAU,SAAS,WAAW,aAAa;CACjD,OAAO;GAAG,OAAO,GAAG,KAAK;GAAM,UAAU;EAAO,SAAS;EAAG,UAAU;CAAS;AACjF;;;;;;AAOA,SAAgB,kBAAkB,IAAiB,UAAiC,CAAC,GAA0B;CAC7G,IAAI,qBAAqB,GACvB,OAAO,KAAA;CACT,MAAM,EAAE,OAAO,SAAS,SAAS,WAAW;CAC5C,MAAM,WAAW,gBAAgB,IAAI,QAAQ,QAAQ;CACrD,MAAM,OAAO,QAAQ,IAAI,IAAI;CAC7B,IAAI,QAAQ,GACV,OAAO,KAAA;CACT,OAAO,GAAG,QAAQ,CAAC,eAAe,IAAI,GAAG,UAAU,MAAM,IAAI,CAAC,GAAG;EAAE;EAAU;CAAO,CAAC;AACvF;AAaA,SAAS,UAAU,UAAyB,WAAqD;CAC/F,OAAO,OAAO,OAAO,UAAU,EAAE,UAAU,CAAC;AAC9C;;;;;;;;AASA,SAAgB,0BAA0B,IAAiB,UAAiC,CAAC,GAA0B;CACrH,IAAI,qBAAqB,GACvB,OAAO,KAAA;CACT,MAAM,EAAE,OAAO,SAAS,SAAS,WAAW;CAC5C,MAAM,WAAW,gBAAgB,IAAI,QAAQ,QAAQ;CACrD,MAAM,OAAO,QAAQ,IAAI,IAAI;CAC7B,IAAI,QAAQ,GACV,OAAO,KAAA;CACT,OAAO,GAAG,QAAQ,CAAC,UAAU,MAAM,IAAI,GAAG,eAAe,IAAI,CAAC,GAAG;EAC/D;EACA;EACA,MAAM;CACR,CAAC;AACH;;;;;;;;;;;AAYA,SAAgB,mBAAmB,IAAiB,UAAiC,CAAC,GAAsB;CAC1G,MAAM,YAAY,0BAA0B,IAAI,OAAO;CACvD,IAAI,CAAC,WACH,OAAO,UAAU,QAAQ,QAAQ,GAAG,KAAA,CAAS;CAE/C,OAAO,UADU,UAAU,SAAS,WAAW,KAAA,SAAiB,KAAA,CACxC,GAAG,SAAS;AACtC"}
@@ -0,0 +1,555 @@
1
+ //#region src/interaction/list-drag-preview-default.ts
2
+ const CARD_DEFAULT = 72;
3
+ const CARD_MIN = 48;
4
+ const CARD_MAX = 96;
5
+ const LIST_WIDTH = 220;
6
+ const LIST_MAX_ROWS = 5;
7
+ const MAX_CANVAS_DIM = 1024;
8
+ function cssVar(style, name, fallback) {
9
+ try {
10
+ return style.getPropertyValue(name)?.trim() || fallback;
11
+ } catch {
12
+ return fallback;
13
+ }
14
+ }
15
+ function fontOf(style) {
16
+ try {
17
+ const font = style.font?.trim();
18
+ if (font) return font;
19
+ } catch {}
20
+ return `12px system-ui, sans-serif`;
21
+ }
22
+ /** Caption for a drag entry — element labels win, then string items, then common object fields, then the key. */
23
+ function captionForEntry(entry) {
24
+ const el = entry.element ?? (entry.item instanceof Element ? entry.item : void 0);
25
+ if (el) {
26
+ const labelled = el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.getAttribute(`data-search-label`) ?? el.querySelector(`.cell`)?.textContent?.trim();
27
+ if (labelled) return labelled.slice(0, 80);
28
+ const text = el.textContent?.trim();
29
+ if (text) return text.slice(0, 80);
30
+ }
31
+ const item = entry.item;
32
+ if (typeof item === `string` && item.length > 0) return item.slice(0, 80);
33
+ if (item !== null && typeof item === `object` && !(item instanceof Element)) {
34
+ const record = item;
35
+ for (const field of [
36
+ `label`,
37
+ `name`,
38
+ `title`,
39
+ `text`
40
+ ]) {
41
+ const value = record[field];
42
+ if (typeof value === `string` && value.length > 0) return value.slice(0, 80);
43
+ }
44
+ }
45
+ return entry.key;
46
+ }
47
+ function glyphFor(caption) {
48
+ const first = caption.trim().at(0);
49
+ return first ? first.toLocaleUpperCase() : `?`;
50
+ }
51
+ /** A decoded `<img>` ready for `drawImage`, if the entry's element has one. */
52
+ function decodedImage(entry) {
53
+ const scope = entry.element ?? (entry.item instanceof Element ? entry.item : void 0);
54
+ if (!scope || typeof scope.querySelector !== `function`) return void 0;
55
+ if (scope instanceof HTMLImageElement) {
56
+ if (scope.complete && scope.naturalWidth > 0) return scope;
57
+ return;
58
+ }
59
+ try {
60
+ const img = scope.querySelector(`img`);
61
+ if (img instanceof HTMLImageElement && img.complete && img.naturalWidth > 0) return img;
62
+ } catch {}
63
+ }
64
+ function cardSize(metrics) {
65
+ if (metrics.itemSize !== void 0 && Number.isFinite(metrics.itemSize)) return Math.max(CARD_MIN, Math.min(CARD_MAX, Math.round(metrics.itemSize)));
66
+ return CARD_DEFAULT;
67
+ }
68
+ function rowHeight(metrics) {
69
+ const raw = metrics.rowHeight;
70
+ if (raw !== void 0 && Number.isFinite(raw)) return Math.max(24, Math.min(36, Math.round(raw)));
71
+ return 28;
72
+ }
73
+ function thumbCell(doc, caption, img, size) {
74
+ const well = doc.createElement(`div`);
75
+ well.setAttribute(`data-thumb`, ``);
76
+ 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;`;
77
+ if (img) try {
78
+ const clone = img.cloneNode(true);
79
+ clone.style.cssText = `width:100%;height:100%;object-fit:cover;`;
80
+ clone.removeAttribute(`id`);
81
+ well.appendChild(clone);
82
+ } catch {
83
+ well.textContent = glyphFor(caption);
84
+ }
85
+ else well.textContent = glyphFor(caption);
86
+ return well;
87
+ }
88
+ function buildCardsElement(ctx, captions, images) {
89
+ const doc = document;
90
+ const size = cardSize(ctx.metrics);
91
+ const gap = 6;
92
+ const pad = 8;
93
+ const root = doc.createElement(`div`);
94
+ root.setAttribute(`data-preview`, `cards`);
95
+ root.setAttribute(`part`, `drag-preview`);
96
+ 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;`;
97
+ const n = ctx.selected.length;
98
+ for (let i = 0; i < n; i++) {
99
+ const card = doc.createElement(`div`);
100
+ card.setAttribute(`data-card`, ``);
101
+ card.style.cssText = `display:flex;flex-direction:column;gap:4px;align-items:center;width:${size}px;`;
102
+ card.appendChild(thumbCell(doc, captions[i], images[i], Math.round(size * .72)));
103
+ const label = doc.createElement(`span`);
104
+ label.setAttribute(`data-caption`, ``);
105
+ label.style.cssText = `max-width:${size}px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;opacity:0.9;`;
106
+ label.textContent = captions[i];
107
+ card.appendChild(label);
108
+ root.appendChild(card);
109
+ }
110
+ return root;
111
+ }
112
+ function buildListElement(ctx, captions, images) {
113
+ const doc = document;
114
+ const rh = rowHeight(ctx.metrics);
115
+ const thumb = Math.round(rh * .78);
116
+ const root = doc.createElement(`div`);
117
+ root.setAttribute(`data-preview`, `list`);
118
+ root.setAttribute(`part`, `drag-preview`);
119
+ 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;`;
120
+ const header = doc.createElement(`div`);
121
+ header.setAttribute(`data-header`, ``);
122
+ header.style.cssText = `font-weight:600;padding-bottom:4px;border-bottom:1px solid var(--border-subtle, #555);margin-bottom:4px;`;
123
+ header.textContent = `${ctx.selected.length} items`;
124
+ root.appendChild(header);
125
+ for (let i = 0; i < Math.min(LIST_MAX_ROWS, captions.length); i++) {
126
+ const row = doc.createElement(`div`);
127
+ row.setAttribute(`data-row`, ``);
128
+ row.style.cssText = `display:flex;align-items:center;gap:6px;height:${rh}px;overflow:hidden;`;
129
+ row.appendChild(thumbCell(doc, captions[i], images[i], thumb));
130
+ const label = doc.createElement(`span`);
131
+ label.setAttribute(`data-caption`, ``);
132
+ label.style.cssText = `flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`;
133
+ label.textContent = captions[i];
134
+ row.appendChild(label);
135
+ root.appendChild(row);
136
+ }
137
+ if (captions.length > LIST_MAX_ROWS) {
138
+ const more = doc.createElement(`div`);
139
+ more.setAttribute(`data-more`, ``);
140
+ more.style.cssText = `opacity:0.6;text-align:center;`;
141
+ more.textContent = `+${captions.length - LIST_MAX_ROWS} more`;
142
+ root.appendChild(more);
143
+ }
144
+ return root;
145
+ }
146
+ function paletteOf(style) {
147
+ const bg = cssVar(style, `--surface-4`, `#333333`);
148
+ const fg = cssVar(style, `--surface-4-text`, `#ffffff`);
149
+ const subtle = cssVar(style, `--border-subtle`, `#555555`);
150
+ const accent = cssVar(style, `--accent`, `#4da3ff`);
151
+ const radiusRaw = cssVar(style, `--radius-m`, `8`);
152
+ return {
153
+ bg,
154
+ fg,
155
+ subtle,
156
+ accent,
157
+ radius: Number.parseFloat(radiusRaw) || 8,
158
+ font: fontOf(style)
159
+ };
160
+ }
161
+ function fitCanvas(canvas, cssW, cssH) {
162
+ let scale = Math.max(1, Math.min(3, typeof devicePixelRatio === `number` && devicePixelRatio > 0 ? devicePixelRatio : 1));
163
+ const maxScaleW = MAX_CANVAS_DIM / Math.max(1, cssW);
164
+ const maxScaleH = MAX_CANVAS_DIM / Math.max(1, cssH);
165
+ scale = Math.min(scale, maxScaleW, maxScaleH);
166
+ canvas.width = Math.max(1, Math.round(cssW * scale));
167
+ canvas.height = Math.max(1, Math.round(cssH * scale));
168
+ canvas.style.width = `${cssW}px`;
169
+ canvas.style.height = `${cssH}px`;
170
+ return {
171
+ scale,
172
+ dpr: scale
173
+ };
174
+ }
175
+ function roundRectPath(context, x, y, w, h, r) {
176
+ const radius = Math.max(0, Math.min(r, w / 2, h / 2));
177
+ if (typeof context.roundRect === `function`) {
178
+ context.beginPath();
179
+ context.roundRect(x, y, w, h, radius);
180
+ return;
181
+ }
182
+ context.beginPath();
183
+ context.moveTo(x + radius, y);
184
+ context.arcTo(x + w, y, x + w, y + h, radius);
185
+ context.arcTo(x + w, y + h, x, y + h, radius);
186
+ context.arcTo(x, y + h, x, y, radius);
187
+ context.arcTo(x, y, x + w, y, radius);
188
+ context.closePath();
189
+ }
190
+ function paintCards(context, scale, palette, captions, images, size, cssW, cssH) {
191
+ context.save();
192
+ context.scale(scale, scale);
193
+ roundRectPath(context, 0, 0, cssW, cssH, palette.radius);
194
+ context.fillStyle = palette.bg;
195
+ context.fill();
196
+ context.strokeStyle = palette.subtle;
197
+ context.lineWidth = 1;
198
+ context.stroke();
199
+ context.clip();
200
+ const gap = 6;
201
+ const pad = 8;
202
+ const wellSize = Math.round(size * .72);
203
+ context.font = palette.font;
204
+ context.textBaseline = `middle`;
205
+ for (let i = 0; i < captions.length; i++) {
206
+ const x = pad + i * (size + gap);
207
+ const wellX = x + (size - wellSize) / 2;
208
+ const wellY = pad;
209
+ roundRectPath(context, wellX, wellY, wellSize, wellSize, 4);
210
+ context.fillStyle = palette.subtle;
211
+ context.globalAlpha = .35;
212
+ context.fill();
213
+ context.globalAlpha = 1;
214
+ const img = images[i];
215
+ if (img) try {
216
+ context.save();
217
+ context.clip();
218
+ context.drawImage(img, wellX, wellY, wellSize, wellSize);
219
+ context.restore();
220
+ } catch {}
221
+ else {
222
+ context.fillStyle = palette.fg;
223
+ context.globalAlpha = .9;
224
+ context.font = `600 ${Math.round(wellSize * .4)}px system-ui, sans-serif`;
225
+ context.textAlign = `center`;
226
+ context.fillText(glyphFor(captions[i]), wellX + wellSize / 2, wellY + wellSize / 2 + 1);
227
+ context.globalAlpha = 1;
228
+ context.textAlign = `left`;
229
+ context.font = palette.font;
230
+ }
231
+ context.fillStyle = palette.fg;
232
+ context.globalAlpha = .9;
233
+ context.font = palette.font;
234
+ const label = captions[i].length > 12 ? `${captions[i].slice(0, 11)}…` : captions[i];
235
+ const textWidth = context.measureText(label).width;
236
+ context.fillText(label, x + (size - Math.min(size, textWidth)) / 2, wellY + wellSize + 10, size);
237
+ context.globalAlpha = 1;
238
+ }
239
+ context.restore();
240
+ }
241
+ function paintList(context, scale, palette, captions, images, rh, cssW, cssH) {
242
+ context.save();
243
+ context.scale(scale, scale);
244
+ roundRectPath(context, 0, 0, cssW, cssH, palette.radius);
245
+ context.fillStyle = palette.bg;
246
+ context.fill();
247
+ context.strokeStyle = palette.subtle;
248
+ context.lineWidth = 1;
249
+ context.stroke();
250
+ context.clip();
251
+ const pad = 8;
252
+ context.fillStyle = palette.fg;
253
+ context.font = `600 ${palette.font}`;
254
+ context.textBaseline = `middle`;
255
+ context.fillText(`${captions.length} items`, pad, 16, cssW - 16);
256
+ context.strokeStyle = palette.subtle;
257
+ context.beginPath();
258
+ context.moveTo(pad, 28);
259
+ context.lineTo(cssW - pad, 28);
260
+ context.stroke();
261
+ const thumb = Math.round(rh * .78);
262
+ const rows = Math.min(LIST_MAX_ROWS, captions.length);
263
+ context.font = palette.font;
264
+ for (let i = 0; i < rows; i++) {
265
+ const y = 32 + i * rh;
266
+ roundRectPath(context, pad, y + (rh - thumb) / 2, thumb, thumb, 4);
267
+ context.fillStyle = palette.subtle;
268
+ context.globalAlpha = .35;
269
+ context.fill();
270
+ context.globalAlpha = 1;
271
+ const img = images[i];
272
+ if (img) try {
273
+ context.save();
274
+ context.clip();
275
+ context.drawImage(img, pad, y + (rh - thumb) / 2, thumb, thumb);
276
+ context.restore();
277
+ } catch {}
278
+ else {
279
+ context.fillStyle = palette.fg;
280
+ context.font = `600 ${Math.round(thumb * .45)}px system-ui, sans-serif`;
281
+ context.textAlign = `center`;
282
+ context.fillText(glyphFor(captions[i]), pad + thumb / 2, y + rh / 2 + 1);
283
+ context.textAlign = `left`;
284
+ context.font = palette.font;
285
+ }
286
+ context.fillStyle = palette.fg;
287
+ context.globalAlpha = .92;
288
+ const maxText = cssW - 16 - thumb - 6;
289
+ context.fillText(captions[i], pad + thumb + 6, y + rh / 2 + 1, Math.max(8, maxText));
290
+ context.globalAlpha = 1;
291
+ }
292
+ if (captions.length > LIST_MAX_ROWS) {
293
+ context.fillStyle = palette.fg;
294
+ context.globalAlpha = .6;
295
+ context.textAlign = `center`;
296
+ context.fillText(`+${captions.length - LIST_MAX_ROWS} more`, cssW / 2, cssH - pad - 6, cssW - 16);
297
+ context.textAlign = `left`;
298
+ context.globalAlpha = 1;
299
+ }
300
+ context.restore();
301
+ }
302
+ /**
303
+ * Uniform built-in HTML+canvas drag preview for all four list components.
304
+ *
305
+ * - ≤5 selected → a row of small cards (thumbnail well + caption).
306
+ * - >5 selected → a compact list (`N items` header, up to 5 rows, `+N more`).
307
+ *
308
+ * Always returns an offscreen `element` for `DataTransfer.setDragImage()` and,
309
+ * when a 2D context is available, a DPR-aware painted `canvas` for native
310
+ * hosts. Colours track the active theme via `ctx.hostStyle` CSS variables.
311
+ */
312
+ function createDefaultListDragPreview(ctx) {
313
+ const entries = ctx.selected;
314
+ const captions = entries.map((entry) => captionForEntry(entry));
315
+ const images = entries.map((entry) => decodedImage(entry));
316
+ const isCards = entries.length <= 5;
317
+ let element;
318
+ let cssW;
319
+ let cssH;
320
+ if (isCards) {
321
+ const size = cardSize(ctx.metrics);
322
+ cssW = entries.length * size + Math.max(0, entries.length - 1) * 6 + 16;
323
+ cssH = Math.round(size * .72) + 22 + 16;
324
+ element = buildCardsElement(ctx, captions, images);
325
+ } else {
326
+ const rh = rowHeight(ctx.metrics);
327
+ const rows = Math.min(LIST_MAX_ROWS, entries.length);
328
+ const more = entries.length > LIST_MAX_ROWS ? 18 : 0;
329
+ cssW = 236;
330
+ cssH = 40 + rows * rh + more;
331
+ element = buildListElement(ctx, captions, images);
332
+ }
333
+ let canvas;
334
+ try {
335
+ const candidate = document.createElement(`canvas`);
336
+ const paint = candidate.getContext(`2d`);
337
+ if (paint) {
338
+ const { scale } = fitCanvas(candidate, cssW, cssH);
339
+ const palette = paletteOf(ctx.hostStyle);
340
+ if (isCards) paintCards(paint, scale, palette, captions, images, cardSize(ctx.metrics), cssW, cssH);
341
+ else paintList(paint, scale, palette, captions, images, rowHeight(ctx.metrics), cssW, cssH);
342
+ canvas = candidate;
343
+ }
344
+ } catch {
345
+ canvas = void 0;
346
+ }
347
+ return {
348
+ element,
349
+ hotspot: {
350
+ x: 16,
351
+ y: 16
352
+ },
353
+ ...canvas ? { canvas } : {},
354
+ dispose: () => {}
355
+ };
356
+ }
357
+ //#endregion
358
+ //#region src/interaction/list-native-drag-controller.ts
359
+ /**
360
+ * Shared native item/row drag-out for list components.
361
+ *
362
+ * Owns capture-phase `dragstart` / `dragend` listeners on the host and its
363
+ * render root, a `MutationObserver` that keeps light-DOM / slotted item `draggable`
364
+ * attributes in sync, and a single post-dispatch disposal on
365
+ * `requestAnimationFrame`. All DOM / selection knowledge lives in the host
366
+ * config callbacks.
367
+ */
368
+ var ListNativeDragController = class {
369
+ #host;
370
+ #config;
371
+ #dragging = false;
372
+ #boundRoot;
373
+ #boundHost = false;
374
+ #seenDragStarts = /* @__PURE__ */ new WeakSet();
375
+ #observer;
376
+ constructor(host, config) {
377
+ this.#host = host;
378
+ this.#config = config;
379
+ host.addController(this);
380
+ }
381
+ get isDragging() {
382
+ return this.#dragging;
383
+ }
384
+ /** `"true"` when enabled, else `"false"` — for shadow-row template bindings. */
385
+ get draggableAttr() {
386
+ try {
387
+ return this.#config.isEnabled() ? `true` : `false`;
388
+ } catch {
389
+ return `false`;
390
+ }
391
+ }
392
+ /** Re-run the light-DOM `draggable` sync (e.g. after `itemsDraggable` flips). */
393
+ refreshDraggable() {
394
+ try {
395
+ this.#config.syncLightDomDraggable?.();
396
+ } catch {}
397
+ }
398
+ hostConnected() {
399
+ this.#bind();
400
+ this.#observer ??= new MutationObserver(() => this.refreshDraggable());
401
+ try {
402
+ this.#observer.observe(this.#host, {
403
+ childList: true,
404
+ subtree: true
405
+ });
406
+ } catch {}
407
+ this.refreshDraggable();
408
+ }
409
+ hostDisconnected() {
410
+ this.#unbind();
411
+ try {
412
+ this.#observer?.disconnect();
413
+ } catch {}
414
+ this.#dragging = false;
415
+ }
416
+ #renderRoot() {
417
+ return this.#host.renderRoot ?? this.#host;
418
+ }
419
+ #bind() {
420
+ const root = this.#renderRoot();
421
+ if (root !== this.#boundRoot) {
422
+ this.#unbind();
423
+ this.#boundRoot = root;
424
+ root.addEventListener(`dragstart`, this.#onDragStart, { capture: true });
425
+ root.addEventListener(`dragend`, this.#onDragEnd, { capture: true });
426
+ }
427
+ if (!this.#boundHost) {
428
+ this.#boundHost = true;
429
+ this.#host.addEventListener(`dragstart`, this.#onDragStart, { capture: true });
430
+ this.#host.addEventListener(`dragend`, this.#onDragEnd, { capture: true });
431
+ }
432
+ }
433
+ #unbind() {
434
+ this.#boundRoot?.removeEventListener(`dragstart`, this.#onDragStart, { capture: true });
435
+ this.#boundRoot?.removeEventListener(`dragend`, this.#onDragEnd, { capture: true });
436
+ this.#boundRoot = void 0;
437
+ if (this.#boundHost) {
438
+ this.#boundHost = false;
439
+ this.#host.removeEventListener(`dragstart`, this.#onDragStart, { capture: true });
440
+ this.#host.removeEventListener(`dragend`, this.#onDragEnd, { capture: true });
441
+ }
442
+ }
443
+ #onDragStart = (event) => {
444
+ if (!(event instanceof DragEvent)) return;
445
+ if (this.#seenDragStarts.has(event)) return;
446
+ this.#seenDragStarts.add(event);
447
+ let enabled = false;
448
+ try {
449
+ enabled = this.#config.isEnabled();
450
+ } catch {
451
+ return;
452
+ }
453
+ if (!enabled) return;
454
+ let originKey;
455
+ try {
456
+ originKey = this.#config.resolveEventToKey(event.target);
457
+ } catch {
458
+ return;
459
+ }
460
+ if (!originKey) return;
461
+ let prepared;
462
+ try {
463
+ prepared = this.#config.prepareSelection(originKey);
464
+ } catch {
465
+ return;
466
+ }
467
+ if (!prepared || prepared.selected.length === 0) return;
468
+ const { origin, selected } = prepared;
469
+ let hostStyle;
470
+ try {
471
+ hostStyle = getComputedStyle(this.#host);
472
+ } catch {
473
+ hostStyle = document.createElement(`div`).style;
474
+ }
475
+ let metrics = {};
476
+ try {
477
+ metrics = this.#config.getMetrics() ?? {};
478
+ } catch {
479
+ metrics = {};
480
+ }
481
+ const context = {
482
+ source: this.#config.source,
483
+ origin,
484
+ selected,
485
+ metrics,
486
+ hostStyle
487
+ };
488
+ let renderer;
489
+ try {
490
+ renderer = this.#config.getRenderer();
491
+ } catch {
492
+ renderer = void 0;
493
+ }
494
+ let preview;
495
+ try {
496
+ preview = renderer ? renderer(context) : createDefaultListDragPreview(context);
497
+ } catch {
498
+ preview = createDefaultListDragPreview(context);
499
+ }
500
+ try {
501
+ document.body.appendChild(preview.element);
502
+ } catch {}
503
+ const detail = {
504
+ key: origin.key,
505
+ index: origin.index,
506
+ item: origin.item,
507
+ selectedKeys: selected.map((entry) => entry.key),
508
+ selected,
509
+ preview
510
+ };
511
+ let notCancelled = true;
512
+ try {
513
+ notCancelled = this.#config.dispatch(detail);
514
+ } catch {
515
+ notCancelled = true;
516
+ }
517
+ if (notCancelled) {
518
+ const hotspot = preview.hotspot ?? {
519
+ x: 16,
520
+ y: 16
521
+ };
522
+ try {
523
+ event.dataTransfer?.setDragImage(preview.element, hotspot.x, hotspot.y);
524
+ } catch {}
525
+ } else try {
526
+ event.preventDefault();
527
+ } catch {}
528
+ this.#dragging = true;
529
+ try {
530
+ this.#config.onActiveChange(true, origin.key);
531
+ } catch {}
532
+ requestAnimationFrame(() => {
533
+ try {
534
+ preview.element.remove();
535
+ } catch {}
536
+ try {
537
+ preview.dispose?.();
538
+ } catch {}
539
+ });
540
+ };
541
+ #onDragEnd = () => {
542
+ if (!this.#dragging) return;
543
+ this.#dragging = false;
544
+ try {
545
+ this.#config.onActiveChange(false);
546
+ } catch {}
547
+ try {
548
+ this.#config.onEnd();
549
+ } catch {}
550
+ };
551
+ };
552
+ //#endregion
553
+ export { captionForEntry as n, createDefaultListDragPreview as r, ListNativeDragController as t };
554
+
555
+ //# sourceMappingURL=list-native-drag-controller-DrY3qeJM.js.map