@ixfx/components 0.8.4 → 0.8.5

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 (66) hide show
  1. package/bundle/index.d.ts +78 -9
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +241 -47
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.d.ts +1 -1
  6. package/dist/{ac-token--_vCHXWw.d.ts → ac-token-TCLAZeVg.d.ts} +2 -2
  7. package/dist/{ac-token--_vCHXWw.d.ts.map → ac-token-TCLAZeVg.d.ts.map} +1 -1
  8. package/dist/ac-token.d.ts +1 -1
  9. package/dist/button.d.ts +1 -1
  10. package/dist/button.d.ts.map +1 -1
  11. package/dist/colour-picker.d.ts +1 -1
  12. package/dist/crumbs.d.ts +1 -1
  13. package/dist/{data-grid-D-z7bx5-.d.ts → data-grid-DCt0oack.d.ts} +2 -2
  14. package/dist/{data-grid-D-z7bx5-.d.ts.map → data-grid-DCt0oack.d.ts.map} +1 -1
  15. package/dist/data-grid.d.ts +1 -1
  16. package/dist/{detail-list-_ZJHYEBr.d.ts → detail-list-CqRzhFnv.d.ts} +2 -2
  17. package/dist/{detail-list-_ZJHYEBr.d.ts.map → detail-list-CqRzhFnv.d.ts.map} +1 -1
  18. package/dist/detail-list.d.ts +1 -1
  19. package/dist/{grid-list-BXfY3XXX.js → grid-list-j5C7qtlG.js} +148 -29
  20. package/dist/grid-list-j5C7qtlG.js.map +1 -0
  21. package/dist/grid-list-x550wJ0X.d.ts.map +1 -1
  22. package/dist/grid-list.js +1 -1
  23. package/dist/{grouped-item-lister-fdZHVxFM.d.ts → grouped-item-lister-CEZ72VOs.d.ts} +2 -2
  24. package/dist/{grouped-item-lister-fdZHVxFM.d.ts.map → grouped-item-lister-CEZ72VOs.d.ts.map} +1 -1
  25. package/dist/grouped-item-lister.d.ts +1 -1
  26. package/dist/{icon-CDGprSdE.d.ts → icon-DR_3rSyZ.d.ts} +2 -2
  27. package/dist/icon-DR_3rSyZ.d.ts.map +1 -0
  28. package/dist/icons.d.ts +2 -2
  29. package/dist/{index-tq6f8007.d.ts → index-BOUaDIW-.d.ts} +2 -2
  30. package/dist/{index-tq6f8007.d.ts.map → index-BOUaDIW-.d.ts.map} +1 -1
  31. package/dist/{index-CoqtMSd9.d.ts → index-BUB5SICW.d.ts} +2 -2
  32. package/dist/{index-CoqtMSd9.d.ts.map → index-BUB5SICW.d.ts.map} +1 -1
  33. package/dist/{index-9AhaHIT2.d.ts → index-C59-JEPl.d.ts} +3 -3
  34. package/dist/{index-9AhaHIT2.d.ts.map → index-C59-JEPl.d.ts.map} +1 -1
  35. package/dist/{index-UvnoZUAo.d.ts → index-CJ5fshD3.d.ts} +2 -2
  36. package/dist/{index-UvnoZUAo.d.ts.map → index-CJ5fshD3.d.ts.map} +1 -1
  37. package/dist/{index-CS9yM8L-.d.ts → index-DsjpDksO.d.ts} +2 -2
  38. package/dist/{index-CS9yM8L-.d.ts.map → index-DsjpDksO.d.ts.map} +1 -1
  39. package/dist/index.d.ts +92 -23
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +95 -20
  42. package/dist/index.js.map +1 -1
  43. package/dist/{labelled-input-base-DiTme1JO.d.ts → labelled-input-base-Dp_9KP0G.d.ts} +2 -2
  44. package/dist/{labelled-input-base-DiTme1JO.d.ts.map → labelled-input-base-Dp_9KP0G.d.ts.map} +1 -1
  45. package/dist/labelled-radial-input.d.ts +2 -2
  46. package/dist/labelled-radial-input.d.ts.map +1 -1
  47. package/dist/labelled-range-input.d.ts +1 -1
  48. package/dist/labelled-slider-input.d.ts +1 -1
  49. package/dist/{menu-item-CZi9vy5O.d.ts → menu-item-ZmkXNjek.d.ts} +2 -2
  50. package/dist/{menu-item-CZi9vy5O.d.ts.map → menu-item-ZmkXNjek.d.ts.map} +1 -1
  51. package/dist/menu.d.ts +2 -2
  52. package/dist/panel.d.ts +1 -1
  53. package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-C5SYbDrq.d.ts} +2 -2
  54. package/dist/{split-layout-JK3xWVX2.d.ts.map → split-layout-C5SYbDrq.d.ts.map} +1 -1
  55. package/dist/split-layout.d.ts +1 -1
  56. package/dist/{tab-list-CGyPt07Z.d.ts → tab-list-DbMIL520.d.ts} +2 -2
  57. package/dist/{tab-list-CGyPt07Z.d.ts.map → tab-list-DbMIL520.d.ts.map} +1 -1
  58. package/dist/tabs.d.ts +1 -1
  59. package/dist/{titlebar-Tqu-VvMU.d.ts → titlebar-B5zSTZnF.d.ts} +2 -2
  60. package/dist/{titlebar-Tqu-VvMU.d.ts.map → titlebar-B5zSTZnF.d.ts.map} +1 -1
  61. package/dist/titlebar.d.ts +1 -1
  62. package/docs-user/grid-list.md +2 -1
  63. package/docs-user/index.json +2 -2
  64. package/package.json +1 -1
  65. package/dist/grid-list-BXfY3XXX.js.map +0 -1
  66. package/dist/icon-CDGprSdE.d.ts.map +0 -1
