@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.d.ts +78 -9
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +245 -60
- package/bundle/index.js.map +1 -1
- package/dist/{grid-list-BXfY3XXX.js → grid-list-j5C7qtlG.js} +148 -29
- package/dist/grid-list-j5C7qtlG.js.map +1 -0
- package/dist/grid-list-x550wJ0X.d.ts.map +1 -1
- package/dist/grid-list.js +1 -1
- package/dist/index.d.ts +78 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -33
- package/dist/index.js.map +1 -1
- package/docs-user/README.md +3 -0
- package/docs-user/bottom-drawer.md +150 -0
- package/docs-user/grid-list.md +2 -1
- package/docs-user/index.json +17 -2
- package/docs-user/llms.txt +3 -0
- package/docs-user/pan-text.md +103 -0
- package/docs-user/text-shimmer.md +56 -0
- package/llms.txt +3 -0
- package/package.json +1 -1
- package/dist/grid-list-BXfY3XXX.js.map +0 -1
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
|
-
|
|
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
|
|
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
|
|
19188
|
-
* edge splits beside the 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
|
|
19201
|
-
|
|
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
|
-
|
|
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
|
|
19222
|
-
|
|
19223
|
-
|
|
19224
|
-
|
|
19225
|
-
|
|
19226
|
-
|
|
19227
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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 (
|
|
21918
|
-
|
|
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
|
-
|
|
21941
|
-
if (
|
|
21942
|
-
if (!
|
|
21943
|
-
const
|
|
21944
|
-
|
|
21945
|
-
|
|
21946
|
-
|
|
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);
|