@ixfx/components 0.8.4 → 0.8.6

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.
package/bundle/index.js CHANGED
@@ -4669,26 +4669,18 @@ let BottomDrawerElement = class BottomDrawerElement extends i {
4669
4669
  --bottom-drawer-collapsed-width: 96px;
4670
4670
 
4671
4671
  display: block;
4672
- position: relative;
4673
- height: 100%;
4674
- min-width: 0;
4675
- min-height: 0;
4676
- overflow: hidden;
4677
- }
4678
- .content {
4679
4672
  position: absolute;
4680
4673
  inset: 0;
4681
4674
  min-width: 0;
4682
4675
  min-height: 0;
4683
- }
4684
- .content ::slotted(*) {
4685
- width: 100%;
4686
- height: 100%;
4676
+ overflow: hidden;
4677
+ pointer-events: none;
4687
4678
  }
4688
4679
  .drawer {
4689
4680
  --m: 0;
4690
4681
  position: absolute;
4691
4682
  z-index: 2;
4683
+ pointer-events: auto;
4692
4684
  right: calc(var(--m) * (50% - var(--bottom-drawer-collapsed-width) / 2));
4693
4685
  bottom: 0;
4694
4686
  left: calc(var(--m) * (50% - var(--bottom-drawer-collapsed-width) / 2));
@@ -4923,7 +4915,6 @@ let BottomDrawerElement = class BottomDrawerElement extends i {
4923
4915
  const height = this.open ? raw * (1 - morph) + 20 * morph : 20;
4924
4916
  const opacity = this.maintainHandle && this.open ? 1 : this.#dragOpacity(raw);
4925
4917
  return b`
4926
- <div class="content"><slot></slot></div>
4927
4918
  <section
4928
4919
  class="drawer"
4929
4920
  style=${`--bottom-drawer-height: ${height}px; --bottom-drawer-opacity: ${opacity}; --m: ${morph}`}
@@ -4955,7 +4946,7 @@ let BottomDrawerElement = class BottomDrawerElement extends i {
4955
4946
  >
4956
4947
  <slot name="toolbar" slot="toolbar"></slot>
4957
4948
  </ixfx-titlebar>
4958
- <div class="body" ?inert=${!this.open}><slot name="drawer"></slot></div>
4949
+ <div class="body" ?inert=${!this.open}><slot></slot></div>
4959
4950
  </section>
4960
4951
  `;
4961
4952
  }
@@ -19008,6 +18999,16 @@ let DockHostElement = class DockHostElement extends i {
19008
18999
  if (rules.deny.has(kind)) return false;
19009
19000
  return rules.allow === null || rules.allow.has(kind);
19010
19001
  }
19002
+ /**
19003
+ * Whether an edge drop may split a new sibling leaf beside `stack`. Unnamed
19004
+ * leaves and leaves with no policy always allow it; a named leaf whose
19005
+ * policy sets `allowEdgeSplit: false` does not (strict named-area
19006
+ * containment).
19007
+ */
19008
+ _leafAllowsEdgeSplit(stack) {
19009
+ if (stack.name === void 0) return true;
19010
+ return this._leafContentRules.get(stack.name)?.allowEdgeSplit ?? true;
19011
+ }
19011
19012
  /** First stack (tree order) whose rules admit `kind`, else the first stack. */
19012
19013
  _firstStackAllowing(kind) {
19013
19014
  for (const path of collectStackPaths(this._model)) {
@@ -19072,6 +19073,9 @@ let DockHostElement = class DockHostElement extends i {
19072
19073
  else if (op === `tabs` && !this._stackAllowsKindAt(namedPath, kind)) {
19073
19074
  console.warn(`[ixfx-dock-host] addPanel: container "${target.container}" does not admit kind "${kind}"`);
19074
19075
  return false;
19076
+ } else if (op !== `tabs` && !this._stackAllowsEdgeSplitAt(namedPath)) {
19077
+ console.warn(`[ixfx-dock-host] addPanel: container "${target.container}" forbids edge splits (allowEdgeSplit: false)`);
19078
+ return false;
19075
19079
  } else stackPath = namedPath;
19076
19080
  } else if (target?.stack) {
19077
19081
  const found = findStackOf(this._model, target.stack);
@@ -19079,6 +19083,9 @@ let DockHostElement = class DockHostElement extends i {
19079
19083
  else if (op === `tabs` && !this._stackAllowsKindAt(found, kind)) {
19080
19084
  console.warn(`[ixfx-dock-host] addPanel: target stack does not admit kind "${kind}"`);
19081
19085
  return false;
19086
+ } else if (op !== `tabs` && !this._stackAllowsEdgeSplitAt(found)) {
19087
+ console.warn(`[ixfx-dock-host] addPanel: target stack forbids edge splits (allowEdgeSplit: false)`);
19088
+ return false;
19082
19089
  } else stackPath = found;
19083
19090
  }
19084
19091
  if (stackPath !== null && op !== `tabs`) {
@@ -19120,6 +19127,11 @@ let DockHostElement = class DockHostElement extends i {
19120
19127
  const stack = findNode(this._model, stackPath);
19121
19128
  return stack !== null && isStack(stack) && this._stackAllowsKind(stack, kind);
19122
19129
  }
19130
+ /** Whether the stack at `stackPath` permits an edge split beside it. */
19131
+ _stackAllowsEdgeSplitAt(stackPath) {
19132
+ const stack = findNode(this._model, stackPath);
19133
+ return stack === null || !isStack(stack) || this._leafAllowsEdgeSplit(stack);
19134
+ }
19123
19135
  removePanel(panelId) {
19124
19136
  if (!collectPanelIds(this._model).includes(panelId)) return false;
19125
19137
  this._returnAllToolbars();
@@ -19181,11 +19193,26 @@ let DockHostElement = class DockHostElement extends i {
19181
19193
  getContainerElements(name) {
19182
19194
  return this.getContainerPanels(name).map((id) => this._findPanelEl(id)).filter((el) => el !== void 0);
19183
19195
  }
19196
+ _defaultLeafRule() {
19197
+ return {
19198
+ allow: null,
19199
+ deny: /* @__PURE__ */ new Set(),
19200
+ allowEdgeSplit: true
19201
+ };
19202
+ }
19203
+ /** A rule entry that constrains nothing — safe to drop from the map. */
19204
+ _isEmptyLeafRule(entry) {
19205
+ return entry.allow === null && entry.deny.size === 0 && entry.allowEdgeSplit;
19206
+ }
19207
+ _warnUnknownContainer(method, key) {
19208
+ if (findStackPathByName(this._model, key) === null) console.warn(`[ixfx-dock-host] ${method}: no container named "${key}" yet; rule stored`);
19209
+ }
19184
19210
  /**
19185
19211
  * Restrict a named container to only the given panel kinds — e.g.
19186
19212
  * `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).
19213
+ * `addPanel`, and re-homing into the leaf then refuse other kinds. Whether
19214
+ * edge splits beside the leaf are offered is governed separately by
19215
+ * {@link setLeafPlacementPolicy}'s `allowEdgeSplit` (default `true`).
19189
19216
  * Pass `[]` to seal the leaf (nothing admitted). Warns when no stack
19190
19217
  * currently carries `name`, but the rule is still stored and applies once
19191
19218
  * one appears. Rules are host policy: kept across `loadLayout()`, never
@@ -19197,14 +19224,10 @@ let DockHostElement = class DockHostElement extends i {
19197
19224
  console.warn(`[ixfx-dock-host] setLeafExclusiveContents: empty container name`);
19198
19225
  return;
19199
19226
  }
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;
19227
+ const entry = this._leafContentRules.get(key) ?? this._defaultLeafRule();
19228
+ entry.allow = new Set(kinds.map((k) => k.trim()).filter((k) => k.length > 0));
19206
19229
  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`);
19230
+ this._warnUnknownContainer(`setLeafExclusiveContents`, key);
19208
19231
  }
19209
19232
  /**
19210
19233
  * Forbid the given panel kinds in a named container — e.g.
@@ -19218,21 +19241,53 @@ let DockHostElement = class DockHostElement extends i {
19218
19241
  console.warn(`[ixfx-dock-host] setLeafDenyContents: empty container name`);
19219
19242
  return;
19220
19243
  }
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);
19244
+ const entry = this._leafContentRules.get(key) ?? this._defaultLeafRule();
19245
+ entry.deny = new Set(kinds.map((k) => k.trim()).filter((k) => k.length > 0));
19246
+ if (this._isEmptyLeafRule(entry)) this._leafContentRules.delete(key);
19247
+ else this._leafContentRules.set(key, entry);
19248
+ this._warnUnknownContainer(`setLeafDenyContents`, key);
19249
+ }
19250
+ /**
19251
+ * Set (merge) the full placement policy for a named leaf: kind allow/deny
19252
+ * lists plus the `allowEdgeSplit` gate. Omitted fields are left unchanged
19253
+ * — pass `clearLeafPlacementPolicy(name)` to reset. `allowKinds` /
19254
+ * `denyKinds` are equivalent to {@link setLeafExclusiveContents} /
19255
+ * {@link setLeafDenyContents} and share the same storage.
19256
+ *
19257
+ * `allowEdgeSplit: false` enforces strict named-area containment: the edge
19258
+ * drop zones beside the leaf go inert for every placement route (pointer
19259
+ * preview and drop, `queryDropTarget` / `previewExternalDrop`,
19260
+ * `addPanel` with a split `op`), so a panel cannot escape the area by
19261
+ * splitting next to it. A permitted split still creates an *unnamed,
19262
+ * unconstrained* leaf — the policy is not propagated to it.
19263
+ *
19264
+ * Warns when no stack currently carries `name`; the policy is stored and
19265
+ * applies once one appears. Host policy: kept across `loadLayout()`, never
19266
+ * serialized.
19267
+ */
19268
+ setLeafPlacementPolicy(name, policy) {
19269
+ const key = name.trim();
19270
+ if (!key) {
19271
+ console.warn(`[ixfx-dock-host] setLeafPlacementPolicy: empty container name`);
19272
+ return;
19273
+ }
19274
+ const entry = this._leafContentRules.get(key) ?? this._defaultLeafRule();
19275
+ if (policy.allowKinds !== void 0) entry.allow = new Set(policy.allowKinds.map((k) => k.trim()).filter((k) => k.length > 0));
19276
+ if (policy.denyKinds !== void 0) entry.deny = new Set(policy.denyKinds.map((k) => k.trim()).filter((k) => k.length > 0));
19277
+ if (policy.allowEdgeSplit !== void 0) entry.allowEdgeSplit = policy.allowEdgeSplit;
19278
+ if (this._isEmptyLeafRule(entry)) this._leafContentRules.delete(key);
19228
19279
  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`);
19280
+ this._warnUnknownContainer(`setLeafPlacementPolicy`, key);
19230
19281
  }
19231
- /** Remove every content rule for a named container. */
19282
+ /** Remove every content rule and reset the edge-split gate for a named container. */
19232
19283
  clearLeafContentRestrictions(name) {
19233
19284
  this._leafContentRules.delete(name.trim());
19234
19285
  }
19235
- /** Copy of a container's rules, or null when unrestricted. */
19286
+ /** Alias of {@link clearLeafContentRestrictions} — clears kind rules and the edge-split gate. */
19287
+ clearLeafPlacementPolicy(name) {
19288
+ this._leafContentRules.delete(name.trim());
19289
+ }
19290
+ /** Copy of a container's kind rules, or null when unrestricted. */
19236
19291
  getLeafContentRestrictions(name) {
19237
19292
  const rules = this._leafContentRules.get(name.trim());
19238
19293
  if (rules === void 0) return null;
@@ -19241,6 +19296,16 @@ let DockHostElement = class DockHostElement extends i {
19241
19296
  deny: [...rules.deny]
19242
19297
  };
19243
19298
  }
19299
+ /** Resolved placement policy for a container, or null when it has none. */
19300
+ getLeafPlacementPolicy(name) {
19301
+ const rules = this._leafContentRules.get(name.trim());
19302
+ if (rules === void 0) return null;
19303
+ return {
19304
+ allowKinds: rules.allow === null ? null : [...rules.allow],
19305
+ denyKinds: [...rules.deny],
19306
+ allowEdgeSplit: rules.allowEdgeSplit
19307
+ };
19308
+ }
19244
19309
  /**
19245
19310
  * Geometric neighbours of a leaf (`name` or numeric `path`) — what sits
19246
19311
  * above / below / left / right of it on screen. Each entry is
@@ -19535,6 +19600,7 @@ let DockHostElement = class DockHostElement extends i {
19535
19600
  if (zone.operation === `tabs` && stack !== null && isStack(stack)) {
19536
19601
  if (!this._stackAllowsKind(stack, kind?.trim() ? kind.trim() : this._panelKind(panelId))) return false;
19537
19602
  }
19603
+ if (zone.operation !== `tabs` && stack !== null && isStack(stack) && !this._leafAllowsEdgeSplit(stack)) return false;
19538
19604
  if (this.canAccept && !this.canAccept(panelId, zone.operation)) return false;
19539
19605
  return true;
19540
19606
  }
@@ -21914,14 +21980,8 @@ let GridListElement = class GridListElement extends i {
21914
21980
  }
21915
21981
  },
21916
21982
  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);
21983
+ for (const node of event.composedPath()) if (node instanceof Element && node.classList.contains(`cell`)) return false;
21984
+ return true;
21925
21985
  }
21926
21986
  });
21927
21987
  this.#selectedSnapshot = /* @__PURE__ */ new Set();
@@ -21936,26 +21996,17 @@ let GridListElement = class GridListElement extends i {
21936
21996
  });
21937
21997
  };
21938
21998
  this.#onDragStart = (e, key) => {
21999
+ this.#doDragStart(key, () => e.preventDefault(), e);
22000
+ };
22001
+ this.#onSlottedDragStart = (e) => {
21939
22002
  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`);
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, () => {});
21959
22010
  };
21960
22011
  this.#onDragEnd = () => {
21961
22012
  this.#clearNativeDragState();
@@ -22132,7 +22183,6 @@ let GridListElement = class GridListElement extends i {
22132
22183
  align-items: center;
22133
22184
  justify-content: center;
22134
22185
  overflow: hidden;
22135
- draggable: false;
22136
22186
  }
22137
22187
  .thumb ::slotted(*:empty)::after {
22138
22188
  content: "Empty";
@@ -22611,6 +22661,7 @@ let GridListElement = class GridListElement extends i {
22611
22661
  this.#measureViewport(false);
22612
22662
  this.#syncItems();
22613
22663
  this.requestUpdate();
22664
+ this.#initSlottedDragInterceptor();
22614
22665
  }
22615
22666
  /** (Re)construct {@link #source} for the current mode and re-apply the filters. */
22616
22667
  #rebuildSource() {
@@ -22700,6 +22751,7 @@ let GridListElement = class GridListElement extends i {
22700
22751
  this.selection.clearAnchor();
22701
22752
  this.#syncAriaMultiselectable();
22702
22753
  }
22754
+ if (changed.has(`itemsDraggable`)) this.#syncSlottedDraggable();
22703
22755
  if (changed.has(`orientation`)) {
22704
22756
  const vp = this.#viewportRef.value;
22705
22757
  if (vp) {
@@ -23226,7 +23278,140 @@ let GridListElement = class GridListElement extends i {
23226
23278
  this.selection.handleClick(key, e);
23227
23279
  this.#emitItemEvent(`list-item-click`, key);
23228
23280
  }
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
+ }
23229
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
+ }
23230
23415
  #onDragEnd;
23231
23416
  #startPostDragSuppressTimer() {
23232
23417
  if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);