package/bundle/index.js CHANGED
@@ -19008,6 +19008,16 @@ let DockHostElement = class DockHostElement extends i {
19008
19008
  if (rules.deny.has(kind)) return false;
19009
19009
  return rules.allow === null || rules.allow.has(kind);
19010
19010
  }
19011
+ /**
19012
+ * Whether an edge drop may split a new sibling leaf beside `stack`. Unnamed
19013
+ * leaves and leaves with no policy always allow it; a named leaf whose
19014
+ * policy sets `allowEdgeSplit: false` does not (strict named-area
19015
+ * containment).
19016
+ */
19017
+ _leafAllowsEdgeSplit(stack) {
19018
+ if (stack.name === void 0) return true;
19019
+ return this._leafContentRules.get(stack.name)?.allowEdgeSplit ?? true;
19020
+ }
19011
19021
  /** First stack (tree order) whose rules admit `kind`, else the first stack. */
19012
19022
  _firstStackAllowing(kind) {
19013
19023
  for (const path of collectStackPaths(this._model)) {
@@ -19072,6 +19082,9 @@ let DockHostElement = class DockHostElement extends i {
19072
19082
  else if (op === `tabs` && !this._stackAllowsKindAt(namedPath, kind)) {
19073
19083
  console.warn(`[ixfx-dock-host] addPanel: container "${target.container}" does not admit kind "${kind}"`);
19074
19084
  return false;
19085
+ } else if (op !== `tabs` && !this._stackAllowsEdgeSplitAt(namedPath)) {
19086
+ console.warn(`[ixfx-dock-host] addPanel: container "${target.container}" forbids edge splits (allowEdgeSplit: false)`);
19087
+ return false;
19075
19088
  } else stackPath = namedPath;
19076
19089
  } else if (target?.stack) {
19077
19090
  const found = findStackOf(this._model, target.stack);
@@ -19079,6 +19092,9 @@ let DockHostElement = class DockHostElement extends i {
19079
19092
  else if (op === `tabs` && !this._stackAllowsKindAt(found, kind)) {
19080
19093
  console.warn(`[ixfx-dock-host] addPanel: target stack does not admit kind "${kind}"`);
19081
19094
  return false;
19095
+ } else if (op !== `tabs` && !this._stackAllowsEdgeSplitAt(found)) {
19096
+ console.warn(`[ixfx-dock-host] addPanel: target stack forbids edge splits (allowEdgeSplit: false)`);
19097
+ return false;
19082
19098
  } else stackPath = found;
19083
19099
  }
19084
19100
  if (stackPath !== null && op !== `tabs`) {
@@ -19120,6 +19136,11 @@ let DockHostElement = class DockHostElement extends i {
19120
19136
  const stack = findNode(this._model, stackPath);
19121
19137
  return stack !== null && isStack(stack) && this._stackAllowsKind(stack, kind);
19122
19138
  }
19139
+ /** Whether the stack at `stackPath` permits an edge split beside it. */
19140
+ _stackAllowsEdgeSplitAt(stackPath) {
19141
+ const stack = findNode(this._model, stackPath);
19142
+ return stack === null || !isStack(stack) || this._leafAllowsEdgeSplit(stack);
19143
+ }
19123
19144
  removePanel(panelId) {
19124
19145
  if (!collectPanelIds(this._model).includes(panelId)) return false;
19125
19146
  this._returnAllToolbars();
@@ -19181,11 +19202,26 @@ let DockHostElement = class DockHostElement extends i {
19181
19202
  getContainerElements(name) {
19182
19203
  return this.getContainerPanels(name).map((id) => this._findPanelEl(id)).filter((el) => el !== void 0);
19183
19204
  }
19205
+ _defaultLeafRule() {
19206
+ return {
19207
+ allow: null,
19208
+ deny: /* @__PURE__ */ new Set(),
19209
+ allowEdgeSplit: true
19210
+ };
19211
+ }
19212
+ /** A rule entry that constrains nothing — safe to drop from the map. */
19213
+ _isEmptyLeafRule(entry) {
19214
+ return entry.allow === null && entry.deny.size === 0 && entry.allowEdgeSplit;
19215
+ }
19216
+ _warnUnknownContainer(method, key) {
19217
+ if (findStackPathByName(this._model, key) === null) console.warn(`[ixfx-dock-host] ${method}: no container named "${key}" yet; rule stored`);
19218
+ }
19184
19219
  /**
19185
19220
  * Restrict a named container to only the given panel kinds — e.g.
19186
19221
  * `setLeafExclusiveContents('editor', ['document'])`. `tabs` drops,
19187
- * `addPanel`, and re-homing into the leaf then refuse other kinds, while
19188
- * edge splits beside the leaf stay allowed (they create a new leaf).
19222
+ * `addPanel`, and re-homing into the leaf then refuse other kinds. Whether
19223
+ * edge splits beside the leaf are offered is governed separately by
19224
+ * {@link setLeafPlacementPolicy}'s `allowEdgeSplit` (default `true`).
19189
19225
  * Pass `[]` to seal the leaf (nothing admitted). Warns when no stack
19190
19226
  * currently carries `name`, but the rule is still stored and applies once
19191
19227
  * one appears. Rules are host policy: kept across `loadLayout()`, never
@@ -19197,14 +19233,10 @@ let DockHostElement = class DockHostElement extends i {
19197
19233
  console.warn(`[ixfx-dock-host] setLeafExclusiveContents: empty container name`);
19198
19234
  return;
19199
19235
  }
19200
- const allow = new Set(kinds.map((k) => k.trim()).filter((k) => k.length > 0));
19201
- const entry = this._leafContentRules.get(key) ?? {
19202
- allow: null,
19203
- deny: /* @__PURE__ */ new Set()
19204
- };
19205
- entry.allow = allow;
19236
+ const entry = this._leafContentRules.get(key) ?? this._defaultLeafRule();
19237
+ entry.allow = new Set(kinds.map((k) => k.trim()).filter((k) => k.length > 0));
19206
19238
  this._leafContentRules.set(key, entry);
19207
- if (findStackPathByName(this._model, key) === null) console.warn(`[ixfx-dock-host] setLeafExclusiveContents: no container named "${key}" yet; rule stored`);
19239
+ this._warnUnknownContainer(`setLeafExclusiveContents`, key);
19208
19240
  }
19209
19241
  /**
19210
19242
  * Forbid the given panel kinds in a named container — e.g.
@@ -19218,21 +19250,53 @@ let DockHostElement = class DockHostElement extends i {
19218
19250
  console.warn(`[ixfx-dock-host] setLeafDenyContents: empty container name`);
19219
19251
  return;
19220
19252
  }
19221
- const deny = new Set(kinds.map((k) => k.trim()).filter((k) => k.length > 0));
19222
- const entry = this._leafContentRules.get(key) ?? {
19223
- allow: null,
19224
- deny: /* @__PURE__ */ new Set()
19225
- };
19226
- entry.deny = deny;
19227
- if (entry.allow === null && entry.deny.size === 0) this._leafContentRules.delete(key);
19253
+ const entry = this._leafContentRules.get(key) ?? this._defaultLeafRule();
19254
+ entry.deny = new Set(kinds.map((k) => k.trim()).filter((k) => k.length > 0));
19255
+ if (this._isEmptyLeafRule(entry)) this._leafContentRules.delete(key);
19256
+ else this._leafContentRules.set(key, entry);
19257
+ this._warnUnknownContainer(`setLeafDenyContents`, key);
19258
+ }
19259
+ /**
19260
+ * Set (merge) the full placement policy for a named leaf: kind allow/deny
19261
+ * lists plus the `allowEdgeSplit` gate. Omitted fields are left unchanged
19262
+ * — pass `clearLeafPlacementPolicy(name)` to reset. `allowKinds` /
19263
+ * `denyKinds` are equivalent to {@link setLeafExclusiveContents} /
19264
+ * {@link setLeafDenyContents} and share the same storage.
19265
+ *
19266
+ * `allowEdgeSplit: false` enforces strict named-area containment: the edge
19267
+ * drop zones beside the leaf go inert for every placement route (pointer
19268
+ * preview and drop, `queryDropTarget` / `previewExternalDrop`,
19269
+ * `addPanel` with a split `op`), so a panel cannot escape the area by
19270
+ * splitting next to it. A permitted split still creates an *unnamed,
19271
+ * unconstrained* leaf — the policy is not propagated to it.
19272
+ *
19273
+ * Warns when no stack currently carries `name`; the policy is stored and
19274
+ * applies once one appears. Host policy: kept across `loadLayout()`, never
19275
+ * serialized.
19276
+ */
19277
+ setLeafPlacementPolicy(name, policy) {
19278
+ const key = name.trim();
19279
+ if (!key) {
19280
+ console.warn(`[ixfx-dock-host] setLeafPlacementPolicy: empty container name`);
19281
+ return;
19282
+ }
19283
+ const entry = this._leafContentRules.get(key) ?? this._defaultLeafRule();
19284
+ if (policy.allowKinds !== void 0) entry.allow = new Set(policy.allowKinds.map((k) => k.trim()).filter((k) => k.length > 0));
19285
+ if (policy.denyKinds !== void 0) entry.deny = new Set(policy.denyKinds.map((k) => k.trim()).filter((k) => k.length > 0));
19286
+ if (policy.allowEdgeSplit !== void 0) entry.allowEdgeSplit = policy.allowEdgeSplit;
19287
+ if (this._isEmptyLeafRule(entry)) this._leafContentRules.delete(key);
19228
19288
  else this._leafContentRules.set(key, entry);
19229
- if (findStackPathByName(this._model, key) === null) console.warn(`[ixfx-dock-host] setLeafDenyContents: no container named "${key}" yet; rule stored`);
19289
+ this._warnUnknownContainer(`setLeafPlacementPolicy`, key);
19230
19290
  }
19231
- /** Remove every content rule for a named container. */
19291
+ /** Remove every content rule and reset the edge-split gate for a named container. */
19232
19292
  clearLeafContentRestrictions(name) {
19233
19293
  this._leafContentRules.delete(name.trim());
19234
19294
  }
19235
- /** Copy of a container's rules, or null when unrestricted. */
19295
+ /** Alias of {@link clearLeafContentRestrictions} — clears kind rules and the edge-split gate. */
19296
+ clearLeafPlacementPolicy(name) {
19297
+ this._leafContentRules.delete(name.trim());
19298
+ }
19299
+ /** Copy of a container's kind rules, or null when unrestricted. */
19236
19300
  getLeafContentRestrictions(name) {
19237
19301
  const rules = this._leafContentRules.get(name.trim());
19238
19302
  if (rules === void 0) return null;
@@ -19241,6 +19305,16 @@ let DockHostElement = class DockHostElement extends i {
19241
19305
  deny: [...rules.deny]
19242
19306
  };
19243
19307
  }
19308
+ /** Resolved placement policy for a container, or null when it has none. */
19309
+ getLeafPlacementPolicy(name) {
19310
+ const rules = this._leafContentRules.get(name.trim());
19311
+ if (rules === void 0) return null;
19312
+ return {
19313
+ allowKinds: rules.allow === null ? null : [...rules.allow],
19314
+ denyKinds: [...rules.deny],
19315
+ allowEdgeSplit: rules.allowEdgeSplit
19316
+ };
19317
+ }
19244
19318
  /**
19245
19319
  * Geometric neighbours of a leaf (`name` or numeric `path`) — what sits
19246
19320
  * above / below / left / right of it on screen. Each entry is
@@ -19535,6 +19609,7 @@ let DockHostElement = class DockHostElement extends i {
19535
19609
  if (zone.operation === `tabs` && stack !== null && isStack(stack)) {
19536
19610
  if (!this._stackAllowsKind(stack, kind?.trim() ? kind.trim() : this._panelKind(panelId))) return false;
19537
19611
  }
19612
+ if (zone.operation !== `tabs` && stack !== null && isStack(stack) && !this._leafAllowsEdgeSplit(stack)) return false;
19538
19613
  if (this.canAccept && !this.canAccept(panelId, zone.operation)) return false;
19539
19614
  return true;
19540
19615
  }
@@ -21914,14 +21989,8 @@ let GridListElement = class GridListElement extends i {
21914
21989
  }
21915
21990
  },
21916
21991
  shouldStart: (event) => {
21917
- if (!this.itemsDraggable) return true;
21918
- const cell = event.composedPath()[0]?.closest?.(`.cell`);
21919
- if (!cell) return true;
21920
- const key = cell.dataset.key;
21921
- if (!key) return true;
21922
- const index = this.#source.indexOfKey(key);
21923
- if (index < 0) return true;
21924
- return !this.#source.isSelectable(index);
21992
+ for (const node of event.composedPath()) if (node instanceof Element && node.classList.contains(`cell`)) return false;
21993
+ return true;
21925
21994
  }
21926
21995
  });
21927
21996
  this.#selectedSnapshot = /* @__PURE__ */ new Set();
@@ -21936,26 +22005,17 @@ let GridListElement = class GridListElement extends i {
21936
22005
  });
21937
22006
  };
21938
22007
  this.#onDragStart = (e, key) => {
22008
+ this.#doDragStart(key, () => e.preventDefault(), e);
22009
+ };
22010
+ this.#onSlottedDragStart = (e) => {
21939
22011
  if (!this.itemsDraggable) return;
21940
- const index = this.#source.indexOfKey(key);
21941
- if (index < 0 || !this.#source.isSelectable(index)) return;
21942
- if (!this.selection.selectedKeys.has(key)) this.selection.replace(key);
21943
- const selectedKeys = this.#source.keys().filter((k) => !isSyntheticKey(k)).filter((k) => this.selection.selectedKeys.has(k));
21944
- const detail = {
21945
- key,
21946
- index: this.#source.indexOfKey(key),
21947
- item: this.#source.resolveKeyToData(key) ?? this.#source.resolveKeyToElement(key),
21948
- selectedKeys
21949
- };
21950
- if (!this._dispatch(`grid-list-native-drag-start`, detail, true)) {
21951
- e.preventDefault();
21952
- return;
21953
- }
21954
- this.#nativeDragging = true;
21955
- this.#dragOriginKey = key;
21956
- this.toggleAttribute(`data-native-dragging`, true);
21957
- const count = selectedKeys.length;
21958
- this.setAttribute(`aria-label`, count === 1 ? `Dragging 1 item` : `Dragging ${count} items`);
22012
+ let cell = e.composedPath().find((n) => n instanceof Element && n.classList.contains(`cell`));
22013
+ if (!cell && e.target instanceof Element) cell = e.target.closest?.(`.cell`);
22014
+ if (!cell) return;
22015
+ const key = cell.dataset.key;
22016
+ if (!key) return;
22017
+ if (e instanceof DragEvent) this.#doDragStart(key, () => e.preventDefault(), e);
22018
+ else this.#doDragStart(key, () => {});
21959
22019
  };
21960
22020
  this.#onDragEnd = () => {
21961
22021
  this.#clearNativeDragState();
@@ -22132,7 +22192,6 @@ let GridListElement = class GridListElement extends i {
22132
22192
  align-items: center;
22133
22193
  justify-content: center;
22134
22194
  overflow: hidden;
22135
- draggable: false;
22136
22195
  }
22137
22196
  .thumb ::slotted(*:empty)::after {
22138
22197
  content: "Empty";
@@ -22611,6 +22670,7 @@ let GridListElement = class GridListElement extends i {
22611
22670
  this.#measureViewport(false);
22612
22671
  this.#syncItems();
22613
22672
  this.requestUpdate();
22673
+ this.#initSlottedDragInterceptor();
22614
22674
  }
22615
22675
  /** (Re)construct {@link #source} for the current mode and re-apply the filters. */
22616
22676
  #rebuildSource() {
@@ -22700,6 +22760,7 @@ let GridListElement = class GridListElement extends i {
22700
22760
  this.selection.clearAnchor();
22701
22761
  this.#syncAriaMultiselectable();
22702
22762
  }
22763
+ if (changed.has(`itemsDraggable`)) this.#syncSlottedDraggable();
22703
22764
  if (changed.has(`orientation`)) {
22704
22765
  const vp = this.#viewportRef.value;
22705
22766
  if (vp) {
@@ -23226,7 +23287,140 @@ let GridListElement = class GridListElement extends i {
23226
23287
  this.selection.handleClick(key, e);
23227
23288
  this.#emitItemEvent(`list-item-click`, key);
23228
23289
  }
23290
+ #doDragStart(key, cancelled, dragEvent) {
23291
+ if (!this.itemsDraggable) return false;
23292
+ const index = this.#source.indexOfKey(key);
23293
+ if (index < 0 || !this.#source.isSelectable(index)) return false;
23294
+ if (!this.selection.selectedKeys.has(key)) this.selection.replace(key);
23295
+ const selectedKeys = this.#source.keys().filter((k) => !isSyntheticKey(k)).filter((k) => this.selection.selectedKeys.has(k));
23296
+ const detail = {
23297
+ key,
23298
+ index: this.#source.indexOfKey(key),
23299
+ item: this.#source.resolveKeyToData(key) ?? this.#source.resolveKeyToElement(key),
23300
+ selectedKeys
23301
+ };
23302
+ if (!this._dispatch(`grid-list-native-drag-start`, detail, true)) {
23303
+ cancelled();
23304
+ return false;
23305
+ }
23306
+ this.#nativeDragging = true;
23307
+ this.#dragOriginKey = key;
23308
+ this.toggleAttribute(`data-native-dragging`, true);
23309
+ const count = selectedKeys.length;
23310
+ this.setAttribute(`aria-label`, count === 1 ? `Dragging 1 item` : `Dragging ${count} items`);
23311
+ if (dragEvent && count > 1) this.#setMultiItemDragImage(dragEvent, selectedKeys);
23312
+ return true;
23313
+ }
23314
+ #setMultiItemDragImage(e, selectedKeys) {
23315
+ const preview = this.#createMultiItemPreview(selectedKeys);
23316
+ document.body.appendChild(preview);
23317
+ const rect = preview.getBoundingClientRect();
23318
+ e.dataTransfer?.setDragImage(preview, rect.width / 2, rect.height / 2);
23319
+ requestAnimationFrame(() => preview.remove());
23320
+ }
23321
+ #createMultiItemPreview(selectedKeys) {
23322
+ const preview = document.createElement(`div`);
23323
+ preview.style.cssText = `
23324
+ position: fixed;
23325
+ top: -9999px;
23326
+ left: -9999px;
23327
+ display: flex;
23328
+ flex-direction: column;
23329
+ gap: 4px;
23330
+ padding: 8px;
23331
+ background: var(--surface-4, #333);
23332
+ border: 1px solid var(--border, #555);
23333
+ border-radius: 8px;
23334
+ box-shadow: 0 4px 16px rgba(0,0,0,0.3);
23335
+ font-family: system-ui, sans-serif;
23336
+ font-size: 12px;
23337
+ color: var(--surface-text, #fff);
23338
+ max-width: 200px;
23339
+ z-index: 999999;
23340
+ `;
23341
+ const header = document.createElement(`div`);
23342
+ header.style.cssText = `
23343
+ font-weight: 600;
23344
+ padding-bottom: 4px;
23345
+ border-bottom: 1px solid var(--border, #555);
23346
+ margin-bottom: 4px;
23347
+ `;
23348
+ header.textContent = `${selectedKeys.length} items`;
23349
+ preview.appendChild(header);
23350
+ for (const key of selectedKeys.slice(0, 5)) {
23351
+ const row = this.#createPreviewRow(key);
23352
+ if (row) preview.appendChild(row);
23353
+ }
23354
+ if (selectedKeys.length > 5) {
23355
+ const more = document.createElement(`div`);
23356
+ more.style.cssText = `opacity: 0.6; text-align: center;`;
23357
+ more.textContent = `+${selectedKeys.length - 5} more`;
23358
+ preview.appendChild(more);
23359
+ }
23360
+ return preview;
23361
+ }
23362
+ #createPreviewRow(key) {
23363
+ const el = this.#source.resolveKeyToElement(key);
23364
+ if (!el) return null;
23365
+ const row = document.createElement(`div`);
23366
+ row.style.cssText = `
23367
+ display: flex;
23368
+ align-items: center;
23369
+ gap: 6px;
23370
+ `;
23371
+ const thumb = document.createElement(`div`);
23372
+ thumb.style.cssText = `
23373
+ width: 32px;
23374
+ height: 32px;
23375
+ border-radius: 4px;
23376
+ overflow: hidden;
23377
+ background: var(--surface-5, #444);
23378
+ flex-shrink: 0;
23379
+ display: flex;
23380
+ align-items: center;
23381
+ justify-content: center;
23382
+ `;
23383
+ const slottedImg = el.querySelector(`img`);
23384
+ const slottedSvg = el.querySelector(`svg`);
23385
+ const slottedPlaceholder = el.querySelector(`.placeholder`);
23386
+ if (slottedImg) {
23387
+ const img = slottedImg.cloneNode(true);
23388
+ img.style.cssText = `width: 100%; height: 100%; object-fit: cover;`;
23389
+ thumb.appendChild(img);
23390
+ } else if (slottedSvg) {
23391
+ const svg = slottedSvg.cloneNode(true);
23392
+ svg.style.cssText = `width: 70%; height: 70%; opacity: 0.8;`;
23393
+ thumb.appendChild(svg);
23394
+ } else if (slottedPlaceholder) thumb.textContent = slottedPlaceholder.textContent || `?`;
23395
+ const label = document.createElement(`span`);
23396
+ label.style.cssText = `
23397
+ flex: 1;
23398
+ overflow: hidden;
23399
+ text-overflow: ellipsis;
23400
+ white-space: nowrap;
23401
+ `;
23402
+ label.textContent = el.getAttribute(`data-label`) || key;
23403
+ row.appendChild(thumb);
23404
+ row.appendChild(label);
23405
+ return row;
23406
+ }
23229
23407
  #onDragStart;
23408
+ #onSlottedDragStart;
23409
+ #initSlottedDragInterceptor() {
23410
+ this.renderRoot.addEventListener(`dragstart`, this.#onSlottedDragStart, { capture: true });
23411
+ const observer = new MutationObserver(() => this.#syncSlottedDraggable());
23412
+ observer.observe(this, {
23413
+ childList: true,
23414
+ subtree: true
23415
+ });
23416
+ this.#slottedDragObserver = observer;
23417
+ this.#syncSlottedDraggable();
23418
+ }
23419
+ #slottedDragObserver;
23420
+ #syncSlottedDraggable() {
23421
+ const draggable = this.itemsDraggable;
23422
+ for (const child of this.children) if (child instanceof HTMLElement) child.draggable = draggable;
23423
+ }
23230
23424
  #onDragEnd;
23231
23425
  #startPostDragSuppressTimer() {
23232
23426
  if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);