@ixfx/components 0.13.1 → 0.13.3
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 +100 -14
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +2211 -636
- package/bundle/index.js.map +1 -1
- package/dist/ac-token.js +1 -1
- package/dist/{data-grid-Ck1Qx2H1.d.ts → data-grid-DHWJN66w.d.ts} +7 -5
- package/dist/{data-grid-Ck1Qx2H1.d.ts.map → data-grid-DHWJN66w.d.ts.map} +1 -1
- package/dist/data-grid.d.ts +1 -1
- package/dist/data-grid.js +210 -120
- package/dist/data-grid.js.map +1 -1
- package/dist/detail-list-C96B5Txz.js +2001 -0
- package/dist/detail-list-C96B5Txz.js.map +1 -0
- package/dist/{detail-list-C1xzr1z7.d.ts → detail-list-dX7jLO6b.d.ts} +12 -3
- package/dist/{detail-list-C1xzr1z7.d.ts.map → detail-list-dX7jLO6b.d.ts.map} +1 -1
- package/dist/detail-list.d.ts +2 -2
- package/dist/detail-list.js +1 -1680
- package/dist/{flat-list-DEDG6epz.d.ts → flat-list-Y98IqH00.d.ts} +5 -2
- package/dist/flat-list-Y98IqH00.d.ts.map +1 -0
- package/dist/flat-list.d.ts +1 -1
- package/dist/flat-list.js +33 -1
- package/dist/flat-list.js.map +1 -1
- package/dist/{grid-list-pefLORIr.d.ts → grid-list-CoT9e1jB.d.ts} +9 -2
- package/dist/{grid-list-pefLORIr.d.ts.map → grid-list-CoT9e1jB.d.ts.map} +1 -1
- package/dist/{grid-list-DjIl910A.js → grid-list-DeqAd9-h.js} +189 -40
- package/dist/grid-list-DeqAd9-h.js.map +1 -0
- package/dist/grid-list.d.ts +1 -1
- package/dist/grid-list.js +1 -1
- package/dist/{incr-search-D91M4Ozq.js → incr-search-JIhwB49y.js} +27 -5
- package/dist/incr-search-JIhwB49y.js.map +1 -0
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +1 -1
- package/dist/index-CnQPCywE.d.ts +325 -0
- package/dist/index-CnQPCywE.d.ts.map +1 -0
- package/dist/{index-CxZ61DO1.d.ts → index-Dlah7Zfi.d.ts} +8 -1
- package/dist/{index-CxZ61DO1.d.ts.map → index-Dlah7Zfi.d.ts.map} +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -25
- package/dist/index.js.map +1 -1
- package/dist/miller.d.ts +1 -1
- package/dist/miller.d.ts.map +1 -1
- package/dist/miller.js +146 -100
- package/dist/miller.js.map +1 -1
- package/dist/narrowed-text.js +1 -1
- package/dist/split-layout-C5SYbDrq.d.ts.map +1 -1
- package/dist/{split-layout-CXotkdbx.js → split-layout-Deoxy47d.js} +6 -1
- package/dist/{split-layout-CXotkdbx.js.map → split-layout-Deoxy47d.js.map} +1 -1
- package/dist/split-layout.js +1 -1
- package/dist/{tab-list-v8mKX7Fu.d.ts → tab-list-D_dmImJM.d.ts} +2 -3
- package/dist/tab-list-D_dmImJM.d.ts.map +1 -0
- package/dist/tabs.d.ts +1 -1
- package/dist/tabs.js +22 -0
- package/dist/tabs.js.map +1 -1
- package/dist/{tree-DGveys4K.js → tree-DD8OaqsK.js} +1010 -230
- package/dist/tree-DD8OaqsK.js.map +1 -0
- package/dist/tree.d.ts +4 -267
- package/dist/tree.js +1 -1
- package/dist/{vertical-list-CqBc-t8w.d.ts → vertical-list-lj_19DCx.d.ts} +3 -2
- package/dist/{vertical-list-CqBc-t8w.d.ts.map → vertical-list-lj_19DCx.d.ts.map} +1 -1
- package/dist/vertical-list.d.ts +1 -1
- package/dist/vertical-list.js +113 -24
- package/dist/vertical-list.js.map +1 -1
- package/docs-user/data-grid.md +74 -4
- package/docs-user/detail-list.md +65 -0
- package/docs-user/flat-list.md +14 -2
- package/docs-user/grid-list.md +81 -4
- package/docs-user/index.json +2 -2
- package/docs-user/miller.md +59 -3
- package/docs-user/tree.md +63 -2
- package/docs-user/vertical-list.md +9 -1
- package/package.json +6 -1
- package/dist/detail-list.js.map +0 -1
- package/dist/flat-list-DEDG6epz.d.ts.map +0 -1
- package/dist/grid-list-DjIl910A.js.map +0 -1
- package/dist/incr-search-D91M4Ozq.js.map +0 -1
- package/dist/tab-list-v8mKX7Fu.d.ts.map +0 -1
- package/dist/tree-DGveys4K.js.map +0 -1
- package/dist/tree.d.ts.map +0 -1
package/bundle/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { Colour } from "@ixfx/visual";
|
|
|
10
10
|
import { Trees } from "@ixfx/collections";
|
|
11
11
|
import { clamp, clamper, rangeStream, round, scaler } from "@ixfx/numbers";
|
|
12
12
|
import { classMap } from "lit/directives/class-map.js";
|
|
13
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
13
14
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
14
15
|
import { Easings } from "@ixfx/modulation";
|
|
15
16
|
import "@clinth/vcanvas";
|
|
@@ -2073,7 +2074,11 @@ function createElementListSearchAdapter(host, options = {}) {
|
|
|
2073
2074
|
const getLabel = options.getLabel ?? defaultLabel;
|
|
2074
2075
|
const getKey = options.getKey ?? ((el) => keyForElement(el) ?? defaultLabel(el));
|
|
2075
2076
|
const hidden = /* @__PURE__ */ new Set();
|
|
2076
|
-
const
|
|
2077
|
+
const observeContainer = options.observeContainer ?? true;
|
|
2078
|
+
const items = () => {
|
|
2079
|
+
if (options.getAllItems) return options.getAllItems();
|
|
2080
|
+
return Array.from(host.querySelectorAll(itemSelector));
|
|
2081
|
+
};
|
|
2077
2082
|
return {
|
|
2078
2083
|
getItems: items,
|
|
2079
2084
|
getLabel,
|
|
@@ -2082,7 +2087,9 @@ function createElementListSearchAdapter(host, options = {}) {
|
|
|
2082
2087
|
if (el) host.focusKey(getKey(el));
|
|
2083
2088
|
},
|
|
2084
2089
|
activateItem: (el) => {
|
|
2085
|
-
|
|
2090
|
+
const key = getKey(el);
|
|
2091
|
+
if (host.activateKey) host.activateKey(key);
|
|
2092
|
+
else el.dispatchEvent(new MouseEvent(`click`, {
|
|
2086
2093
|
bubbles: true,
|
|
2087
2094
|
composed: true
|
|
2088
2095
|
}));
|
|
@@ -2108,7 +2115,7 @@ function createElementListSearchAdapter(host, options = {}) {
|
|
|
2108
2115
|
highlightKeyOf: (el) => getKey(el),
|
|
2109
2116
|
highlightText: getLabel,
|
|
2110
2117
|
highlightKey: options.highlightKey,
|
|
2111
|
-
container: () => host
|
|
2118
|
+
container: observeContainer ? () => host : void 0
|
|
2112
2119
|
};
|
|
2113
2120
|
}
|
|
2114
2121
|
//#endregion
|
|
@@ -2426,6 +2433,10 @@ var ListSearchController = class {
|
|
|
2426
2433
|
}
|
|
2427
2434
|
this.#search.setItems(items);
|
|
2428
2435
|
}
|
|
2436
|
+
/** Re-paint highlights without rebuilding the item set. Use when the rendered window changes. */
|
|
2437
|
+
repaintHighlights() {
|
|
2438
|
+
this.#applyHighlights();
|
|
2439
|
+
}
|
|
2429
2440
|
/** Popover markup. Pair with `listSearchStyles` in the host's `static styles`. */
|
|
2430
2441
|
renderField() {
|
|
2431
2442
|
if (!this.enabled) return nothing;
|
|
@@ -2547,9 +2558,10 @@ var ListSearchController = class {
|
|
|
2547
2558
|
return;
|
|
2548
2559
|
}
|
|
2549
2560
|
const state = this.#search.state;
|
|
2561
|
+
const itemByKey = new Map(state.results.map((r) => [r.item.key, r.item]));
|
|
2550
2562
|
const targets = buildHighlightTargets(root, selector, state.positionsByItem, (el) => {
|
|
2551
2563
|
const key = keyOf(el);
|
|
2552
|
-
return key === void 0 ? void 0 :
|
|
2564
|
+
return key === void 0 ? void 0 : itemByKey.get(key);
|
|
2553
2565
|
}, this.#adapter.highlightText ?? ((el) => el.textContent ?? ``));
|
|
2554
2566
|
try {
|
|
2555
2567
|
applyHighlights(targets, { key: this.#highlightKey });
|
|
@@ -2673,6 +2685,16 @@ const listSearchStyles = css`
|
|
|
2673
2685
|
//#endregion
|
|
2674
2686
|
//#region src/incr-search/tree-list-search-adapter.ts
|
|
2675
2687
|
/**
|
|
2688
|
+
* The label's own text. Highlights are painted on one text node matched by its
|
|
2689
|
+
* exact content, but a Lit-rendered label also holds template whitespace and
|
|
2690
|
+
* sibling bindings (tooltips), so `textContent` is padded and never equals the
|
|
2691
|
+
* node's content. Take the first non-blank text node instead.
|
|
2692
|
+
*/
|
|
2693
|
+
function labelText(el) {
|
|
2694
|
+
for (const node of el.childNodes) if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? ``).trim().length > 0) return node.textContent ?? ``;
|
|
2695
|
+
return el.textContent ?? ``;
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2676
2698
|
* A {@link ListSearchAdapter} over any `TreeSearchHost` (`ixfx-tree-list`,
|
|
2677
2699
|
* `ixfx-miller-list`, `ixfx-crumb-navigation`). Navigate reveals the match
|
|
2678
2700
|
* (expands its ancestors, moves the cursor); select drives `selectMany`; filter
|
|
@@ -2700,6 +2722,7 @@ function createTreeListSearchAdapter(host, options = {}) {
|
|
|
2700
2722
|
},
|
|
2701
2723
|
highlightRoot: () => host.shadowRoot,
|
|
2702
2724
|
highlightSelector: options.labelSelector ?? `.tree-label`,
|
|
2725
|
+
highlightText: labelText,
|
|
2703
2726
|
highlightKeyOf: (el) => el.closest(`[data-key]`)?.dataset.key,
|
|
2704
2727
|
highlightKey: options.highlightKey
|
|
2705
2728
|
};
|
|
@@ -15220,6 +15243,10 @@ function sameKeys$4(a, b) {
|
|
|
15220
15243
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
15221
15244
|
return true;
|
|
15222
15245
|
}
|
|
15246
|
+
/** Element-wise reference equality, treating `undefined` (never computed) as different. */
|
|
15247
|
+
function sameRefs(a, b) {
|
|
15248
|
+
return a !== void 0 && a.length === b.length && a.every((v, i) => v === b[i]);
|
|
15249
|
+
}
|
|
15223
15250
|
let DataGridElement = class DataGridElement extends LitElement {
|
|
15224
15251
|
constructor(..._args) {
|
|
15225
15252
|
super(..._args);
|
|
@@ -15237,7 +15264,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15237
15264
|
this.groupBy = null;
|
|
15238
15265
|
this.allowGrouping = false;
|
|
15239
15266
|
this.crossGroupSelection = false;
|
|
15240
|
-
this.columnMenu = `
|
|
15267
|
+
this.columnMenu = `none`;
|
|
15241
15268
|
this.textColorColumn = ``;
|
|
15242
15269
|
this.iconColumn = ``;
|
|
15243
15270
|
this.badgeColumn = ``;
|
|
@@ -15258,9 +15285,9 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15258
15285
|
this._lastClickedCell = null;
|
|
15259
15286
|
this._editValue = ``;
|
|
15260
15287
|
this._tickled = new TickledController(this, {
|
|
15261
|
-
getItems: () => this.
|
|
15262
|
-
id:
|
|
15263
|
-
enabled:
|
|
15288
|
+
getItems: () => this.#tickledItems ??= this._flat.map((f) => ({
|
|
15289
|
+
id: f.key,
|
|
15290
|
+
enabled: f.type === `row`
|
|
15264
15291
|
})),
|
|
15265
15292
|
onStateChange: () => this.requestUpdate(),
|
|
15266
15293
|
hoverDisabled: () => this.#nativeDrag.isDragging
|
|
@@ -15307,6 +15334,9 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15307
15334
|
this._sortedRows = [];
|
|
15308
15335
|
this._flat = [];
|
|
15309
15336
|
this._flatKeys = [];
|
|
15337
|
+
this.#flatIndexByKey = /* @__PURE__ */ new Map();
|
|
15338
|
+
this.#flatVersion = 0;
|
|
15339
|
+
this.#notedFlatVersion = -1;
|
|
15310
15340
|
this.#searchKeyByRow = /* @__PURE__ */ new Map();
|
|
15311
15341
|
this.search = new ListSearchController(this, { adapter: this.#createSearchAdapter() });
|
|
15312
15342
|
this.#prevFlatKeys = [];
|
|
@@ -15318,7 +15348,6 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15318
15348
|
this._colWidthSum = 0;
|
|
15319
15349
|
this._raf = 0;
|
|
15320
15350
|
this.#viewportChangeRaf = 0;
|
|
15321
|
-
this.#appendInProgress = false;
|
|
15322
15351
|
this.#resortRaf = 0;
|
|
15323
15352
|
this._drag = null;
|
|
15324
15353
|
this._onFocusOut = (e) => {
|
|
@@ -15335,10 +15364,12 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15335
15364
|
const el = e.target;
|
|
15336
15365
|
cancelAnimationFrame(this._raf);
|
|
15337
15366
|
this._raf = requestAnimationFrame(() => {
|
|
15367
|
+
const before = this.#windowRows(2);
|
|
15338
15368
|
this._scrollTop = el.scrollTop;
|
|
15339
15369
|
this._scrollLeft = el.scrollLeft;
|
|
15340
15370
|
if (this._headerScrollEl) this._headerScrollEl.scrollLeft = el.scrollLeft;
|
|
15341
|
-
this
|
|
15371
|
+
const after = this.#windowRows(2);
|
|
15372
|
+
if (before.start !== after.start || before.end !== after.end) this.requestUpdate();
|
|
15342
15373
|
cancelAnimationFrame(this.#viewportChangeRaf);
|
|
15343
15374
|
this.#viewportChangeRaf = 0;
|
|
15344
15375
|
this.#emitViewportChangeIfChanged();
|
|
@@ -15377,7 +15408,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15377
15408
|
let nextIdx = idx + dir;
|
|
15378
15409
|
while (nextIdx >= 0 && nextIdx < rowKeys.length) {
|
|
15379
15410
|
const nk = rowKeys[nextIdx];
|
|
15380
|
-
const row = this
|
|
15411
|
+
const row = this.#rowEntry(nk)?.row;
|
|
15381
15412
|
const col = this._visibleColumns.find((c) => c.key === colKey);
|
|
15382
15413
|
if (row && col && this._isEditable(row, col)) {
|
|
15383
15414
|
this._startEdit(nk, colKey);
|
|
@@ -15426,9 +15457,9 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15426
15457
|
if (!key) break;
|
|
15427
15458
|
e.preventDefault();
|
|
15428
15459
|
if (!this._editing && this.editability === `user`) {
|
|
15429
|
-
const
|
|
15430
|
-
if (
|
|
15431
|
-
const row =
|
|
15460
|
+
const entry = this.#rowEntry(key);
|
|
15461
|
+
if (entry) {
|
|
15462
|
+
const row = entry.row;
|
|
15432
15463
|
if (this._lastClickedCell?.rowKey === key) {
|
|
15433
15464
|
const col = this._visibleColumns.find((c) => c.key === this._lastClickedCell.colKey);
|
|
15434
15465
|
if (col && this._isEditable(row, col)) {
|
|
@@ -15444,9 +15475,10 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15444
15475
|
}
|
|
15445
15476
|
}
|
|
15446
15477
|
if (!this._editing) {
|
|
15447
|
-
const
|
|
15478
|
+
const entry = this.#rowEntry(key);
|
|
15479
|
+
const idx = entry?.idx ?? -1;
|
|
15448
15480
|
if (this._selectedKeys.has(key)) {
|
|
15449
|
-
this._activateRow(key,
|
|
15481
|
+
this._activateRow(key, entry?.row, idx);
|
|
15450
15482
|
break;
|
|
15451
15483
|
}
|
|
15452
15484
|
if (this.interactionMode !== `manual` && this.interactionMode !== `checked`) this._replaceSelection(key);
|
|
@@ -15476,7 +15508,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15476
15508
|
next = !active.checked;
|
|
15477
15509
|
active.checked = next;
|
|
15478
15510
|
} else {
|
|
15479
|
-
const row = this
|
|
15511
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
15480
15512
|
next = !(this._editValue !== `` ? Boolean(this._editValue) : Boolean(row?.[colKey]));
|
|
15481
15513
|
}
|
|
15482
15514
|
if (isImmediate) this._commitImmediate(rowKey, colKey, next);
|
|
@@ -15745,6 +15777,15 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15745
15777
|
this.#postDragSuppressTimer = 0;
|
|
15746
15778
|
}, 0);
|
|
15747
15779
|
}
|
|
15780
|
+
#flatIndexByKey;
|
|
15781
|
+
/** Bumped whenever `_flat` changes; drives `#noteFlatMutation`. */
|
|
15782
|
+
#flatVersion;
|
|
15783
|
+
#notedFlatVersion;
|
|
15784
|
+
/** Inputs `_sortedRows` / `_flat` were last derived from — see `#syncDerivedRows`. */
|
|
15785
|
+
#sortInputs;
|
|
15786
|
+
#flatInputs;
|
|
15787
|
+
/** `_flat` projected for the tickled controller, cached until `_flat` changes. */
|
|
15788
|
+
#tickledItems;
|
|
15748
15789
|
#searchRegistry;
|
|
15749
15790
|
#unregisterSearchCommands;
|
|
15750
15791
|
#searchKeyByRow;
|
|
@@ -15803,7 +15844,6 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15803
15844
|
/** Key sets as of the last `datagrid-viewport-change` dispatch, for dedupe. */
|
|
15804
15845
|
#lastViewportRenderedKeys;
|
|
15805
15846
|
#lastViewportVisibleKeys;
|
|
15806
|
-
#appendInProgress;
|
|
15807
15847
|
#resortRaf;
|
|
15808
15848
|
connectedCallback() {
|
|
15809
15849
|
super.connectedCallback();
|
|
@@ -15876,7 +15916,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15876
15916
|
updated(changed) {
|
|
15877
15917
|
super.updated(changed);
|
|
15878
15918
|
if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`) || changed.has(`filterPredicate`) || changed.has(`rowHeight`)) this.#scheduleViewportChangeEmit();
|
|
15879
|
-
if (changed.has(`_editing`) || changed.has(`_editingRow`))
|
|
15919
|
+
if (changed.has(`_editing`) || changed.has(`_editingRow`)) this.updateComplete.then(() => {
|
|
15880
15920
|
let sel = null;
|
|
15881
15921
|
if (this._editing) sel = `.row[data-key="${this._editing.rowKey}"] .cell[data-col="${this._editing.colKey}"] input, .row[data-key="${this._editing.rowKey}"] .cell[data-col="${this._editing.colKey}"] select`;
|
|
15882
15922
|
else if (this._editingRow) sel = `.row[data-key="${this._editingRow}"] .cell input, .row[data-key="${this._editingRow}"] .cell select`;
|
|
@@ -15890,10 +15930,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15890
15930
|
super.willUpdate(changed);
|
|
15891
15931
|
this.setAttribute(`data-input-mode`, this._tickled.inputMode);
|
|
15892
15932
|
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
15893
|
-
|
|
15894
|
-
if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`)) this._sortedRows = this._applySort([...this.rows]);
|
|
15895
|
-
this._rebuildFlat();
|
|
15896
|
-
}
|
|
15933
|
+
this.#syncDerivedRows();
|
|
15897
15934
|
this._visibleColumns = this.columns.filter((c) => c.visible !== false);
|
|
15898
15935
|
let off = 0;
|
|
15899
15936
|
this._colOffsets = this._visibleColumns.map((c) => {
|
|
@@ -15903,7 +15940,53 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15903
15940
|
});
|
|
15904
15941
|
this._colWidthSum = off;
|
|
15905
15942
|
if (this.interactionMode === `checked`) this._colWidthSum += 28;
|
|
15906
|
-
this.#
|
|
15943
|
+
if (this.#flatVersion !== this.#notedFlatVersion) {
|
|
15944
|
+
this.#notedFlatVersion = this.#flatVersion;
|
|
15945
|
+
this.#noteFlatMutation();
|
|
15946
|
+
}
|
|
15947
|
+
}
|
|
15948
|
+
/**
|
|
15949
|
+
* Re-derive `_sortedRows` / `_flat` only when one of their inputs changed
|
|
15950
|
+
* identity. Scroll, hover, selection and edit-state updates touch none of
|
|
15951
|
+
* them, so those frames cost O(window) instead of O(rows).
|
|
15952
|
+
*/
|
|
15953
|
+
#syncDerivedRows() {
|
|
15954
|
+
const sortInputs = [
|
|
15955
|
+
this.rows,
|
|
15956
|
+
this.sortState,
|
|
15957
|
+
this.columns
|
|
15958
|
+
];
|
|
15959
|
+
const flatInputs = [
|
|
15960
|
+
this.filterPredicate,
|
|
15961
|
+
this.groupBy,
|
|
15962
|
+
this.groupLabel,
|
|
15963
|
+
this._collapsedGroups,
|
|
15964
|
+
this.getRowKey
|
|
15965
|
+
];
|
|
15966
|
+
const resort = !sameRefs(this.#sortInputs, sortInputs);
|
|
15967
|
+
if (resort) {
|
|
15968
|
+
this.#sortInputs = sortInputs;
|
|
15969
|
+
this._sortedRows = this._applySort([...this.rows]);
|
|
15970
|
+
}
|
|
15971
|
+
if (resort || !sameRefs(this.#flatInputs, flatInputs)) {
|
|
15972
|
+
this.#flatInputs = flatInputs;
|
|
15973
|
+
this._rebuildFlat();
|
|
15974
|
+
}
|
|
15975
|
+
}
|
|
15976
|
+
/** Mark the derived rows as matching the current inputs (after an incremental update). */
|
|
15977
|
+
#markDerivedRowsSynced() {
|
|
15978
|
+
this.#sortInputs = [
|
|
15979
|
+
this.rows,
|
|
15980
|
+
this.sortState,
|
|
15981
|
+
this.columns
|
|
15982
|
+
];
|
|
15983
|
+
this.#flatInputs = [
|
|
15984
|
+
this.filterPredicate,
|
|
15985
|
+
this.groupBy,
|
|
15986
|
+
this.groupLabel,
|
|
15987
|
+
this._collapsedGroups,
|
|
15988
|
+
this.getRowKey
|
|
15989
|
+
];
|
|
15907
15990
|
}
|
|
15908
15991
|
/**
|
|
15909
15992
|
* Diff the row-key list against the previous update. Rows that appeared get an
|
|
@@ -15924,10 +16007,9 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15924
16007
|
return;
|
|
15925
16008
|
}
|
|
15926
16009
|
const before = new Set(prev);
|
|
15927
|
-
const after = new Set(now);
|
|
15928
16010
|
const added = now.filter((k) => !before.has(k));
|
|
15929
|
-
const
|
|
15930
|
-
if (added.length === now.length || added.length === 0 &&
|
|
16011
|
+
const removedCount = prev.length - (now.length - added.length);
|
|
16012
|
+
if (added.length === now.length || added.length === 0 && removedCount === 0) return;
|
|
15931
16013
|
this.#enteringRowKeys = new Set(added);
|
|
15932
16014
|
this.toggleAttribute(`reflowing`, true);
|
|
15933
16015
|
if (this.#rowReflowTimer) clearTimeout(this.#rowReflowTimer);
|
|
@@ -15944,55 +16026,64 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
15944
16026
|
return String(this.groupBy(row));
|
|
15945
16027
|
}
|
|
15946
16028
|
_rebuildFlat() {
|
|
15947
|
-
const
|
|
15948
|
-
const
|
|
15949
|
-
if (!this.
|
|
15950
|
-
this._flat = source.map((r, i) => {
|
|
15951
|
-
const idx = filtered ? this._sortedRows.indexOf(r) : i;
|
|
15952
|
-
return {
|
|
15953
|
-
type: `row`,
|
|
15954
|
-
key: this.getRowKey(r, idx),
|
|
15955
|
-
row: r,
|
|
15956
|
-
idx
|
|
15957
|
-
};
|
|
15958
|
-
});
|
|
15959
|
-
this._flatKeys = this._flat.map((f) => f.key);
|
|
15960
|
-
return;
|
|
15961
|
-
}
|
|
15962
|
-
const groups = /* @__PURE__ */ new Map();
|
|
15963
|
-
for (const r of source) {
|
|
15964
|
-
const k = this._getGroupKey(r) ?? ``;
|
|
15965
|
-
const arr = groups.get(k);
|
|
15966
|
-
if (arr) arr.push(r);
|
|
15967
|
-
else groups.set(k, [r]);
|
|
15968
|
-
}
|
|
16029
|
+
const pred = this.filterPredicate;
|
|
16030
|
+
const sourceIdx = [];
|
|
16031
|
+
for (let i = 0; i < this._sortedRows.length; i++) if (!pred || pred(this._sortedRows[i])) sourceIdx.push(i);
|
|
15969
16032
|
const flat = [];
|
|
15970
16033
|
const keys = [];
|
|
15971
|
-
|
|
15972
|
-
const
|
|
15973
|
-
const
|
|
16034
|
+
const pushRow = (idx) => {
|
|
16035
|
+
const row = this._sortedRows[idx];
|
|
16036
|
+
const key = this.getRowKey(row, idx);
|
|
15974
16037
|
flat.push({
|
|
15975
|
-
type: `
|
|
15976
|
-
key
|
|
15977
|
-
|
|
15978
|
-
|
|
16038
|
+
type: `row`,
|
|
16039
|
+
key,
|
|
16040
|
+
row,
|
|
16041
|
+
idx
|
|
15979
16042
|
});
|
|
15980
|
-
keys.push(
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
16043
|
+
keys.push(key);
|
|
16044
|
+
};
|
|
16045
|
+
if (!this.groupBy) sourceIdx.forEach(pushRow);
|
|
16046
|
+
else {
|
|
16047
|
+
const groups = /* @__PURE__ */ new Map();
|
|
16048
|
+
for (const idx of sourceIdx) {
|
|
16049
|
+
const k = this._getGroupKey(this._sortedRows[idx]) ?? ``;
|
|
16050
|
+
const arr = groups.get(k);
|
|
16051
|
+
if (arr) arr.push(idx);
|
|
16052
|
+
else groups.set(k, [idx]);
|
|
16053
|
+
}
|
|
16054
|
+
for (const [gKey, idxs] of groups) {
|
|
16055
|
+
const label = this.groupLabel ? this.groupLabel(gKey, idxs.map((i) => this._sortedRows[i])) : gKey;
|
|
16056
|
+
const gId = `__group__${gKey}`;
|
|
15985
16057
|
flat.push({
|
|
15986
|
-
type: `
|
|
15987
|
-
key:
|
|
15988
|
-
|
|
15989
|
-
|
|
16058
|
+
type: `group`,
|
|
16059
|
+
key: gId,
|
|
16060
|
+
label,
|
|
16061
|
+
count: idxs.length
|
|
15990
16062
|
});
|
|
15991
|
-
keys.push(
|
|
16063
|
+
keys.push(gId);
|
|
16064
|
+
if (!this._collapsedGroups.has(gKey)) idxs.forEach(pushRow);
|
|
15992
16065
|
}
|
|
15993
16066
|
}
|
|
15994
16067
|
this._flat = flat;
|
|
15995
16068
|
this._flatKeys = keys;
|
|
16069
|
+
this.#rebuildFlatIndex();
|
|
16070
|
+
}
|
|
16071
|
+
#rebuildFlatIndex() {
|
|
16072
|
+
this.#flatIndexByKey.clear();
|
|
16073
|
+
this._flat.forEach((f, i) => this.#flatIndexByKey.set(f.key, i));
|
|
16074
|
+
this.#flatVersion++;
|
|
16075
|
+
this.#tickledItems = void 0;
|
|
16076
|
+
}
|
|
16077
|
+
/** Flat entry for a row key, or `undefined` for unknown keys and group headers. */
|
|
16078
|
+
#rowEntry(key) {
|
|
16079
|
+
const i = this.#flatIndexByKey.get(key);
|
|
16080
|
+
const entry = i === void 0 ? void 0 : this._flat[i];
|
|
16081
|
+
if (entry?.type === `row`) return entry;
|
|
16082
|
+
const idx = this._sortedRows.findIndex((r, j) => this.getRowKey(r, j) === key);
|
|
16083
|
+
return idx < 0 ? void 0 : {
|
|
16084
|
+
row: this._sortedRows[idx],
|
|
16085
|
+
idx
|
|
16086
|
+
};
|
|
15996
16087
|
}
|
|
15997
16088
|
_toggleGroup(gKey) {
|
|
15998
16089
|
const next = new Set(this._collapsedGroups);
|
|
@@ -16222,17 +16313,26 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16222
16313
|
}));
|
|
16223
16314
|
}
|
|
16224
16315
|
_scrollRowIntoView(key) {
|
|
16225
|
-
const idx = this.
|
|
16226
|
-
if (idx
|
|
16227
|
-
const
|
|
16228
|
-
const
|
|
16316
|
+
const idx = this.#flatIndexByKey.get(key);
|
|
16317
|
+
if (idx === void 0 || !this._viewportEl) return;
|
|
16318
|
+
const rh = this.rowHeight;
|
|
16319
|
+
const top = (idx > 0 && this._flat[idx - 1].type === `group` ? idx - 1 : idx) * rh;
|
|
16320
|
+
const bottom = (idx + 1) * rh;
|
|
16229
16321
|
const vt = this._viewportEl.scrollTop;
|
|
16230
|
-
const
|
|
16231
|
-
|
|
16232
|
-
|
|
16322
|
+
const vh = this._viewportEl.clientHeight;
|
|
16323
|
+
let next = vt;
|
|
16324
|
+
if (top < vt) next = top;
|
|
16325
|
+
else if (bottom > vt + vh) next = bottom - vh;
|
|
16326
|
+
if (next !== vt) {
|
|
16327
|
+
this._viewportEl.scrollTop = next;
|
|
16328
|
+
this._scrollTop = next;
|
|
16329
|
+
this.requestUpdate();
|
|
16330
|
+
}
|
|
16233
16331
|
}
|
|
16234
16332
|
_getRowForKey(key) {
|
|
16235
|
-
|
|
16333
|
+
const i = this.#flatIndexByKey.get(key);
|
|
16334
|
+
const entry = i === void 0 ? void 0 : this._flat[i];
|
|
16335
|
+
return entry?.type === `row` ? entry.row : void 0;
|
|
16236
16336
|
}
|
|
16237
16337
|
/** The selected row keys (read-only). */
|
|
16238
16338
|
get selectedKeys() {
|
|
@@ -16249,8 +16349,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16249
16349
|
* set when the selection actually changes.
|
|
16250
16350
|
*/
|
|
16251
16351
|
selectKeys(keys) {
|
|
16252
|
-
|
|
16253
|
-
let next = new Set([...keys].filter((k) => known.has(k)));
|
|
16352
|
+
let next = new Set([...keys].filter((k) => this._getRowForKey(k) !== void 0));
|
|
16254
16353
|
const multi = this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode`;
|
|
16255
16354
|
if (this.selectionMode === `none`) next = /* @__PURE__ */ new Set();
|
|
16256
16355
|
else if (!multi && next.size > 1) {
|
|
@@ -16276,7 +16375,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16276
16375
|
}
|
|
16277
16376
|
/** Move the cursor to this row key and scroll it into view. */
|
|
16278
16377
|
tickleRow(key) {
|
|
16279
|
-
if (!this.
|
|
16378
|
+
if (!this.#flatIndexByKey.has(key) || key.startsWith(`__group__`)) return;
|
|
16280
16379
|
this._tickled.focus(key);
|
|
16281
16380
|
this._scrollRowIntoView(key);
|
|
16282
16381
|
}
|
|
@@ -16290,25 +16389,21 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16290
16389
|
appendRows(rows) {
|
|
16291
16390
|
if (rows.length === 0) return;
|
|
16292
16391
|
const startIdx = this.rows.length;
|
|
16293
|
-
this.#
|
|
16392
|
+
const inSync = this._sortedRows.length === startIdx && sameRefs(this.#sortInputs, [
|
|
16393
|
+
this.rows,
|
|
16394
|
+
this.sortState,
|
|
16395
|
+
this.columns
|
|
16396
|
+
]);
|
|
16294
16397
|
this.rows = [...this.rows, ...rows];
|
|
16398
|
+
if (!inSync) return;
|
|
16295
16399
|
const appended = this.rows.slice(startIdx);
|
|
16296
|
-
|
|
16297
|
-
else {
|
|
16298
|
-
this._sortedRows = this._applySort([...this.rows]);
|
|
16299
|
-
this._rebuildFlat();
|
|
16300
|
-
this.#appendInProgress = false;
|
|
16301
|
-
this.requestUpdate();
|
|
16302
|
-
return;
|
|
16303
|
-
}
|
|
16400
|
+
this._sortedRows = [...this._sortedRows, ...appended];
|
|
16304
16401
|
this.#appendToFlat(appended, startIdx);
|
|
16305
|
-
this.#
|
|
16306
|
-
this.requestUpdate();
|
|
16402
|
+
this.#markDerivedRowsSynced();
|
|
16307
16403
|
if (this.sortState != null) {
|
|
16308
16404
|
cancelAnimationFrame(this.#resortRaf);
|
|
16309
16405
|
this.#resortRaf = requestAnimationFrame(() => {
|
|
16310
|
-
this
|
|
16311
|
-
this._rebuildFlat();
|
|
16406
|
+
this.#sortInputs = void 0;
|
|
16312
16407
|
this.requestUpdate();
|
|
16313
16408
|
});
|
|
16314
16409
|
}
|
|
@@ -16320,6 +16415,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16320
16415
|
}
|
|
16321
16416
|
for (let j = 0; j < appended.length; j++) {
|
|
16322
16417
|
const idx = startIdx + j;
|
|
16418
|
+
if (this.filterPredicate && !this.filterPredicate(appended[j])) continue;
|
|
16323
16419
|
const key = this.getRowKey(appended[j], idx);
|
|
16324
16420
|
this._flat.push({
|
|
16325
16421
|
type: `row`,
|
|
@@ -16328,7 +16424,10 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16328
16424
|
idx
|
|
16329
16425
|
});
|
|
16330
16426
|
this._flatKeys.push(key);
|
|
16427
|
+
this.#flatIndexByKey.set(key, this._flat.length - 1);
|
|
16331
16428
|
}
|
|
16429
|
+
this.#flatVersion++;
|
|
16430
|
+
this.#tickledItems = void 0;
|
|
16332
16431
|
}
|
|
16333
16432
|
_replaceSelection(key) {
|
|
16334
16433
|
if (this.selectionMode === `none`) return;
|
|
@@ -16434,8 +16533,8 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16434
16533
|
return !e || e === `auto`;
|
|
16435
16534
|
}
|
|
16436
16535
|
_isCellEditing(rowKey, colKey) {
|
|
16437
|
-
if (this.editability === `immediate` && this._isEditable(this
|
|
16438
|
-
if (this._editingRow === rowKey) return this._isEditable(this
|
|
16536
|
+
if (this.editability === `immediate` && this._isEditable(this.#rowEntry(rowKey)?.row ?? {}, this.columns.find((c) => c.key === colKey))) return true;
|
|
16537
|
+
if (this._editingRow === rowKey) return this._isEditable(this.#rowEntry(rowKey)?.row ?? {}, this.columns.find((c) => c.key === colKey));
|
|
16439
16538
|
return this._editing?.rowKey === rowKey && this._editing?.colKey === colKey;
|
|
16440
16539
|
}
|
|
16441
16540
|
_activateRow(rowKey, row, rowIndex) {
|
|
@@ -16451,7 +16550,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16451
16550
|
}
|
|
16452
16551
|
_startEdit(rowKey, colKey) {
|
|
16453
16552
|
if (this.editability === `disabled`) return;
|
|
16454
|
-
const row = this
|
|
16553
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
16455
16554
|
if (!row) return;
|
|
16456
16555
|
const col = this.columns.find((c) => c.key === colKey);
|
|
16457
16556
|
if (!col || !this._isEditable(row, col)) return;
|
|
@@ -16469,7 +16568,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16469
16568
|
/** Programmatic: put all editable cells of a row in edit mode. */
|
|
16470
16569
|
startRowEdit(rowKey) {
|
|
16471
16570
|
if (this.editability === `disabled`) return;
|
|
16472
|
-
if (!this
|
|
16571
|
+
if (!this.#rowEntry(rowKey)?.row) return;
|
|
16473
16572
|
this._editing = null;
|
|
16474
16573
|
this._editingRow = rowKey;
|
|
16475
16574
|
this.requestUpdate();
|
|
@@ -16537,7 +16636,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16537
16636
|
const target = this._editing;
|
|
16538
16637
|
if (!target) return;
|
|
16539
16638
|
const { rowKey, colKey } = target;
|
|
16540
|
-
const row = this
|
|
16639
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
16541
16640
|
if (!row) {
|
|
16542
16641
|
this._editing = null;
|
|
16543
16642
|
return;
|
|
@@ -16562,7 +16661,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16562
16661
|
}));
|
|
16563
16662
|
}
|
|
16564
16663
|
_tabToNext(rowKey, curColKey, dir) {
|
|
16565
|
-
const row = this
|
|
16664
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
16566
16665
|
if (!row) return;
|
|
16567
16666
|
const cols = this._visibleColumns;
|
|
16568
16667
|
let next = cols.findIndex((c) => c.key === curColKey) + dir;
|
|
@@ -16583,7 +16682,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16583
16682
|
this.requestUpdate();
|
|
16584
16683
|
}
|
|
16585
16684
|
_commitImmediate(rowKey, colKey, value) {
|
|
16586
|
-
const row = this
|
|
16685
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
16587
16686
|
if (!row) return;
|
|
16588
16687
|
const oldValue = row[colKey];
|
|
16589
16688
|
if (oldValue === value) return;
|
|
@@ -16706,11 +16805,19 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16706
16805
|
window.addEventListener(`pointermove`, onMove);
|
|
16707
16806
|
window.addEventListener(`pointerup`, onUp);
|
|
16708
16807
|
}
|
|
16709
|
-
|
|
16808
|
+
/** Flat-index window `[start, end)` intersecting the viewport, widened by `overscan` rows each side. */
|
|
16809
|
+
#windowRows(overscan) {
|
|
16710
16810
|
const rh = this.rowHeight;
|
|
16711
16811
|
return {
|
|
16712
|
-
|
|
16713
|
-
|
|
16812
|
+
start: Math.max(0, Math.floor(this._scrollTop / rh) - overscan),
|
|
16813
|
+
end: Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh) + overscan)
|
|
16814
|
+
};
|
|
16815
|
+
}
|
|
16816
|
+
getVisibleRange() {
|
|
16817
|
+
const { start: startRow, end: endRow } = this.#windowRows(2);
|
|
16818
|
+
return {
|
|
16819
|
+
startRow,
|
|
16820
|
+
endRow,
|
|
16714
16821
|
startCol: this._findFirstVisibleCol(this._scrollLeft),
|
|
16715
16822
|
endCol: this._findLastVisibleCol(this._scrollLeft + this._viewportW)
|
|
16716
16823
|
};
|
|
@@ -16725,10 +16832,8 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16725
16832
|
* `ixfx-grid-list.visibleKeys()` for the `datagrid-viewport-change` detail.
|
|
16726
16833
|
*/
|
|
16727
16834
|
#visibleKeys() {
|
|
16728
|
-
const
|
|
16729
|
-
|
|
16730
|
-
const endRow = Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh));
|
|
16731
|
-
return this._flat.slice(startRow, endRow).filter((f) => f.type === `row`).map((f) => f.key);
|
|
16835
|
+
const { start, end } = this.#windowRows(0);
|
|
16836
|
+
return this._flat.slice(start, end).filter((f) => f.type === `row`).map((f) => f.key);
|
|
16732
16837
|
}
|
|
16733
16838
|
/** Coalesce viewport-change dispatches to at most one per animation frame. */
|
|
16734
16839
|
#scheduleViewportChangeEmit() {
|
|
@@ -16777,17 +16882,28 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16777
16882
|
}
|
|
16778
16883
|
render() {
|
|
16779
16884
|
const rh = this.rowHeight;
|
|
16780
|
-
const
|
|
16781
|
-
const
|
|
16782
|
-
|
|
16885
|
+
const { start: startRow, end: endRow } = this.#windowRows(2);
|
|
16886
|
+
const visibleFlat = this._flat.slice(startRow, endRow).map((entry, i) => ({
|
|
16887
|
+
entry,
|
|
16888
|
+
absIdx: startRow + i
|
|
16889
|
+
}));
|
|
16890
|
+
const pinnedKey = this._editing?.rowKey ?? this._editingRow;
|
|
16891
|
+
const pinnedIdx = pinnedKey ? this.#flatIndexByKey.get(pinnedKey) : void 0;
|
|
16892
|
+
if (pinnedIdx !== void 0) {
|
|
16893
|
+
if (pinnedIdx >= startRow && pinnedIdx < endRow) visibleFlat.splice(pinnedIdx - startRow, 1);
|
|
16894
|
+
visibleFlat.unshift({
|
|
16895
|
+
entry: this._flat[pinnedIdx],
|
|
16896
|
+
absIdx: pinnedIdx
|
|
16897
|
+
});
|
|
16898
|
+
}
|
|
16783
16899
|
const showCheckbox = this.interactionMode === `checked`;
|
|
16784
16900
|
return html`
|
|
16785
|
-
<div class="grid">
|
|
16901
|
+
<div class="grid" role="grid" aria-rowcount=${this._flat.length + 1} aria-colcount=${this._visibleColumns.length} aria-multiselectable=${this.selectionMode === `multiple` ? `true` : `false`}>
|
|
16786
16902
|
<div class="header-bar">
|
|
16787
|
-
<div class="header-scroll scroll-fade-x">
|
|
16788
|
-
${showCheckbox ? html`<div class="header-cell checkbox-col"></div>` : nothing}
|
|
16903
|
+
<div class="header-scroll scroll-fade-x" role="row" aria-rowindex="1">
|
|
16904
|
+
${showCheckbox ? html`<div class="header-cell checkbox-col" role="columnheader"></div>` : nothing}
|
|
16789
16905
|
${repeat(this._visibleColumns, (c) => c.key, (col, idx) => html`
|
|
16790
|
-
<div class="header-cell ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
|
|
16906
|
+
<div class="header-cell ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" role="columnheader" aria-sort=${this.sortState?.key === col.key ? this.sortState.direction === `asc` ? `ascending` : `descending` : `none`} style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
|
|
16791
16907
|
@click=${(e) => this._onHeaderClick(col, e)}
|
|
16792
16908
|
@pointerenter=${() => this._tickled.handlePointerEnter(col.key)}>
|
|
16793
16909
|
${col.reorderable !== false ? html`<span class="reorder-handle" draggable="false"
|
|
@@ -16810,7 +16926,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16810
16926
|
return last >= 0 ? this._colOffsets[last] + (this._visibleColumns[last].width ?? 150) : 0;
|
|
16811
16927
|
})()}px"></div>` : nothing}
|
|
16812
16928
|
</div>
|
|
16813
|
-
${this.columnMenu
|
|
16929
|
+
${this.columnMenu !== `auto` ? nothing : html`
|
|
16814
16930
|
<ixfx-menu-trigger label="⚙" placement="bottom-end" @menu-command=${(e) => {
|
|
16815
16931
|
const cmd = e.detail.command;
|
|
16816
16932
|
const checked = e.detail.checked;
|
|
@@ -16854,12 +16970,11 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16854
16970
|
</div>
|
|
16855
16971
|
<div class="viewport scroll-fade-y" @scroll=${this._onScroll}>
|
|
16856
16972
|
<div class="spacer" style="height:${this._flat.length * rh}px; width:${this._colWidthSum}px">
|
|
16857
|
-
${repeat(visibleFlat, (f) => f.key, (entry,
|
|
16858
|
-
const absIdx = startRow + i;
|
|
16973
|
+
${repeat(visibleFlat, (f) => f.entry.key, ({ entry, absIdx }) => {
|
|
16859
16974
|
if (entry.type === `group`) {
|
|
16860
16975
|
const gKey = entry.key.replace(/^__group__/, ``);
|
|
16861
16976
|
const collapsed = this._collapsedGroups.has(gKey);
|
|
16862
|
-
return html`<div class="group-row ${collapsed ? `collapsed` : ``}" style="top:${absIdx * rh}px; height:${rh}px" @click=${() => this._toggleGroup(gKey)}><span class="caret"><ixfx-icon name="chevron-down" size="12"></ixfx-icon></span><span>${entry.label}</span><span class="count">(${entry.count})</span><span class="divider"></span></div>`;
|
|
16977
|
+
return html`<div class="group-row ${collapsed ? `collapsed` : ``}" role="row" aria-rowindex=${absIdx + 2} style="top:${absIdx * rh}px; height:${rh}px" @click=${() => this._toggleGroup(gKey)}><span class="caret"><ixfx-icon name="chevron-down" size="12"></ixfx-icon></span><span role="rowheader" aria-expanded=${collapsed ? `false` : `true`}>${entry.label}</span><span class="count">(${entry.count})</span><span class="divider"></span></div>`;
|
|
16863
16978
|
}
|
|
16864
16979
|
const row = entry.row;
|
|
16865
16980
|
const absIdx2 = entry.idx;
|
|
@@ -16867,7 +16982,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16867
16982
|
const selected = this._selectedKeys.has(rowKey);
|
|
16868
16983
|
const tickled = this._tickled.isTickled(rowKey);
|
|
16869
16984
|
return html`
|
|
16870
|
-
<div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.zebraStripes && absIdx2 % 2 === 1 ? `zebra-odd` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``} ${this.#nativeDragging && rowKey === this.#dragOriginKey ? `dragging-origin` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
|
|
16985
|
+
<div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.zebraStripes && absIdx2 % 2 === 1 ? `zebra-odd` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``} ${this.#nativeDragging && rowKey === this.#dragOriginKey ? `dragging-origin` : ``}" role="row" aria-rowindex=${absIdx + 2} aria-selected=${selected ? `true` : `false`} style="top:${absIdx * rh}px; height:${rh}px"
|
|
16871
16986
|
data-key=${rowKey}
|
|
16872
16987
|
draggable=${this.#nativeDrag.draggableAttr}
|
|
16873
16988
|
@click=${(e) => {
|
|
@@ -16896,7 +17011,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16896
17011
|
const immediate = this.editability === `immediate` || this._editingRow === rowKey;
|
|
16897
17012
|
const curVal = immediate ? val : this._editValue ?? val;
|
|
16898
17013
|
const effectiveDataType = col.dataType ?? `text`;
|
|
16899
|
-
if (effectiveDataType === `boolean` && (col.editor === `auto` || col.editor === `select` || !col.editor)) return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px;justify-content:center;align-items:center"><input type="checkbox" .checked=${Boolean(curVal)} @change=${(e) => {
|
|
17014
|
+
if (effectiveDataType === `boolean` && (col.editor === `auto` || col.editor === `select` || !col.editor)) return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px;justify-content:center;align-items:center"><input type="checkbox" .checked=${Boolean(curVal)} @change=${(e) => {
|
|
16900
17015
|
const v = e.target.checked;
|
|
16901
17016
|
if (immediate) this._commitImmediate(rowKey, col.key, v);
|
|
16902
17017
|
else {
|
|
@@ -16906,7 +17021,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16906
17021
|
}} /></div>`;
|
|
16907
17022
|
if (col.editor === `select`) {
|
|
16908
17023
|
const opts = col.editorOptions ?? [];
|
|
16909
|
-
return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px"><select @change=${(e) => {
|
|
17024
|
+
return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px"><select @change=${(e) => {
|
|
16910
17025
|
const v = e.target.value;
|
|
16911
17026
|
if (immediate) this._commitImmediate(rowKey, col.key, v);
|
|
16912
17027
|
else {
|
|
@@ -16952,11 +17067,11 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16952
17067
|
this._editingRow = null;
|
|
16953
17068
|
}
|
|
16954
17069
|
};
|
|
16955
|
-
return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px">${factory(ctx)}</div>`;
|
|
17070
|
+
return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px">${factory(ctx)}</div>`;
|
|
16956
17071
|
}
|
|
16957
17072
|
const inputType = effectiveDataType === `number` ? `number` : `text`;
|
|
16958
17073
|
const inputValue = effectiveDataType === `number` ? String(curVal ?? ``) : String(curVal ?? ``);
|
|
16959
|
-
return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px"><input type=${inputType} .value=${inputValue} @input=${(e) => this._editValue = e.target.value} @keydown=${(e) => {
|
|
17074
|
+
return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px"><input type=${inputType} .value=${inputValue} @input=${(e) => this._editValue = e.target.value} @keydown=${(e) => {
|
|
16960
17075
|
if (e.key === `Enter`) this._commitEdit();
|
|
16961
17076
|
if (e.key === `Escape`) {
|
|
16962
17077
|
this._editing = null;
|
|
@@ -16980,7 +17095,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
16980
17095
|
const canPan = this.cellOverflow === `pan-text` && !col.renderer && (selected || tickled);
|
|
16981
17096
|
const look = this._cellLook(row, col.key);
|
|
16982
17097
|
const cellStyle = look.textColor === void 0 ? `` : `color:${look.textColor};`;
|
|
16983
|
-
return html`<div class="cell ${textEditable ? `editable-text` : ``} ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" data-col=${col.key} style="width:${col.width ?? 150}px; justify-content:${col.align === `right` ? `flex-end` : col.align === `center` ? `center` : `flex-start`}; ${indent} ${cellStyle}"
|
|
17098
|
+
return html`<div class="cell ${textEditable ? `editable-text` : ``} ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px; justify-content:${col.align === `right` ? `flex-end` : col.align === `center` ? `center` : `flex-start`}; ${indent} ${cellStyle}"
|
|
16984
17099
|
@click=${(e) => {
|
|
16985
17100
|
this._lastClickedCell = {
|
|
16986
17101
|
rowKey,
|
|
@@ -17095,8 +17210,6 @@ __decorate([property({
|
|
|
17095
17210
|
__decorate([state()], DataGridElement.prototype, "_resizingColKey", void 0);
|
|
17096
17211
|
__decorate([state()], DataGridElement.prototype, "_animatingColKeys", void 0);
|
|
17097
17212
|
__decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
|
|
17098
|
-
__decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
|
|
17099
|
-
__decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
|
|
17100
17213
|
__decorate([state()], DataGridElement.prototype, "_viewportH", void 0);
|
|
17101
17214
|
__decorate([state()], DataGridElement.prototype, "_viewportW", void 0);
|
|
17102
17215
|
__decorate([state()], DataGridElement.prototype, "_collapsedGroups", void 0);
|
|
@@ -17374,6 +17487,123 @@ __decorate([state()], DataTableElement.prototype, "_detailIndex", void 0);
|
|
|
17374
17487
|
__decorate([state()], DataTableElement.prototype, "_stacked", void 0);
|
|
17375
17488
|
DataTableElement = __decorate([safeCustomElement(`ixfx-data-table`)], DataTableElement);
|
|
17376
17489
|
//#endregion
|
|
17490
|
+
//#region src/detail-list/detail-list-geometry.ts
|
|
17491
|
+
const EMPTY_GEOMETRY = {
|
|
17492
|
+
columns: [],
|
|
17493
|
+
positions: /* @__PURE__ */ new Map(),
|
|
17494
|
+
tops: /* @__PURE__ */ new Map(),
|
|
17495
|
+
lefts: /* @__PURE__ */ new Map()
|
|
17496
|
+
};
|
|
17497
|
+
function contentOrigin(scroller) {
|
|
17498
|
+
const r = scroller.getBoundingClientRect();
|
|
17499
|
+
return {
|
|
17500
|
+
left: r.left - scroller.scrollLeft,
|
|
17501
|
+
top: r.top - scroller.scrollTop
|
|
17502
|
+
};
|
|
17503
|
+
}
|
|
17504
|
+
/** Items closer than this (px) horizontally share a column (sub-pixel layout). */
|
|
17505
|
+
const COLUMN_TOLERANCE_PX = 1;
|
|
17506
|
+
/**
|
|
17507
|
+
* Measure `items` (one rect read each — a single layout flush) and bucket them
|
|
17508
|
+
* into columns. Zero-sized items (e.g. `display:none`) are skipped.
|
|
17509
|
+
*/
|
|
17510
|
+
function measureGeometry(items, origin) {
|
|
17511
|
+
const measured = [];
|
|
17512
|
+
for (const el of items) {
|
|
17513
|
+
const r = el.getBoundingClientRect();
|
|
17514
|
+
if (r.width === 0 && r.height === 0) continue;
|
|
17515
|
+
measured.push({
|
|
17516
|
+
el,
|
|
17517
|
+
left: r.left - origin.left,
|
|
17518
|
+
top: r.top - origin.top,
|
|
17519
|
+
width: r.width
|
|
17520
|
+
});
|
|
17521
|
+
}
|
|
17522
|
+
measured.sort((a, b) => a.left - b.left || a.top - b.top);
|
|
17523
|
+
const columns = [];
|
|
17524
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17525
|
+
const tops = /* @__PURE__ */ new Map();
|
|
17526
|
+
const lefts = /* @__PURE__ */ new Map();
|
|
17527
|
+
for (const m of measured) {
|
|
17528
|
+
let col = columns.at(-1);
|
|
17529
|
+
if (!col || m.left - col.left > COLUMN_TOLERANCE_PX) {
|
|
17530
|
+
col = {
|
|
17531
|
+
left: m.left,
|
|
17532
|
+
width: m.width,
|
|
17533
|
+
items: []
|
|
17534
|
+
};
|
|
17535
|
+
columns.push(col);
|
|
17536
|
+
}
|
|
17537
|
+
positions.set(m.el, {
|
|
17538
|
+
col: columns.length - 1,
|
|
17539
|
+
row: col.items.length
|
|
17540
|
+
});
|
|
17541
|
+
col.items.push(m.el);
|
|
17542
|
+
col.width = Math.max(col.width, m.width);
|
|
17543
|
+
tops.set(m.el, m.top);
|
|
17544
|
+
lefts.set(m.el, m.left);
|
|
17545
|
+
}
|
|
17546
|
+
return {
|
|
17547
|
+
columns,
|
|
17548
|
+
positions,
|
|
17549
|
+
tops,
|
|
17550
|
+
lefts
|
|
17551
|
+
};
|
|
17552
|
+
}
|
|
17553
|
+
/**
|
|
17554
|
+
* Whether `geometry` still matches layout, judged by re-reading a few probe
|
|
17555
|
+
* elements. Layout shifts that matter (column width, row height, reflow after
|
|
17556
|
+
* an insert) move at least one of first / cursor / last.
|
|
17557
|
+
*/
|
|
17558
|
+
function geometryMatches(geometry, probes, origin) {
|
|
17559
|
+
for (const el of probes) {
|
|
17560
|
+
if (!el) continue;
|
|
17561
|
+
const left = geometry.lefts.get(el);
|
|
17562
|
+
const top = geometry.tops.get(el);
|
|
17563
|
+
if (left === void 0 || top === void 0) return false;
|
|
17564
|
+
const r = el.getBoundingClientRect();
|
|
17565
|
+
if (Math.abs(r.left - origin.left - left) > .5 || Math.abs(r.top - origin.top - top) > .5) return false;
|
|
17566
|
+
}
|
|
17567
|
+
return true;
|
|
17568
|
+
}
|
|
17569
|
+
/** First index in `[lo, hi)` for which `pred` holds, given `pred` is monotonic (false… then true…). */
|
|
17570
|
+
function partitionPoint(lo, hi, pred) {
|
|
17571
|
+
while (lo < hi) {
|
|
17572
|
+
const mid = lo + hi >> 1;
|
|
17573
|
+
if (pred(mid)) hi = mid;
|
|
17574
|
+
else lo = mid + 1;
|
|
17575
|
+
}
|
|
17576
|
+
return lo;
|
|
17577
|
+
}
|
|
17578
|
+
/**
|
|
17579
|
+
* Items (in DOM order) whose box intersects the content-space span
|
|
17580
|
+
* `[start, end)`, found with O(log n) layout reads — no full measure.
|
|
17581
|
+
* Relies on column flow: DOM order is column order, so `left` only moves in
|
|
17582
|
+
* one direction along the list (increasing for LTR, decreasing for RTL).
|
|
17583
|
+
*/
|
|
17584
|
+
function itemsInSpanByOrder(items, origin, start, end) {
|
|
17585
|
+
if (items.length === 0) return [];
|
|
17586
|
+
const box = (i) => {
|
|
17587
|
+
const r = items[i].getBoundingClientRect();
|
|
17588
|
+
return {
|
|
17589
|
+
left: r.left - origin.left,
|
|
17590
|
+
right: r.right - origin.left
|
|
17591
|
+
};
|
|
17592
|
+
};
|
|
17593
|
+
const first = box(0);
|
|
17594
|
+
const last = box(items.length - 1);
|
|
17595
|
+
if (first.left === last.left) return first.right > start && first.left < end ? items : [];
|
|
17596
|
+
const n = items.length;
|
|
17597
|
+
if (last.left > first.left) {
|
|
17598
|
+
const from = partitionPoint(0, n, (i) => box(i).right > start);
|
|
17599
|
+
const to = partitionPoint(from, n, (i) => box(i).left >= end);
|
|
17600
|
+
return items.slice(from, to);
|
|
17601
|
+
}
|
|
17602
|
+
const from = partitionPoint(0, n, (i) => box(i).left < end);
|
|
17603
|
+
const to = partitionPoint(from, n, (i) => box(i).right <= start);
|
|
17604
|
+
return items.slice(from, to);
|
|
17605
|
+
}
|
|
17606
|
+
//#endregion
|
|
17377
17607
|
//#region src/list-group-header/list-group-header.ts
|
|
17378
17608
|
let ListGroupHeaderElement = class ListGroupHeaderElement extends LitElement {
|
|
17379
17609
|
constructor(..._args) {
|
|
@@ -17534,6 +17764,50 @@ function sameKeys$3(a, b) {
|
|
|
17534
17764
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
17535
17765
|
return true;
|
|
17536
17766
|
}
|
|
17767
|
+
function sameMembers(a, b) {
|
|
17768
|
+
if (a.size !== b.size) return false;
|
|
17769
|
+
for (const el of a) if (!b.has(el)) return false;
|
|
17770
|
+
return true;
|
|
17771
|
+
}
|
|
17772
|
+
/** Remove `attr` from elements leaving the set, add it to elements entering it. */
|
|
17773
|
+
function applySetDiff(prev, next, attr) {
|
|
17774
|
+
for (const el of prev) if (!next.has(el)) el.removeAttribute(attr);
|
|
17775
|
+
for (const el of next) if (!prev.has(el)) el.setAttribute(attr, ``);
|
|
17776
|
+
}
|
|
17777
|
+
/** Attributes on an item that change list membership, visibility, keys or selectability. */
|
|
17778
|
+
const ITEM_CACHE_ATTRS = [
|
|
17779
|
+
`hidden`,
|
|
17780
|
+
GROUP_COLLAPSED_ATTR,
|
|
17781
|
+
`not-checked`,
|
|
17782
|
+
`data-key`,
|
|
17783
|
+
`data-value`
|
|
17784
|
+
];
|
|
17785
|
+
/** Whether a mutation record can change the item caches (direct children / their cache attributes). */
|
|
17786
|
+
function affectsItemCaches(host, record) {
|
|
17787
|
+
if (record.type === `childList`) return record.target === host;
|
|
17788
|
+
return record.target.parentElement === host;
|
|
17789
|
+
}
|
|
17790
|
+
/**
|
|
17791
|
+
* Make `parent`'s managed children equal `nodes`, in order, with the fewest
|
|
17792
|
+
* DOM moves — a single insert stays a single move instead of re-appending
|
|
17793
|
+
* every row. `nodes` must contain every managed child; unmanaged children
|
|
17794
|
+
* (text, foreign elements) are left where they are.
|
|
17795
|
+
*/
|
|
17796
|
+
function syncChildOrder(parent, nodes, managed) {
|
|
17797
|
+
const nextManaged = (from) => {
|
|
17798
|
+
let el = from;
|
|
17799
|
+
while (el && !managed(el)) el = el.nextElementSibling;
|
|
17800
|
+
return el;
|
|
17801
|
+
};
|
|
17802
|
+
let cursor = nextManaged(parent.firstElementChild);
|
|
17803
|
+
for (const node of nodes) {
|
|
17804
|
+
if (node === cursor) {
|
|
17805
|
+
cursor = nextManaged(cursor.nextElementSibling);
|
|
17806
|
+
continue;
|
|
17807
|
+
}
|
|
17808
|
+
parent.insertBefore(node, cursor);
|
|
17809
|
+
}
|
|
17810
|
+
}
|
|
17537
17811
|
let DetailListElement = class DetailListElement extends LitElement {
|
|
17538
17812
|
constructor(..._args) {
|
|
17539
17813
|
super(..._args);
|
|
@@ -17554,10 +17828,15 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17554
17828
|
this.crossGroupSelection = false;
|
|
17555
17829
|
this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
17556
17830
|
this.#itemData = /* @__PURE__ */ new WeakMap();
|
|
17557
|
-
this.#
|
|
17558
|
-
this.#
|
|
17559
|
-
this.#
|
|
17831
|
+
this.#itemIndex = /* @__PURE__ */ new WeakMap();
|
|
17832
|
+
this.#geometry = EMPTY_GEOMETRY;
|
|
17833
|
+
this.#geometryDirty = true;
|
|
17560
17834
|
this.#geometryScheduled = false;
|
|
17835
|
+
this.#appliedSelected = /* @__PURE__ */ new Set();
|
|
17836
|
+
this.#appliedChecked = /* @__PURE__ */ new Set();
|
|
17837
|
+
this.#itemStatesFullSync = true;
|
|
17838
|
+
this.#hasCheckboxes = false;
|
|
17839
|
+
this.#draggableApplied = false;
|
|
17561
17840
|
this.#sortRaf = 0;
|
|
17562
17841
|
this.#viewportScrollRaf = 0;
|
|
17563
17842
|
this.#animatedItems = /* @__PURE__ */ new WeakSet();
|
|
@@ -17596,10 +17875,14 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17596
17875
|
}
|
|
17597
17876
|
});
|
|
17598
17877
|
this.tickled = new TickledController(this, {
|
|
17599
|
-
getItems: () =>
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
17878
|
+
getItems: () => {
|
|
17879
|
+
const visible = this._getVisibleItems();
|
|
17880
|
+
this.#tickledItemsCache ??= visible.map((el) => ({
|
|
17881
|
+
id: el,
|
|
17882
|
+
enabled: !el.hasAttribute(`not-checked`)
|
|
17883
|
+
}));
|
|
17884
|
+
return this.#tickledItemsCache;
|
|
17885
|
+
},
|
|
17603
17886
|
onTickle: (id) => {
|
|
17604
17887
|
if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
|
|
17605
17888
|
},
|
|
@@ -17703,7 +17986,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17703
17986
|
}
|
|
17704
17987
|
return;
|
|
17705
17988
|
}
|
|
17706
|
-
if (NAV_KEYS.has(e.key)) this.setAttribute(`data-keyboard-nav`, ``);
|
|
17989
|
+
if (NAV_KEYS.has(e.key) && !this.hasAttribute(`data-keyboard-nav`)) this.setAttribute(`data-keyboard-nav`, ``);
|
|
17707
17990
|
switch (e.key) {
|
|
17708
17991
|
case `ArrowDown`:
|
|
17709
17992
|
e.preventDefault();
|
|
@@ -17782,11 +18065,32 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17782
18065
|
};
|
|
17783
18066
|
}
|
|
17784
18067
|
#itemData;
|
|
17785
|
-
|
|
17786
|
-
#
|
|
17787
|
-
/**
|
|
17788
|
-
#
|
|
18068
|
+
/** The `index` each object item was formatted with — reuse is only safe at the same index. */
|
|
18069
|
+
#itemIndex;
|
|
18070
|
+
/** Measured column grid (content coordinates). See `#ensureGeometry`. */
|
|
18071
|
+
#geometry;
|
|
18072
|
+
/** Layout may have changed since `#geometry` was measured. */
|
|
18073
|
+
#geometryDirty;
|
|
17789
18074
|
#geometryScheduled;
|
|
18075
|
+
/**
|
|
18076
|
+
* Watches child / visibility / key changes so the item caches below can be
|
|
18077
|
+
* validated synchronously (via `takeRecords()`) on every read — they are
|
|
18078
|
+
* never stale, even within the same task as the mutation.
|
|
18079
|
+
*/
|
|
18080
|
+
#cacheObserver;
|
|
18081
|
+
#allItemsCache;
|
|
18082
|
+
#visibleCache;
|
|
18083
|
+
#visibleCachePredicate;
|
|
18084
|
+
#tickledItemsCache;
|
|
18085
|
+
#renderedKeysCache;
|
|
18086
|
+
#keyIndexCache;
|
|
18087
|
+
/** Item states as last written to the DOM, so `_syncItemStates` can apply just the diff. */
|
|
18088
|
+
#appliedTickled;
|
|
18089
|
+
#appliedSelected;
|
|
18090
|
+
#appliedChecked;
|
|
18091
|
+
#itemStatesFullSync;
|
|
18092
|
+
#hasCheckboxes;
|
|
18093
|
+
#draggableApplied;
|
|
17790
18094
|
#sortRaf;
|
|
17791
18095
|
#viewportScrollRaf;
|
|
17792
18096
|
/** Key sets as of the last viewport-change dispatch, for dedupe. */
|
|
@@ -17840,6 +18144,8 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17840
18144
|
}
|
|
17841
18145
|
_syncNativeDraggable() {
|
|
17842
18146
|
if (!this.isConnected) return;
|
|
18147
|
+
if (!this.itemsDraggable && !this.#draggableApplied) return;
|
|
18148
|
+
this.#draggableApplied = this.itemsDraggable;
|
|
17843
18149
|
for (const el of this.querySelectorAll(`li`)) if (el instanceof HTMLElement) el.draggable = this.itemsDraggable && this._isCheckable(el);
|
|
17844
18150
|
}
|
|
17845
18151
|
#startPostDragSuppressTimer() {
|
|
@@ -17921,7 +18227,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17921
18227
|
this.#resizeObserver = new ResizeObserver(() => this.#scheduleGeometry());
|
|
17922
18228
|
this.#resizeObserver.observe(this);
|
|
17923
18229
|
this.#mutationObserver = new MutationObserver((records) => {
|
|
17924
|
-
this.#
|
|
18230
|
+
this.#processAddedChildren(records);
|
|
17925
18231
|
this.#animateChildMutations(records);
|
|
17926
18232
|
this.#scheduleGeometry();
|
|
17927
18233
|
this.#scheduleGroups();
|
|
@@ -17932,6 +18238,16 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17932
18238
|
attributeFilter: [`hidden`]
|
|
17933
18239
|
});
|
|
17934
18240
|
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) this.#animatedItems.add(el);
|
|
18241
|
+
this.#cacheObserver = new MutationObserver((records) => {
|
|
18242
|
+
if (records.some((r) => affectsItemCaches(this, r))) this.#dropItemCaches();
|
|
18243
|
+
});
|
|
18244
|
+
this.#cacheObserver.observe(this, {
|
|
18245
|
+
childList: true,
|
|
18246
|
+
subtree: true,
|
|
18247
|
+
attributes: true,
|
|
18248
|
+
attributeFilter: ITEM_CACHE_ATTRS
|
|
18249
|
+
});
|
|
18250
|
+
this.#dropItemCaches();
|
|
17935
18251
|
this.#searchRegistry ??= createCommandRegistry();
|
|
17936
18252
|
this.#syncSearchCommands();
|
|
17937
18253
|
this.#scheduleGroups();
|
|
@@ -17951,7 +18267,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17951
18267
|
fresh.push(node);
|
|
17952
18268
|
}
|
|
17953
18269
|
if (fresh.length === 0) return;
|
|
17954
|
-
const total = this
|
|
18270
|
+
const total = this.#allItems().length;
|
|
17955
18271
|
if (fresh.length >= total) return;
|
|
17956
18272
|
for (const el of fresh) animateListItemIn(el);
|
|
17957
18273
|
window.setTimeout(() => this.#scheduleGeometry(), 230);
|
|
@@ -17978,6 +18294,9 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17978
18294
|
this.#mutationObserver = void 0;
|
|
17979
18295
|
this.#groupObserver?.disconnect();
|
|
17980
18296
|
this.#groupObserver = void 0;
|
|
18297
|
+
this.#cacheObserver?.disconnect();
|
|
18298
|
+
this.#cacheObserver = void 0;
|
|
18299
|
+
this.#dropItemCaches();
|
|
17981
18300
|
this.#unregisterSearchCommands?.();
|
|
17982
18301
|
this.#unregisterSearchCommands = void 0;
|
|
17983
18302
|
this.#searchRegistry = void 0;
|
|
@@ -17990,6 +18309,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
17990
18309
|
this.#processChildren();
|
|
17991
18310
|
if (this.filterPredicate) this.#applyFilterPredicate();
|
|
17992
18311
|
this.#recomputeGeometry();
|
|
18312
|
+
this.#scheduleViewportChangeEmit();
|
|
17993
18313
|
this._syncNativeDraggable();
|
|
17994
18314
|
}
|
|
17995
18315
|
willUpdate(changed) {
|
|
@@ -18018,7 +18338,8 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18018
18338
|
});
|
|
18019
18339
|
return;
|
|
18020
18340
|
}
|
|
18021
|
-
|
|
18341
|
+
const shapeChanged = changed.has(`displayProperty`) || changed.has(`formatter`) || changed.has(`iconProperty`) || changed.has(`iconColorProperty`) || changed.has(`textColorProperty`) || changed.has(`badgeProperty`) || changed.has(`keyProperty`);
|
|
18342
|
+
if ((changed.has(`items`) || shapeChanged) && this.provider === void 0 && this.items.length > 0) this.#rebuildFromObjects(this.items, !shapeChanged);
|
|
18022
18343
|
this.#syncFromProvider();
|
|
18023
18344
|
if (changed.has(`sort`) || changed.has(`sortBy`) || changed.has(`sortComparator`)) this.#applySort();
|
|
18024
18345
|
if (changed.has(`filterPredicate`)) {
|
|
@@ -18028,6 +18349,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18028
18349
|
}
|
|
18029
18350
|
if (changed.has(`interactionMode`)) {
|
|
18030
18351
|
this.selection.clearAnchor();
|
|
18352
|
+
this.#itemStatesFullSync = true;
|
|
18031
18353
|
this._syncItemStates();
|
|
18032
18354
|
this._syncNativeDraggable();
|
|
18033
18355
|
} else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
|
|
@@ -18051,6 +18373,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18051
18373
|
*/
|
|
18052
18374
|
refreshLayout() {
|
|
18053
18375
|
this.#recomputeGeometry();
|
|
18376
|
+
this.#scheduleViewportChangeEmit();
|
|
18054
18377
|
}
|
|
18055
18378
|
addItem(item, options) {
|
|
18056
18379
|
let el;
|
|
@@ -18076,7 +18399,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18076
18399
|
item.remove();
|
|
18077
18400
|
this.#scheduleGroups();
|
|
18078
18401
|
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
18079
|
-
this.#
|
|
18402
|
+
this.#scheduleGeometry();
|
|
18080
18403
|
});
|
|
18081
18404
|
}
|
|
18082
18405
|
/** {@link removeItem} for several items at once. */
|
|
@@ -18125,13 +18448,12 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18125
18448
|
visibleKeys() {
|
|
18126
18449
|
const container = this._listEl;
|
|
18127
18450
|
if (!container) return [];
|
|
18128
|
-
const
|
|
18451
|
+
const start = container.scrollLeft;
|
|
18452
|
+
const end = start + container.getBoundingClientRect().width;
|
|
18129
18453
|
const out = [];
|
|
18130
|
-
for (const el of this._getVisibleItems()) {
|
|
18454
|
+
for (const el of itemsInSpanByOrder(this._getVisibleItems(), contentOrigin(container), start, end)) {
|
|
18131
18455
|
const key = keyForElement(el);
|
|
18132
|
-
if (key
|
|
18133
|
-
const r = el.getBoundingClientRect();
|
|
18134
|
-
if (r.right > box.left && r.left < box.right) out.push(key);
|
|
18456
|
+
if (key !== void 0) out.push(key);
|
|
18135
18457
|
}
|
|
18136
18458
|
return out;
|
|
18137
18459
|
}
|
|
@@ -18190,7 +18512,15 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18190
18512
|
});
|
|
18191
18513
|
}
|
|
18192
18514
|
#itemForKey(key) {
|
|
18193
|
-
|
|
18515
|
+
if (!this.#keyIndexCache) {
|
|
18516
|
+
const index = /* @__PURE__ */ new Map();
|
|
18517
|
+
for (const el of this.#allItems()) {
|
|
18518
|
+
const k = keyForElement(el);
|
|
18519
|
+
if (k !== void 0 && !index.has(k)) index.set(k, el);
|
|
18520
|
+
}
|
|
18521
|
+
this.#keyIndexCache = index;
|
|
18522
|
+
}
|
|
18523
|
+
return this.#keyIndexCache.get(key);
|
|
18194
18524
|
}
|
|
18195
18525
|
*selectedFilter(kind) {
|
|
18196
18526
|
const wanted = (el) => {
|
|
@@ -18205,11 +18535,12 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18205
18535
|
if (wanted(el)) yield el;
|
|
18206
18536
|
}
|
|
18207
18537
|
}
|
|
18208
|
-
#rebuildFromObjects(items) {
|
|
18538
|
+
#rebuildFromObjects(items, reuseRows = false) {
|
|
18209
18539
|
const keyOf = (el) => el.getAttribute(`data-key`) ?? el.getAttribute(`data-search-label`) ?? null;
|
|
18210
18540
|
const previousKeys = new Set([...this.selection.selected].map(keyOf).filter((k) => k !== null));
|
|
18211
18541
|
const anchorKey = this.selection.anchor ? keyOf(this.selection.anchor) : null;
|
|
18212
|
-
|
|
18542
|
+
if (reuseRows) this.#reconcileObjectRows(items);
|
|
18543
|
+
else this.replaceChildren(...items.map((item, index) => this.#createItemEl(item, index)));
|
|
18213
18544
|
if (previousKeys.size > 0 || anchorKey !== null) {
|
|
18214
18545
|
const next = [];
|
|
18215
18546
|
let anchor;
|
|
@@ -18226,9 +18557,38 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18226
18557
|
this.#scheduleGeometry();
|
|
18227
18558
|
this.#scheduleGroups();
|
|
18228
18559
|
}
|
|
18560
|
+
/**
|
|
18561
|
+
* Update the rows to match `items`, keeping the `<li>` of every object that
|
|
18562
|
+
* is still present (by identity) instead of rebuilding all of them — so
|
|
18563
|
+
* `items = [...items, next]` creates one row, not thousands. A row built by
|
|
18564
|
+
* a `formatter` is only kept at the same index, since the formatter receives
|
|
18565
|
+
* the index.
|
|
18566
|
+
*/
|
|
18567
|
+
#reconcileObjectRows(items) {
|
|
18568
|
+
const reusable = /* @__PURE__ */ new Map();
|
|
18569
|
+
for (const li of this.#allItems()) if (this.#itemData.has(li) && !reusable.has(this.#itemData.get(li))) reusable.set(this.#itemData.get(li), li);
|
|
18570
|
+
const next = items.map((item, index) => {
|
|
18571
|
+
const li = reusable.get(item);
|
|
18572
|
+
if (li && (!this.formatter || this.#itemIndex.get(li) === index)) {
|
|
18573
|
+
reusable.delete(item);
|
|
18574
|
+
return li;
|
|
18575
|
+
}
|
|
18576
|
+
return this.#createItemEl(item, index);
|
|
18577
|
+
});
|
|
18578
|
+
const keep = new Set(next);
|
|
18579
|
+
for (const li of this.#allItems()) if (!keep.has(li)) li.remove();
|
|
18580
|
+
if (this.#groupingActive()) {
|
|
18581
|
+
for (const li of next) this.#seq.set(li, this.#seqNext++);
|
|
18582
|
+
this.append(...next.filter((li) => li.parentElement !== this));
|
|
18583
|
+
this.#scheduleGroups();
|
|
18584
|
+
return;
|
|
18585
|
+
}
|
|
18586
|
+
syncChildOrder(this, next, (el) => el.localName === `li`);
|
|
18587
|
+
}
|
|
18229
18588
|
#createItemEl(item, index) {
|
|
18230
18589
|
const li = document.createElement(`li`);
|
|
18231
18590
|
this.#itemData.set(li, item);
|
|
18591
|
+
this.#itemIndex.set(li, index);
|
|
18232
18592
|
const content = this.formatter ? this.formatter(item, index) : this.displayProperty ? this.#propertyValue(item, this.displayProperty) : item;
|
|
18233
18593
|
const textColor = this.#colorValue(item, this.textColorProperty);
|
|
18234
18594
|
const iconColor = this.#colorValue(item, this.iconColorProperty);
|
|
@@ -18299,42 +18659,40 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18299
18659
|
this.#scheduleGroups();
|
|
18300
18660
|
return;
|
|
18301
18661
|
}
|
|
18302
|
-
|
|
18662
|
+
syncChildOrder(this, this.#sortRows(rows), (el) => el.localName === `li`);
|
|
18303
18663
|
this.#scheduleGeometry();
|
|
18304
18664
|
this.#scheduleGroups();
|
|
18305
18665
|
}
|
|
18306
18666
|
/** Stable sort of `rows` by the current `sort` / `sortBy` / `sortComparator`. */
|
|
18307
18667
|
#sortRows(rows) {
|
|
18308
18668
|
const dir = this.sort === `descending` ? -1 : 1;
|
|
18309
|
-
const
|
|
18669
|
+
const metas = rows.map((el) => ({
|
|
18310
18670
|
element: el,
|
|
18311
18671
|
data: this.getItemData(el),
|
|
18312
18672
|
label: el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``
|
|
18313
|
-
});
|
|
18673
|
+
}));
|
|
18674
|
+
const collator = new Intl.Collator(void 0, { numeric: true });
|
|
18314
18675
|
let cmp;
|
|
18315
18676
|
if (this.sortComparator) {
|
|
18316
18677
|
const user = this.sortComparator;
|
|
18317
|
-
cmp = (a, b) => user(
|
|
18678
|
+
cmp = (a, b) => user(metas[a], metas[b]);
|
|
18318
18679
|
} else if (this.sortBy) {
|
|
18319
18680
|
const prop = this.sortBy;
|
|
18320
|
-
const
|
|
18321
|
-
const v = this.#propertyValue(
|
|
18681
|
+
const keys = metas.map((m) => {
|
|
18682
|
+
const v = this.#propertyValue(m.data, prop);
|
|
18322
18683
|
if (typeof v === `number`) return v;
|
|
18323
18684
|
return v === void 0 || v === null ? `` : String(v);
|
|
18324
|
-
};
|
|
18685
|
+
});
|
|
18325
18686
|
cmp = (a, b) => {
|
|
18326
|
-
const ka =
|
|
18327
|
-
const kb =
|
|
18687
|
+
const ka = keys[a];
|
|
18688
|
+
const kb = keys[b];
|
|
18328
18689
|
if (typeof ka === `number` && typeof kb === `number`) return ka - kb;
|
|
18329
|
-
return String(ka)
|
|
18690
|
+
return collator.compare(String(ka), String(kb));
|
|
18330
18691
|
};
|
|
18331
|
-
} else cmp = (a, b) =>
|
|
18332
|
-
const
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
}));
|
|
18336
|
-
indexed.sort((a, b) => cmp(a.el, b.el) * dir || a.i - b.i);
|
|
18337
|
-
return indexed.map(({ el }) => el);
|
|
18692
|
+
} else cmp = (a, b) => collator.compare(metas[a].label, metas[b].label);
|
|
18693
|
+
const order = rows.map((_, i) => i);
|
|
18694
|
+
order.sort((a, b) => cmp(a, b) * dir || a - b);
|
|
18695
|
+
return order.map((i) => rows[i]);
|
|
18338
18696
|
}
|
|
18339
18697
|
#groupingActive() {
|
|
18340
18698
|
return this.groupBy !== null && this.groupBy !== void 0 && this.groupBy !== ``;
|
|
@@ -18378,7 +18736,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18378
18736
|
#syncGroups() {
|
|
18379
18737
|
const active = this.#groupingActive();
|
|
18380
18738
|
if (!active && !this.#wasGrouped) return;
|
|
18381
|
-
const items =
|
|
18739
|
+
const items = this.#allItems();
|
|
18382
18740
|
for (const el of items) if (!this.#seq.has(el)) this.#seq.set(el, this.#seqNext++);
|
|
18383
18741
|
let ordered = items.toSorted((a, b) => this.#seq.get(a) - this.#seq.get(b));
|
|
18384
18742
|
if (this.sort !== `none` && ordered.length > 1) ordered = this.#sortRows(ordered);
|
|
@@ -18423,8 +18781,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18423
18781
|
this.#groupIndex = index;
|
|
18424
18782
|
this.#groupList = info;
|
|
18425
18783
|
this.#wasGrouped = active;
|
|
18426
|
-
|
|
18427
|
-
if (current.length !== nodes.length || current.some((c, i) => c !== nodes[i])) this.append(...nodes);
|
|
18784
|
+
syncChildOrder(this, nodes, (c) => c.localName === `li` || c.hasAttribute(GROUP_HEADER_ATTR));
|
|
18428
18785
|
if (this.tickled.tickledId?.hasAttribute(GROUP_COLLAPSED_ATTR)) this.tickled.focus(void 0);
|
|
18429
18786
|
this.#syncGroupObserver(active);
|
|
18430
18787
|
this.#scheduleGeometry();
|
|
@@ -18475,59 +18832,65 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18475
18832
|
if (target instanceof HTMLElement && target.parentElement === this && target.hasAttribute(GROUP_HEADER_ATTR)) return target;
|
|
18476
18833
|
}
|
|
18477
18834
|
}
|
|
18835
|
+
/**
|
|
18836
|
+
* Mark the layout dirty and, next frame, check for a viewport change.
|
|
18837
|
+
* Geometry moves (resize, filter, sort, DOM changes) can change the visible
|
|
18838
|
+
* set without a scroll, so every layout-changing path comes through here.
|
|
18839
|
+
* The grid itself is re-measured lazily, by whoever needs it next.
|
|
18840
|
+
*/
|
|
18478
18841
|
#scheduleGeometry() {
|
|
18842
|
+
this.#geometryDirty = true;
|
|
18479
18843
|
if (this.#geometryScheduled) return;
|
|
18480
18844
|
this.#geometryScheduled = true;
|
|
18481
18845
|
requestAnimationFrame(() => {
|
|
18482
18846
|
this.#geometryScheduled = false;
|
|
18483
|
-
this.#
|
|
18847
|
+
this.#emitViewportChangeIfChanged();
|
|
18484
18848
|
});
|
|
18485
18849
|
}
|
|
18850
|
+
/** Re-measure the column grid now (one layout read per visible item). */
|
|
18486
18851
|
#recomputeGeometry() {
|
|
18487
|
-
this.#
|
|
18488
|
-
this.#
|
|
18489
|
-
|
|
18490
|
-
|
|
18491
|
-
|
|
18492
|
-
|
|
18493
|
-
|
|
18494
|
-
|
|
18495
|
-
|
|
18496
|
-
|
|
18497
|
-
|
|
18498
|
-
|
|
18499
|
-
|
|
18500
|
-
items: []
|
|
18501
|
-
});
|
|
18502
|
-
ci = columns.length - 1;
|
|
18503
|
-
}
|
|
18504
|
-
columns[ci].items.push(el);
|
|
18852
|
+
this.#geometryDirty = false;
|
|
18853
|
+
this.#geometry = this.isConnected && this._listEl ? measureGeometry(this._getVisibleItems(), contentOrigin(this._listEl)) : EMPTY_GEOMETRY;
|
|
18854
|
+
}
|
|
18855
|
+
/**
|
|
18856
|
+
* The column grid, re-measured only if it's known dirty or a few probe items
|
|
18857
|
+
* have moved (catches layout changes nothing told us about, e.g. a CSS
|
|
18858
|
+
* variable change). Cheap enough to call per keypress / scroll frame.
|
|
18859
|
+
*/
|
|
18860
|
+
#ensureGeometry() {
|
|
18861
|
+
const visible = this._getVisibleItems();
|
|
18862
|
+
if (this.#geometryDirty || !this._listEl) {
|
|
18863
|
+
this.#recomputeGeometry();
|
|
18864
|
+
return this.#geometry;
|
|
18505
18865
|
}
|
|
18506
|
-
|
|
18507
|
-
|
|
18508
|
-
|
|
18509
|
-
|
|
18510
|
-
|
|
18511
|
-
|
|
18512
|
-
|
|
18513
|
-
this.#
|
|
18866
|
+
const cursor = this.tickled.tickledId;
|
|
18867
|
+
const probes = [
|
|
18868
|
+
visible[0],
|
|
18869
|
+
cursor && this.#geometry.positions.has(cursor) ? cursor : void 0,
|
|
18870
|
+
visible.at(-1)
|
|
18871
|
+
];
|
|
18872
|
+
if (!geometryMatches(this.#geometry, probes, contentOrigin(this._listEl))) this.#recomputeGeometry();
|
|
18873
|
+
return this.#geometry;
|
|
18514
18874
|
}
|
|
18515
18875
|
#positionOf(el) {
|
|
18516
|
-
let pos = this.#positions.get(el);
|
|
18876
|
+
let pos = this.#geometry.positions.get(el);
|
|
18517
18877
|
if (pos === void 0) {
|
|
18518
18878
|
this.#recomputeGeometry();
|
|
18519
|
-
pos = this.#positions.get(el);
|
|
18879
|
+
pos = this.#geometry.positions.get(el);
|
|
18520
18880
|
}
|
|
18521
18881
|
return pos;
|
|
18522
18882
|
}
|
|
18883
|
+
#column(col) {
|
|
18884
|
+
return this.#geometry.columns[col]?.items;
|
|
18885
|
+
}
|
|
18523
18886
|
#itemAt(col, row) {
|
|
18524
|
-
return this.#
|
|
18887
|
+
return this.#column(col)?.[row];
|
|
18525
18888
|
}
|
|
18526
18889
|
#gridSize() {
|
|
18527
|
-
|
|
18890
|
+
const { columns } = this.#ensureGeometry();
|
|
18528
18891
|
return {
|
|
18529
|
-
cols:
|
|
18530
|
-
rows: Math.max(0, ...
|
|
18892
|
+
cols: columns.length,
|
|
18893
|
+
rows: Math.max(0, ...columns.map((c) => c.items.length))
|
|
18531
18894
|
};
|
|
18532
18895
|
}
|
|
18533
18896
|
#navigateTo(target) {
|
|
@@ -18545,7 +18908,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18545
18908
|
let row = deltaRow !== 0 ? fromRow + deltaRow : fromRow;
|
|
18546
18909
|
let wrappedColumns = false;
|
|
18547
18910
|
for (let iterations = 0; iterations < maxIterations; iterations++) {
|
|
18548
|
-
const colLength = this.#
|
|
18911
|
+
const colLength = this.#column(col)?.length ?? 0;
|
|
18549
18912
|
if (row < 0 || row >= colLength) {
|
|
18550
18913
|
if (deltaCol !== 0 || wrappedColumns) {
|
|
18551
18914
|
if (this.itemWrap === `wrap`) {
|
|
@@ -18567,15 +18930,15 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18567
18930
|
} else if (deltaRow < 0) {
|
|
18568
18931
|
if (col - 1 >= 0) {
|
|
18569
18932
|
col = col - 1;
|
|
18570
|
-
row = (this.#
|
|
18933
|
+
row = (this.#column(col)?.length ?? 1) - 1;
|
|
18571
18934
|
wrappedColumns = true;
|
|
18572
18935
|
} else if (this.itemWrap === `wrap`) {
|
|
18573
18936
|
col = cols - 1;
|
|
18574
|
-
row = (this.#
|
|
18937
|
+
row = (this.#column(col)?.length ?? 1) - 1;
|
|
18575
18938
|
wrappedColumns = true;
|
|
18576
18939
|
} else return;
|
|
18577
18940
|
} else if (deltaCol !== 0) {
|
|
18578
|
-
const targetColLength = this.#
|
|
18941
|
+
const targetColLength = this.#column(col)?.length ?? 0;
|
|
18579
18942
|
if (row >= targetColLength && targetColLength > 0) row = targetColLength - 1;
|
|
18580
18943
|
}
|
|
18581
18944
|
if (col === fromCol && row === fromRow + deltaRow) return;
|
|
@@ -18594,7 +18957,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18594
18957
|
for (let i = visibleItems.length - 1; i >= 0; i--) if (this._isCheckable(visibleItems[i])) return visibleItems[i];
|
|
18595
18958
|
}
|
|
18596
18959
|
#navigateDown() {
|
|
18597
|
-
this.#
|
|
18960
|
+
this.#ensureGeometry();
|
|
18598
18961
|
const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
|
|
18599
18962
|
if (!cur) {
|
|
18600
18963
|
this.#navigateTo(this.#findFirstCheckable());
|
|
@@ -18604,7 +18967,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18604
18967
|
if (next) this.#navigateTo(next);
|
|
18605
18968
|
}
|
|
18606
18969
|
#navigateUp() {
|
|
18607
|
-
this.#
|
|
18970
|
+
this.#ensureGeometry();
|
|
18608
18971
|
const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
|
|
18609
18972
|
if (!cur) {
|
|
18610
18973
|
this.#navigateTo(this.#findLastCheckable());
|
|
@@ -18621,15 +18984,15 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18621
18984
|
*/
|
|
18622
18985
|
#horizontalTarget(from, dir) {
|
|
18623
18986
|
if (this.#groupList.length === 0) return this.#findNextCheckable(from.col, from.row, dir, 0);
|
|
18624
|
-
const column = this.#
|
|
18987
|
+
const column = this.#column(from.col + dir);
|
|
18625
18988
|
const current = this.tickled.tickledId;
|
|
18626
|
-
const top = current ? this.#tops.get(current) : void 0;
|
|
18989
|
+
const top = current ? this.#geometry.tops.get(current) : void 0;
|
|
18627
18990
|
if (!column || top === void 0) return void 0;
|
|
18628
18991
|
let best;
|
|
18629
18992
|
let bestDelta = Number.POSITIVE_INFINITY;
|
|
18630
18993
|
for (const el of column) {
|
|
18631
18994
|
if (!this._isCheckable(el)) continue;
|
|
18632
|
-
const delta = Math.abs((this.#tops.get(el) ?? Number.POSITIVE_INFINITY) - top);
|
|
18995
|
+
const delta = Math.abs((this.#geometry.tops.get(el) ?? Number.POSITIVE_INFINITY) - top);
|
|
18633
18996
|
if (delta < bestDelta) {
|
|
18634
18997
|
best = el;
|
|
18635
18998
|
bestDelta = delta;
|
|
@@ -18638,7 +19001,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18638
19001
|
return best;
|
|
18639
19002
|
}
|
|
18640
19003
|
#navigateRight() {
|
|
18641
|
-
this.#
|
|
19004
|
+
this.#ensureGeometry();
|
|
18642
19005
|
const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
|
|
18643
19006
|
if (!cur) {
|
|
18644
19007
|
this.#navigateTo(this.#findFirstCheckable());
|
|
@@ -18649,7 +19012,7 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18649
19012
|
else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findFirstCheckable());
|
|
18650
19013
|
}
|
|
18651
19014
|
#navigateLeft() {
|
|
18652
|
-
this.#
|
|
19015
|
+
this.#ensureGeometry();
|
|
18653
19016
|
const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
|
|
18654
19017
|
if (!cur) {
|
|
18655
19018
|
this.#navigateTo(this.#findFirstCheckable());
|
|
@@ -18659,13 +19022,49 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18659
19022
|
if (next) this.#navigateTo(next);
|
|
18660
19023
|
else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findLastCheckable());
|
|
18661
19024
|
}
|
|
19025
|
+
/**
|
|
19026
|
+
* Visible items in DOM order. Cached — every per-hover / per-frame path
|
|
19027
|
+
* reads it — and validated on each call, so it is never stale.
|
|
19028
|
+
*/
|
|
18662
19029
|
_getVisibleItems() {
|
|
18663
|
-
|
|
19030
|
+
this.#validateItemCaches();
|
|
19031
|
+
if (this.#visibleCachePredicate !== this.filterPredicate) {
|
|
19032
|
+
this.#dropVisibleCaches();
|
|
19033
|
+
this.#visibleCachePredicate = this.filterPredicate;
|
|
19034
|
+
}
|
|
19035
|
+
this.#visibleCache ??= this.#allItems().filter((el) => {
|
|
18664
19036
|
if (el.hidden) return false;
|
|
18665
19037
|
if (el.hasAttribute(GROUP_COLLAPSED_ATTR)) return false;
|
|
18666
19038
|
if (this.filterPredicate && !this.filterPredicate(el)) return false;
|
|
18667
19039
|
return true;
|
|
18668
19040
|
});
|
|
19041
|
+
return this.#visibleCache;
|
|
19042
|
+
}
|
|
19043
|
+
/** Every item (`<li>` child), in DOM order. Cached like `_getVisibleItems`. */
|
|
19044
|
+
#allItems() {
|
|
19045
|
+
this.#validateItemCaches();
|
|
19046
|
+
this.#allItemsCache ??= [...this.querySelectorAll(ITEM_SELECTOR$3)];
|
|
19047
|
+
return this.#allItemsCache;
|
|
19048
|
+
}
|
|
19049
|
+
/** Drop the item caches if anything relevant changed since they were built. */
|
|
19050
|
+
#validateItemCaches() {
|
|
19051
|
+
if (!this.#cacheObserver) {
|
|
19052
|
+
this.#dropItemCaches();
|
|
19053
|
+
return;
|
|
19054
|
+
}
|
|
19055
|
+
if (this.#cacheObserver.takeRecords().some((r) => affectsItemCaches(this, r))) this.#dropItemCaches();
|
|
19056
|
+
}
|
|
19057
|
+
#dropItemCaches() {
|
|
19058
|
+
this.#allItemsCache = void 0;
|
|
19059
|
+
this.#keyIndexCache = void 0;
|
|
19060
|
+
this.#itemStatesFullSync = true;
|
|
19061
|
+
this.#dropVisibleCaches();
|
|
19062
|
+
}
|
|
19063
|
+
#dropVisibleCaches() {
|
|
19064
|
+
this.#visibleCache = void 0;
|
|
19065
|
+
this.#tickledItemsCache = void 0;
|
|
19066
|
+
this.#renderedKeysCache = void 0;
|
|
19067
|
+
this.#geometryDirty = true;
|
|
18669
19068
|
}
|
|
18670
19069
|
/**
|
|
18671
19070
|
* Set the column width to fit the widest visible item.
|
|
@@ -18738,21 +19137,48 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18738
19137
|
}
|
|
18739
19138
|
/** Scan light-DOM children for unprocessed `data-icon` items. */
|
|
18740
19139
|
#processChildren() {
|
|
18741
|
-
for (const el of this
|
|
19140
|
+
for (const el of this.#allItems()) this.#ensureIcon(el);
|
|
18742
19141
|
}
|
|
19142
|
+
/** `#processChildren` for just the items a batch of mutations added. */
|
|
19143
|
+
#processAddedChildren(records) {
|
|
19144
|
+
for (const record of records) for (const node of record.addedNodes) if (node instanceof HTMLLIElement && node.parentElement === this) this.#ensureIcon(node);
|
|
19145
|
+
}
|
|
19146
|
+
/**
|
|
19147
|
+
* Reflect cursor / selection / checked state onto the items. After the item
|
|
19148
|
+
* set changes (or the interaction mode does) every item is visited; otherwise
|
|
19149
|
+
* only the items whose state changed are touched, so hovering is O(1).
|
|
19150
|
+
*/
|
|
18743
19151
|
_syncItemStates() {
|
|
18744
|
-
|
|
18745
|
-
|
|
18746
|
-
|
|
18747
|
-
|
|
18748
|
-
|
|
19152
|
+
const selected = this.selection.selected;
|
|
19153
|
+
const checked = this.interactionMode === `checked` ? new Set([...selected].filter((el) => !el.hasAttribute(`not-checked`))) : /* @__PURE__ */ new Set();
|
|
19154
|
+
const tickled = this.tickled.tickledId;
|
|
19155
|
+
const selectionChanged = this.#itemStatesFullSync || !sameMembers(this.#appliedSelected, selected) || !sameMembers(this.#appliedChecked, checked);
|
|
19156
|
+
if (this.#itemStatesFullSync) {
|
|
19157
|
+
for (const el of this.#allItems()) {
|
|
19158
|
+
el.toggleAttribute(`data-tickled`, el === tickled);
|
|
19159
|
+
el.toggleAttribute(`data-selected`, selected.has(el));
|
|
19160
|
+
el.toggleAttribute(`data-checked`, checked.has(el));
|
|
19161
|
+
}
|
|
19162
|
+
this.#itemStatesFullSync = false;
|
|
19163
|
+
} else {
|
|
19164
|
+
if (tickled !== this.#appliedTickled) {
|
|
19165
|
+
this.#appliedTickled?.removeAttribute(`data-tickled`);
|
|
19166
|
+
tickled?.setAttribute(`data-tickled`, ``);
|
|
19167
|
+
}
|
|
19168
|
+
applySetDiff(this.#appliedSelected, selected, `data-selected`);
|
|
19169
|
+
applySetDiff(this.#appliedChecked, checked, `data-checked`);
|
|
18749
19170
|
}
|
|
18750
|
-
this
|
|
18751
|
-
this
|
|
19171
|
+
this.#appliedTickled = tickled;
|
|
19172
|
+
this.#appliedSelected = new Set(selected);
|
|
19173
|
+
this.#appliedChecked = checked;
|
|
19174
|
+
if (this.dataset.inputMode !== this.tickled.inputMode) this.dataset.inputMode = this.tickled.inputMode;
|
|
19175
|
+
if (selectionChanged) this._syncCheckboxes();
|
|
18752
19176
|
}
|
|
18753
19177
|
_syncCheckboxes() {
|
|
18754
19178
|
const inCheckedMode = this.interactionMode === `checked`;
|
|
18755
19179
|
const wantsCustom = typeof this.checkboxRenderer === `function`;
|
|
19180
|
+
if (!inCheckedMode && !this.#hasCheckboxes) return;
|
|
19181
|
+
this.#hasCheckboxes = inCheckedMode;
|
|
18756
19182
|
for (const el of this.querySelectorAll(`li`)) {
|
|
18757
19183
|
const existing = el.querySelector(`.ixfx-list-checkbox`);
|
|
18758
19184
|
if (!inCheckedMode) {
|
|
@@ -18865,12 +19291,12 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18865
19291
|
* `ixfx-vertical-list.renderedKeys()` in its non-virtualized mode.
|
|
18866
19292
|
*/
|
|
18867
19293
|
#renderedKeys() {
|
|
18868
|
-
const
|
|
18869
|
-
|
|
19294
|
+
const visible = this._getVisibleItems();
|
|
19295
|
+
this.#renderedKeysCache ??= visible.flatMap((el) => {
|
|
18870
19296
|
const key = keyForElement(el);
|
|
18871
|
-
|
|
18872
|
-
}
|
|
18873
|
-
return
|
|
19297
|
+
return key === void 0 ? [] : [key];
|
|
19298
|
+
});
|
|
19299
|
+
return this.#renderedKeysCache;
|
|
18874
19300
|
}
|
|
18875
19301
|
/**
|
|
18876
19302
|
* Dispatch `detail-list-viewport-change` with the current key sets — unless
|
|
@@ -18970,6 +19396,12 @@ let DetailListElement = class DetailListElement extends LitElement {
|
|
|
18970
19396
|
display: flex;
|
|
18971
19397
|
align-items: center;
|
|
18972
19398
|
flex: none;
|
|
19399
|
+
/* Paint-isolates each row, so restyling one row (hover, selection)
|
|
19400
|
+
doesn't repaint the whole list: ~3.5x less render work per hover at
|
|
19401
|
+
10k rows, for ~0.6ms more compositor commit per scroll frame. Paint
|
|
19402
|
+
only: layout/style containment add nothing here, and no size
|
|
19403
|
+
containment — fitColumnWidth() measures rows at max-content. */
|
|
19404
|
+
contain: paint;
|
|
18973
19405
|
width: var(--detail-list-column-width, 220px);
|
|
18974
19406
|
height: var(--detail-list-item-height, 28px);
|
|
18975
19407
|
padding: var(--_detail-list-item-padding);
|
|
@@ -20850,6 +21282,11 @@ let SplitLayoutElement = class SplitLayoutElement extends LitElement {
|
|
|
20850
21282
|
height: 100%;
|
|
20851
21283
|
}
|
|
20852
21284
|
|
|
21285
|
+
/* ::part() doesn't match inside our own shadow root, so target directly. */
|
|
21286
|
+
[part="panel"] {
|
|
21287
|
+
contain: strict;
|
|
21288
|
+
}
|
|
21289
|
+
|
|
20853
21290
|
.gutter {
|
|
20854
21291
|
z-index: 2;
|
|
20855
21292
|
background: var(--split-gutter-color);
|
|
@@ -21045,9 +21482,20 @@ let TabListElement = class TabListElement extends LitElement {
|
|
|
21045
21482
|
window.addEventListener(`pointerup`, this.#onPointerUp);
|
|
21046
21483
|
window.addEventListener(`pointercancel`, this.#onPointerCancel);
|
|
21047
21484
|
};
|
|
21485
|
+
this.#pendingMove = null;
|
|
21486
|
+
this.#moveRaf = null;
|
|
21048
21487
|
this.#onPointerMove = (event) => {
|
|
21049
21488
|
const state = this.#dragState;
|
|
21050
21489
|
if (!state || event.pointerId !== state.pointerId) return;
|
|
21490
|
+
this.#pendingMove = event;
|
|
21491
|
+
this.#moveRaf ??= requestAnimationFrame(this.#flushMove);
|
|
21492
|
+
};
|
|
21493
|
+
this.#flushMove = () => {
|
|
21494
|
+
const event = this.#pendingMove;
|
|
21495
|
+
this.#pendingMove = null;
|
|
21496
|
+
this.#moveRaf = null;
|
|
21497
|
+
const state = this.#dragState;
|
|
21498
|
+
if (!state || !event) return;
|
|
21051
21499
|
const dx = event.clientX - state.startX;
|
|
21052
21500
|
const dy = event.clientY - state.startY;
|
|
21053
21501
|
const distance = Math.hypot(dx, dy);
|
|
@@ -21232,7 +21680,17 @@ let TabListElement = class TabListElement extends LitElement {
|
|
|
21232
21680
|
return item;
|
|
21233
21681
|
}
|
|
21234
21682
|
#onPointerDown;
|
|
21683
|
+
#pendingMove;
|
|
21684
|
+
#moveRaf;
|
|
21235
21685
|
#onPointerMove;
|
|
21686
|
+
#cancelPendingMove() {
|
|
21687
|
+
if (this.#moveRaf !== null) {
|
|
21688
|
+
cancelAnimationFrame(this.#moveRaf);
|
|
21689
|
+
this.#moveRaf = null;
|
|
21690
|
+
}
|
|
21691
|
+
this.#pendingMove = null;
|
|
21692
|
+
}
|
|
21693
|
+
#flushMove;
|
|
21236
21694
|
#onPointerUp;
|
|
21237
21695
|
#onPointerCancel;
|
|
21238
21696
|
#beginDrag(event) {
|
|
@@ -21258,6 +21716,7 @@ let TabListElement = class TabListElement extends LitElement {
|
|
|
21258
21716
|
#endDrag(pointerId) {
|
|
21259
21717
|
const state = this.#dragState;
|
|
21260
21718
|
if (!state) return;
|
|
21719
|
+
this.#cancelPendingMove();
|
|
21261
21720
|
state.ghost?.destroy();
|
|
21262
21721
|
state.ghost = null;
|
|
21263
21722
|
this.#dragState = null;
|
|
@@ -21580,6 +22039,22 @@ __decorate([property({
|
|
|
21580
22039
|
TabListElement = __decorate([safeCustomElement(`ixfx-tab-list`)], TabListElement);
|
|
21581
22040
|
//#endregion
|
|
21582
22041
|
//#region src/tabs/tab-list-item.ts
|
|
22042
|
+
const resizeHandlers = /* @__PURE__ */ new WeakMap();
|
|
22043
|
+
const sharedResizeObserver = new ResizeObserver((entries) => {
|
|
22044
|
+
for (const entry of entries) resizeHandlers.get(entry.target)?.(entry.contentRect.width);
|
|
22045
|
+
});
|
|
22046
|
+
const mutationHandlers = /* @__PURE__ */ new Map();
|
|
22047
|
+
const sharedMutationObserver = new MutationObserver((records) => {
|
|
22048
|
+
const touched = /* @__PURE__ */ new Set();
|
|
22049
|
+
for (const record of records) for (let node = record.target; node; node = node.parentNode) {
|
|
22050
|
+
const handler = mutationHandlers.get(node);
|
|
22051
|
+
if (handler) {
|
|
22052
|
+
touched.add(handler);
|
|
22053
|
+
break;
|
|
22054
|
+
}
|
|
22055
|
+
}
|
|
22056
|
+
for (const handler of touched) handler();
|
|
22057
|
+
});
|
|
21583
22058
|
let TabListItemElement = class TabListItemElement extends LitElement {
|
|
21584
22059
|
constructor(..._args) {
|
|
21585
22060
|
super(..._args);
|
|
@@ -21593,7 +22068,11 @@ let TabListItemElement = class TabListItemElement extends LitElement {
|
|
|
21593
22068
|
this._compact = false;
|
|
21594
22069
|
this._hasToolbarContent = false;
|
|
21595
22070
|
this._hasStatusContent = false;
|
|
21596
|
-
this.
|
|
22071
|
+
this._onLabelMutation = () => {
|
|
22072
|
+
this._syncLabelSizer();
|
|
22073
|
+
this._updateToolbarContent();
|
|
22074
|
+
this._updateStatusContent();
|
|
22075
|
+
};
|
|
21597
22076
|
this._onToolbarSlotChange = () => {
|
|
21598
22077
|
this._updateToolbarContent();
|
|
21599
22078
|
};
|
|
@@ -21606,17 +22085,13 @@ let TabListItemElement = class TabListItemElement extends LitElement {
|
|
|
21606
22085
|
}
|
|
21607
22086
|
connectedCallback() {
|
|
21608
22087
|
super.connectedCallback();
|
|
21609
|
-
this
|
|
21610
|
-
this._compact =
|
|
22088
|
+
resizeHandlers.set(this, (width) => {
|
|
22089
|
+
this._compact = width < 64;
|
|
21611
22090
|
});
|
|
21612
|
-
|
|
22091
|
+
sharedResizeObserver.observe(this);
|
|
21613
22092
|
this._wireSlotListeners();
|
|
21614
|
-
this.
|
|
21615
|
-
|
|
21616
|
-
this._updateToolbarContent();
|
|
21617
|
-
this._updateStatusContent();
|
|
21618
|
-
});
|
|
21619
|
-
this._labelObserver.observe(this, {
|
|
22093
|
+
mutationHandlers.set(this, this._onLabelMutation);
|
|
22094
|
+
sharedMutationObserver.observe(this, {
|
|
21620
22095
|
childList: true,
|
|
21621
22096
|
characterData: true,
|
|
21622
22097
|
subtree: true,
|
|
@@ -21626,12 +22101,11 @@ let TabListItemElement = class TabListItemElement extends LitElement {
|
|
|
21626
22101
|
}
|
|
21627
22102
|
disconnectedCallback() {
|
|
21628
22103
|
super.disconnectedCallback();
|
|
21629
|
-
|
|
21630
|
-
this
|
|
22104
|
+
sharedResizeObserver.unobserve(this);
|
|
22105
|
+
resizeHandlers.delete(this);
|
|
21631
22106
|
this._commandCleanup?.();
|
|
21632
22107
|
this._commandCleanup = void 0;
|
|
21633
|
-
|
|
21634
|
-
this._labelObserver = null;
|
|
22108
|
+
mutationHandlers.delete(this);
|
|
21635
22109
|
this._unwireSlotListeners();
|
|
21636
22110
|
}
|
|
21637
22111
|
updated(changed) {
|
|
@@ -24726,8 +25200,8 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
24726
25200
|
});
|
|
24727
25201
|
}
|
|
24728
25202
|
});
|
|
24729
|
-
this.tickled = new TickledController(
|
|
24730
|
-
getItems: () =>
|
|
25203
|
+
this.tickled = ((self) => new TickledController(self, {
|
|
25204
|
+
getItems: () => self.#selectionOrder().map((el) => ({
|
|
24731
25205
|
id: el,
|
|
24732
25206
|
enabled: !el.hasAttribute(`not-checked`)
|
|
24733
25207
|
})),
|
|
@@ -24735,9 +25209,11 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
24735
25209
|
if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
|
|
24736
25210
|
},
|
|
24737
25211
|
onStateChange: () => this._syncItemStates(),
|
|
24738
|
-
itemWrap
|
|
25212
|
+
get itemWrap() {
|
|
25213
|
+
return self.itemWrap;
|
|
25214
|
+
},
|
|
24739
25215
|
hoverDisabled: () => this.#dragSelect.isDragging || this.#nativeDrag.isDragging
|
|
24740
|
-
});
|
|
25216
|
+
}))(this);
|
|
24741
25217
|
this.#nativeDragging = false;
|
|
24742
25218
|
this.#postDragSuppressTimer = 0;
|
|
24743
25219
|
this.#nativeDrag = new ListNativeDragController(this, {
|
|
@@ -24769,6 +25245,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
24769
25245
|
this.toggleAttribute(`data-native-dragging`, false);
|
|
24770
25246
|
this.removeAttribute(`aria-label`);
|
|
24771
25247
|
for (const el of this._getVisibleItems()) el.removeAttribute(`data-drag-origin`);
|
|
25248
|
+
if (this.#virtualActive()) for (const el of this.#allItems) el.removeAttribute(`data-drag-origin`);
|
|
24772
25249
|
this.#startPostDragSuppressTimer();
|
|
24773
25250
|
},
|
|
24774
25251
|
syncLightDomDraggable: () => this._syncNativeDraggable()
|
|
@@ -24797,7 +25274,9 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
24797
25274
|
this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
|
|
24798
25275
|
itemSelector: ITEM_SELECTOR$2,
|
|
24799
25276
|
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
24800
|
-
highlightKey: `vertical-list-search
|
|
25277
|
+
highlightKey: `vertical-list-search`,
|
|
25278
|
+
getAllItems: () => this.#selectionOrder(),
|
|
25279
|
+
observeContainer: false
|
|
24801
25280
|
}) });
|
|
24802
25281
|
this.#onScroll = () => {
|
|
24803
25282
|
if (this.#scrollRaf !== 0) return;
|
|
@@ -24836,10 +25315,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
24836
25315
|
case `ArrowDown`:
|
|
24837
25316
|
e.preventDefault();
|
|
24838
25317
|
this.tickled.handleKeyboardNavigation(`next`);
|
|
24839
|
-
this
|
|
24840
|
-
block: `nearest`,
|
|
24841
|
-
inline: `nearest`
|
|
24842
|
-
});
|
|
25318
|
+
this.#revealCursor();
|
|
24843
25319
|
if (e.shiftKey && this.interactionMode === `vscode`) {
|
|
24844
25320
|
const cur = this.tickled.tickledId;
|
|
24845
25321
|
if (cur) this.selection.rangeTo(cur);
|
|
@@ -24848,15 +25324,30 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
24848
25324
|
case `ArrowUp`:
|
|
24849
25325
|
e.preventDefault();
|
|
24850
25326
|
this.tickled.handleKeyboardNavigation(`previous`);
|
|
24851
|
-
this
|
|
24852
|
-
block: `nearest`,
|
|
24853
|
-
inline: `nearest`
|
|
24854
|
-
});
|
|
25327
|
+
this.#revealCursor();
|
|
24855
25328
|
if (e.shiftKey && this.interactionMode === `vscode`) {
|
|
24856
25329
|
const cur = this.tickled.tickledId;
|
|
24857
25330
|
if (cur) this.selection.rangeTo(cur);
|
|
24858
25331
|
}
|
|
24859
25332
|
break;
|
|
25333
|
+
case `Home`:
|
|
25334
|
+
e.preventDefault();
|
|
25335
|
+
this.tickled.handleKeyboardNavigation(`first`);
|
|
25336
|
+
this.#revealCursor();
|
|
25337
|
+
break;
|
|
25338
|
+
case `End`:
|
|
25339
|
+
e.preventDefault();
|
|
25340
|
+
this.tickled.handleKeyboardNavigation(`last`);
|
|
25341
|
+
this.#revealCursor();
|
|
25342
|
+
break;
|
|
25343
|
+
case `PageDown`:
|
|
25344
|
+
e.preventDefault();
|
|
25345
|
+
this.#handlePage(1);
|
|
25346
|
+
break;
|
|
25347
|
+
case `PageUp`:
|
|
25348
|
+
e.preventDefault();
|
|
25349
|
+
this.#handlePage(-1);
|
|
25350
|
+
break;
|
|
24860
25351
|
case `Enter`: {
|
|
24861
25352
|
const id = this.tickled.tickledId;
|
|
24862
25353
|
if (id) {
|
|
@@ -24978,16 +25469,17 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
24978
25469
|
#postDragSuppressTimer;
|
|
24979
25470
|
#nativeDrag;
|
|
24980
25471
|
#prepareNativeDrag(originKey) {
|
|
24981
|
-
const
|
|
24982
|
-
const originEl = visible.find((el) => keyForElement(el) === originKey);
|
|
25472
|
+
const originEl = this.#itemForKey(originKey);
|
|
24983
25473
|
if (!originEl || !this._isCheckable(originEl)) return void 0;
|
|
24984
25474
|
if (!this.selection.selected.has(originEl)) this.selection.replace(originEl);
|
|
25475
|
+
const ordered = this.#selectionOrder();
|
|
24985
25476
|
const selected = [];
|
|
24986
|
-
|
|
25477
|
+
ordered.forEach((el) => {
|
|
24987
25478
|
if (!this.selection.selected.has(el)) return;
|
|
25479
|
+
const idx = this.#registryIndexOf(el);
|
|
24988
25480
|
selected.push({
|
|
24989
25481
|
key: keyForElement(el) ?? originKey,
|
|
24990
|
-
index,
|
|
25482
|
+
index: idx,
|
|
24991
25483
|
item: el,
|
|
24992
25484
|
element: el
|
|
24993
25485
|
});
|
|
@@ -25214,6 +25706,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25214
25706
|
return;
|
|
25215
25707
|
}
|
|
25216
25708
|
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
25709
|
+
this.search.refresh();
|
|
25217
25710
|
}
|
|
25218
25711
|
/**
|
|
25219
25712
|
* Collapse + fade `item` out, then detach it. The rows below slide up to
|
|
@@ -25250,6 +25743,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25250
25743
|
this.replaceChildren();
|
|
25251
25744
|
this.selection.reset();
|
|
25252
25745
|
this._syncItemStates();
|
|
25746
|
+
this.search.refresh();
|
|
25253
25747
|
if (this.#virtualActive()) this.#syncVirtualWindow(true);
|
|
25254
25748
|
}
|
|
25255
25749
|
/**
|
|
@@ -25265,11 +25759,13 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25265
25759
|
this.#allItems = [...items];
|
|
25266
25760
|
for (const el of this.#allItems) this.#animatedItems.add(el);
|
|
25267
25761
|
this.selection.reset();
|
|
25762
|
+
this.search.refresh();
|
|
25268
25763
|
this.#syncVirtualWindow(true);
|
|
25269
25764
|
return;
|
|
25270
25765
|
}
|
|
25271
25766
|
for (const item of items) this.appendChild(item);
|
|
25272
25767
|
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
25768
|
+
this.search.refresh();
|
|
25273
25769
|
}
|
|
25274
25770
|
/**
|
|
25275
25771
|
* Dispatch a command to this list — used by `ixfx-grouped-item-lister` to
|
|
@@ -25326,7 +25822,16 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25326
25822
|
if (index < 0) return;
|
|
25327
25823
|
const ul = this.renderRoot.querySelector(`ul`);
|
|
25328
25824
|
if (ul) {
|
|
25329
|
-
|
|
25825
|
+
const stride = this.itemHeight;
|
|
25826
|
+
const viewportH = ul.clientHeight;
|
|
25827
|
+
const top = index * stride;
|
|
25828
|
+
const bottom = (index + 1) * stride;
|
|
25829
|
+
const vt = ul.scrollTop;
|
|
25830
|
+
const vh = viewportH;
|
|
25831
|
+
let next = vt;
|
|
25832
|
+
if (top < vt) next = top;
|
|
25833
|
+
else if (bottom > vt + vh) next = bottom - vh;
|
|
25834
|
+
if (next !== vt) ul.scrollTop = next;
|
|
25330
25835
|
this.#syncVirtualWindow(true);
|
|
25331
25836
|
}
|
|
25332
25837
|
}
|
|
@@ -25338,6 +25843,33 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25338
25843
|
inline: `nearest`
|
|
25339
25844
|
});
|
|
25340
25845
|
}
|
|
25846
|
+
#revealCursor() {
|
|
25847
|
+
const el = this.tickled.tickledId;
|
|
25848
|
+
const key = el ? keyForElement(el) : void 0;
|
|
25849
|
+
if (key !== void 0) this.focusKey(key);
|
|
25850
|
+
}
|
|
25851
|
+
#pageSize() {
|
|
25852
|
+
const ul = this.#scrollList();
|
|
25853
|
+
if (!ul) return 1;
|
|
25854
|
+
const viewportH = ul.clientHeight;
|
|
25855
|
+
if (this.#virtualActive()) return Math.max(1, Math.floor(viewportH / this.itemHeight));
|
|
25856
|
+
const first = this.querySelector(`li[data-key]`);
|
|
25857
|
+
if (!first) return 1;
|
|
25858
|
+
const firstH = first.offsetHeight || this.itemHeight;
|
|
25859
|
+
return Math.max(1, Math.floor(viewportH / firstH));
|
|
25860
|
+
}
|
|
25861
|
+
/** Move the cursor one page up (-1) or down (1) across the whole registry. */
|
|
25862
|
+
#handlePage(direction) {
|
|
25863
|
+
const page = this.#pageSize();
|
|
25864
|
+
const items = this.#selectionOrder().filter((el) => !el.hasAttribute(`not-checked`));
|
|
25865
|
+
if (items.length === 0) return;
|
|
25866
|
+
const currentKey = this.tickled.tickledId ? keyForElement(this.tickled.tickledId) : void 0;
|
|
25867
|
+
const currentIdx = currentKey !== void 0 ? items.findIndex((i) => keyForElement(i) === currentKey) : -1;
|
|
25868
|
+
const fallback = direction === 1 ? page - 1 : 0;
|
|
25869
|
+
const nextIdx = currentIdx < 0 ? fallback : currentIdx + direction * page;
|
|
25870
|
+
this.tickled.handleKeyboardNavigation(items[Math.min(items.length - 1, Math.max(0, nextIdx))]);
|
|
25871
|
+
this.#revealCursor();
|
|
25872
|
+
}
|
|
25341
25873
|
#itemForKey(key) {
|
|
25342
25874
|
if (this.#virtualActive()) return this.#allItems.find((el) => keyForElement(el) === key);
|
|
25343
25875
|
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) {
|
|
@@ -25686,6 +26218,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25686
26218
|
else this.#pendingAnimate.push(...fresh);
|
|
25687
26219
|
}
|
|
25688
26220
|
if (changed && this.interactionMode === `checked`) this._syncCheckboxes();
|
|
26221
|
+
if (changed) this.search.refresh();
|
|
25689
26222
|
return changed;
|
|
25690
26223
|
}
|
|
25691
26224
|
/** Consume queued observer records first so a sync never drops a row. */
|
|
@@ -25763,9 +26296,10 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25763
26296
|
this.#windowEnd = end;
|
|
25764
26297
|
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
25765
26298
|
this._syncItemStates();
|
|
26299
|
+
const totalCount = this.#selectionOrder().length;
|
|
25766
26300
|
for (let i = start; i < end; i++) {
|
|
25767
26301
|
const el = this.#allItems[i];
|
|
25768
|
-
if (el) this.#applyRowState(el);
|
|
26302
|
+
if (el) this.#applyRowState(el, i + 1, totalCount);
|
|
25769
26303
|
}
|
|
25770
26304
|
const pending = this.#pendingAnimate;
|
|
25771
26305
|
this.#pendingAnimate = [];
|
|
@@ -25773,12 +26307,25 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25773
26307
|
this.#animatedItems.add(el);
|
|
25774
26308
|
if (el.parentNode === this) animateListItemIn(el);
|
|
25775
26309
|
}
|
|
26310
|
+
this.search.repaintHighlights();
|
|
25776
26311
|
}
|
|
25777
26312
|
/** Per-attach state the attached sweep can't cover (it only sees the window). */
|
|
25778
|
-
#applyRowState(el) {
|
|
25779
|
-
if (el instanceof HTMLElement)
|
|
26313
|
+
#applyRowState(el, posInSet, setSize) {
|
|
26314
|
+
if (el instanceof HTMLElement) {
|
|
26315
|
+
el.draggable = this.itemsDraggable && this._isCheckable(el) && !el.hasAttribute(`data-editing`);
|
|
26316
|
+
if (posInSet !== void 0 && setSize !== void 0) {
|
|
26317
|
+
el.setAttribute(`aria-setsize`, String(setSize));
|
|
26318
|
+
el.setAttribute(`aria-posinset`, String(posInSet));
|
|
26319
|
+
if (!el.hasAttribute(`role`) || el.hasAttribute(`data-aria-role`)) {
|
|
26320
|
+
el.setAttribute(`role`, `option`);
|
|
26321
|
+
el.setAttribute(`data-aria-role`, ``);
|
|
26322
|
+
el.setAttribute(`aria-selected`, String(this.selection.selected.has(el)));
|
|
26323
|
+
}
|
|
26324
|
+
}
|
|
26325
|
+
}
|
|
25780
26326
|
el.removeAttribute(`data-marquee`);
|
|
25781
26327
|
el.removeAttribute(`data-drag-origin`);
|
|
26328
|
+
el.removeAttribute(`data-drop-target`);
|
|
25782
26329
|
}
|
|
25783
26330
|
/**
|
|
25784
26331
|
* Leave virtualization: reattach every registry row in order and drop the
|
|
@@ -25843,6 +26390,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25843
26390
|
const item = this.#customWrappers.get(el) ?? el;
|
|
25844
26391
|
el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
|
|
25845
26392
|
el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
|
|
26393
|
+
if (el.hasAttribute(`data-aria-role`)) el.setAttribute(`aria-selected`, String(this.selection.selected.has(el)));
|
|
25846
26394
|
if (this.interactionMode === `checked`) el.toggleAttribute(`data-checked`, !item.hasAttribute(`not-checked`) && this.selection.selected.has(el));
|
|
25847
26395
|
else el.removeAttribute(`data-checked`);
|
|
25848
26396
|
}
|
|
@@ -25986,6 +26534,10 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25986
26534
|
if (this.interactionMode !== `manual`) this.selection.replace(item);
|
|
25987
26535
|
this._dispatch(`list-activate`, { item });
|
|
25988
26536
|
}
|
|
26537
|
+
activateKey(key) {
|
|
26538
|
+
const item = this.#itemForKey(key);
|
|
26539
|
+
if (item) this._activateItem(item);
|
|
26540
|
+
}
|
|
25989
26541
|
/** The row (or `undefined` for empty space) the last `list-drag-over` was fired for. */
|
|
25990
26542
|
#dragOver;
|
|
25991
26543
|
/** Tell the consumer the drag left its last target and drop the highlight. */
|
|
@@ -25995,9 +26547,11 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
25995
26547
|
this.#highlightDropTarget(void 0, false);
|
|
25996
26548
|
if (last) this._dispatch(`list-drag-leave`, { key: last.item ? keyForElement(last.item) : void 0 });
|
|
25997
26549
|
}
|
|
26550
|
+
#dropTargetEl;
|
|
25998
26551
|
/** Mark one row (or, for empty space, the host) as the drop target. */
|
|
25999
26552
|
#highlightDropTarget(item, active) {
|
|
26000
|
-
|
|
26553
|
+
this.#dropTargetEl?.removeAttribute(`data-drop-target`);
|
|
26554
|
+
this.#dropTargetEl = active ? item : void 0;
|
|
26001
26555
|
this.toggleAttribute(`data-drop-target`, active && item === void 0);
|
|
26002
26556
|
if (active && item) item.setAttribute(`data-drop-target`, ``);
|
|
26003
26557
|
}
|
|
@@ -26282,9 +26836,17 @@ let VerticalListElement = class VerticalListElement extends LitElement {
|
|
|
26282
26836
|
];
|
|
26283
26837
|
}
|
|
26284
26838
|
render() {
|
|
26839
|
+
const role = this.getAttribute(`role`) ? void 0 : `listbox`;
|
|
26840
|
+
const ariaMulti = this.selectionMode === `multiple`;
|
|
26285
26841
|
return html`
|
|
26286
26842
|
<div class="container">
|
|
26287
|
-
<ul
|
|
26843
|
+
<ul
|
|
26844
|
+
part="list"
|
|
26845
|
+
class="scroll-fade-y"
|
|
26846
|
+
role=${ifDefined(role)}
|
|
26847
|
+
aria-multiselectable=${ariaMulti ? `true` : `false`}
|
|
26848
|
+
@scroll=${this.#onScroll}
|
|
26849
|
+
>
|
|
26288
26850
|
<slot></slot>
|
|
26289
26851
|
</ul>
|
|
26290
26852
|
${this.#marqueeRect ? html`<div class="marquee" part="marquee" style=${this.#marqueeStyle(this.#marqueeRect)}></div>` : ``}
|
|
@@ -26411,6 +26973,7 @@ let FlatListElement = class FlatListElement extends LitElement {
|
|
|
26411
26973
|
this.hideRoot = false;
|
|
26412
26974
|
this.emptyText = `Empty`;
|
|
26413
26975
|
this._focusVisible = false;
|
|
26976
|
+
this._hasToolbar = false;
|
|
26414
26977
|
this._rows = [];
|
|
26415
26978
|
this._loading = false;
|
|
26416
26979
|
this.#lastPath = [];
|
|
@@ -26502,6 +27065,9 @@ let FlatListElement = class FlatListElement extends LitElement {
|
|
|
26502
27065
|
composed: true
|
|
26503
27066
|
}));
|
|
26504
27067
|
};
|
|
27068
|
+
this.#onToolbarSlotChange = (e) => {
|
|
27069
|
+
this._hasToolbar = e.target.assignedNodes({ flatten: true }).some((n) => n.nodeType === Node.ELEMENT_NODE || !!n.textContent?.trim());
|
|
27070
|
+
};
|
|
26505
27071
|
this.#syncFocusVisible = () => {
|
|
26506
27072
|
this._focusVisible = this.list?.matches(`:focus-visible`) ?? false;
|
|
26507
27073
|
};
|
|
@@ -26744,16 +27310,21 @@ let FlatListElement = class FlatListElement extends LitElement {
|
|
|
26744
27310
|
#onListEditCancel;
|
|
26745
27311
|
#onListSelect;
|
|
26746
27312
|
#onListActivate;
|
|
27313
|
+
#onToolbarSlotChange;
|
|
26747
27314
|
#syncFocusVisible;
|
|
26748
27315
|
#onKeyDown;
|
|
26749
27316
|
/** A crumb was chosen (or the Up button used): go to that node, or to its parent and select it if it is a leaf. */
|
|
26750
27317
|
#onCrumbSelect;
|
|
27318
|
+
#markedEl;
|
|
26751
27319
|
/** Flag the marked row. Rows are rebuilt with the level, so this runs after each rebuild. */
|
|
26752
27320
|
async #applyMarked() {
|
|
26753
27321
|
const list = this.list;
|
|
26754
27322
|
if (!list) return;
|
|
26755
27323
|
await list.updateComplete;
|
|
26756
|
-
|
|
27324
|
+
this.#markedEl?.removeAttribute(`data-marked`);
|
|
27325
|
+
const key = this.markedKey;
|
|
27326
|
+
this.#markedEl = key === void 0 ? void 0 : list.getItemByKey(key);
|
|
27327
|
+
this.#markedEl?.setAttribute(`data-marked`, ``);
|
|
26757
27328
|
}
|
|
26758
27329
|
/** After the inner list has rebuilt, place the cursor / selection / focus. */
|
|
26759
27330
|
async #settleList() {
|
|
@@ -26795,6 +27366,9 @@ let FlatListElement = class FlatListElement extends LitElement {
|
|
|
26795
27366
|
?hide-root=${this.hideRoot}
|
|
26796
27367
|
@select=${this.#onCrumbSelect}
|
|
26797
27368
|
></ixfx-crumb-navigation>
|
|
27369
|
+
<div class="toolbar" part="toolbar" ?hidden=${!this._hasToolbar}>
|
|
27370
|
+
<slot @slotchange=${this.#onToolbarSlotChange}></slot>
|
|
27371
|
+
</div>
|
|
26798
27372
|
</div>
|
|
26799
27373
|
<div class="body">
|
|
26800
27374
|
<ixfx-vertical-list
|
|
@@ -26845,12 +27419,31 @@ let FlatListElement = class FlatListElement extends LitElement {
|
|
|
26845
27419
|
}
|
|
26846
27420
|
|
|
26847
27421
|
.crumbs {
|
|
27422
|
+
display: flex;
|
|
27423
|
+
align-items: center;
|
|
27424
|
+
gap: var(--flat-list-toolbar-gap, var(--space-xs));
|
|
26848
27425
|
flex: none;
|
|
26849
27426
|
font-size: var(--flat-list-crumb-font-size, var(--text-s));
|
|
26850
27427
|
padding: var(--flat-list-crumb-padding, var(--space-xs));
|
|
26851
27428
|
border-bottom: 1px solid var(--flat-list-border, var(--list-border, var(--border)));
|
|
26852
27429
|
}
|
|
26853
27430
|
|
|
27431
|
+
.crumbs ixfx-crumb-navigation {
|
|
27432
|
+
flex: 1 1 0;
|
|
27433
|
+
min-width: 0;
|
|
27434
|
+
}
|
|
27435
|
+
|
|
27436
|
+
.toolbar {
|
|
27437
|
+
display: flex;
|
|
27438
|
+
flex: none;
|
|
27439
|
+
align-items: center;
|
|
27440
|
+
gap: var(--flat-list-toolbar-gap, var(--space-xs));
|
|
27441
|
+
}
|
|
27442
|
+
|
|
27443
|
+
.toolbar[hidden] {
|
|
27444
|
+
display: none;
|
|
27445
|
+
}
|
|
27446
|
+
|
|
26854
27447
|
.body {
|
|
26855
27448
|
position: relative;
|
|
26856
27449
|
flex: 1 1 auto;
|
|
@@ -26984,6 +27577,7 @@ __decorate([property({
|
|
|
26984
27577
|
reflect: true,
|
|
26985
27578
|
attribute: `data-focus-visible`
|
|
26986
27579
|
})], FlatListElement.prototype, "_focusVisible", void 0);
|
|
27580
|
+
__decorate([state()], FlatListElement.prototype, "_hasToolbar", void 0);
|
|
26987
27581
|
__decorate([state()], FlatListElement.prototype, "_rows", void 0);
|
|
26988
27582
|
__decorate([state()], FlatListElement.prototype, "_loading", void 0);
|
|
26989
27583
|
FlatListElement = __decorate([safeCustomElement(`ixfx-flat-list`)], FlatListElement);
|
|
@@ -27498,16 +28092,24 @@ function warnDuplicateKeys(keys) {
|
|
|
27498
28092
|
}
|
|
27499
28093
|
if (dupes.size > 0) console.warn(`ixfx-grid-list: duplicate item key(s) ${[...dupes].map((k) => `"${k}"`).join(`, `)} — the first occurrence wins for key-based operations.`);
|
|
27500
28094
|
}
|
|
28095
|
+
/** Key → first effective index, built from `keys` (first occurrence wins on duplicates). */
|
|
28096
|
+
function indexByKey(keys) {
|
|
28097
|
+
const index = /* @__PURE__ */ new Map();
|
|
28098
|
+
keys.forEach((k, i) => {
|
|
28099
|
+
if (!index.has(k)) index.set(k, i);
|
|
28100
|
+
});
|
|
28101
|
+
return index;
|
|
28102
|
+
}
|
|
27501
28103
|
var ElementGridListSource = class {
|
|
27502
28104
|
#host;
|
|
27503
28105
|
#items = [];
|
|
27504
|
-
#slotNames = /* @__PURE__ */ new WeakMap();
|
|
27505
28106
|
#syntheticKeys = /* @__PURE__ */ new WeakMap();
|
|
27506
28107
|
#seq = 0;
|
|
27507
28108
|
#predicate;
|
|
27508
28109
|
#searchKeys;
|
|
27509
28110
|
#effectiveIndices = [];
|
|
27510
28111
|
#keysCache;
|
|
28112
|
+
#indexCache;
|
|
27511
28113
|
#grouping;
|
|
27512
28114
|
#groups = [];
|
|
27513
28115
|
#groupByKey = /* @__PURE__ */ new Map();
|
|
@@ -27526,7 +28128,8 @@ var ElementGridListSource = class {
|
|
|
27526
28128
|
return eff === void 0 ? `` : this.#itemKey(this.#items[eff]);
|
|
27527
28129
|
}
|
|
27528
28130
|
indexOfKey(key) {
|
|
27529
|
-
|
|
28131
|
+
this.#indexCache ??= indexByKey(this.keys());
|
|
28132
|
+
return this.#indexCache.get(key) ?? -1;
|
|
27530
28133
|
}
|
|
27531
28134
|
keys() {
|
|
27532
28135
|
this.#keysCache ??= this.#effectiveIndices.map((i) => this.#itemKey(this.#items[i]));
|
|
@@ -27549,11 +28152,11 @@ var ElementGridListSource = class {
|
|
|
27549
28152
|
const eff = this.#effectiveIndices[index];
|
|
27550
28153
|
return eff !== void 0 && !this.#items[eff].hasAttribute(`not-checked`);
|
|
27551
28154
|
}
|
|
27552
|
-
|
|
27553
|
-
|
|
27554
|
-
|
|
27555
|
-
|
|
27556
|
-
}
|
|
28155
|
+
/**
|
|
28156
|
+
* Element mode cells project their item through a per-cell `<slot>` that
|
|
28157
|
+
* `GridListElement` renders and assigns itself (see its `#applyCellSlots`).
|
|
28158
|
+
*/
|
|
28159
|
+
renderCellContent(_index) {}
|
|
27557
28160
|
iconAt(_index) {}
|
|
27558
28161
|
iconColorAt(_index) {}
|
|
27559
28162
|
textColorAt(_index) {}
|
|
@@ -27575,10 +28178,6 @@ var ElementGridListSource = class {
|
|
|
27575
28178
|
}
|
|
27576
28179
|
refresh() {
|
|
27577
28180
|
this.#items = Array.from(this.#host.querySelectorAll(ITEM_SELECTOR$1)).filter((el) => !el.hidden);
|
|
27578
|
-
for (const el of this.#items) {
|
|
27579
|
-
const name = this.slotNameFor(el);
|
|
27580
|
-
if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
|
|
27581
|
-
}
|
|
27582
28181
|
warnDuplicateKeys(this.#items.map((el) => this.#itemKey(el)));
|
|
27583
28182
|
this.#recomputeEffective();
|
|
27584
28183
|
}
|
|
@@ -27590,16 +28189,6 @@ var ElementGridListSource = class {
|
|
|
27590
28189
|
rawElementAt(index) {
|
|
27591
28190
|
return this.#items[index];
|
|
27592
28191
|
}
|
|
27593
|
-
/** Stable per-element `<slot>` name. */
|
|
27594
|
-
slotNameFor(el) {
|
|
27595
|
-
let name = this.#slotNames.get(el);
|
|
27596
|
-
if (name === void 0) {
|
|
27597
|
-
this.#seq += 1;
|
|
27598
|
-
name = `gl-item-${this.#seq}`;
|
|
27599
|
-
this.#slotNames.set(el, name);
|
|
27600
|
-
}
|
|
27601
|
-
return name;
|
|
27602
|
-
}
|
|
27603
28192
|
resolveKeyToElement(key) {
|
|
27604
28193
|
if (isSyntheticKey(key)) return this.#items.find((el) => this.#syntheticKeys.get(el) === key);
|
|
27605
28194
|
return this.#items.find((el) => keyForElement(el) === key);
|
|
@@ -27617,6 +28206,7 @@ var ElementGridListSource = class {
|
|
|
27617
28206
|
}
|
|
27618
28207
|
this.#applyGroupingTo(effective);
|
|
27619
28208
|
this.#keysCache = void 0;
|
|
28209
|
+
this.#indexCache = void 0;
|
|
27620
28210
|
}
|
|
27621
28211
|
#applyGroupingTo(effective) {
|
|
27622
28212
|
if (this.#grouping === void 0) {
|
|
@@ -27652,6 +28242,7 @@ var DataGridListSource = class {
|
|
|
27652
28242
|
#searchKeys;
|
|
27653
28243
|
#effectiveIndices = [];
|
|
27654
28244
|
#keysCache;
|
|
28245
|
+
#indexCache;
|
|
27655
28246
|
#grouping;
|
|
27656
28247
|
#groups = [];
|
|
27657
28248
|
#groupByKey = /* @__PURE__ */ new Map();
|
|
@@ -27672,7 +28263,8 @@ var DataGridListSource = class {
|
|
|
27672
28263
|
return eff === void 0 ? `` : this.#adapter.key(this.#items[eff], eff);
|
|
27673
28264
|
}
|
|
27674
28265
|
indexOfKey(key) {
|
|
27675
|
-
|
|
28266
|
+
this.#indexCache ??= indexByKey(this.keys());
|
|
28267
|
+
return this.#indexCache.get(key) ?? -1;
|
|
27676
28268
|
}
|
|
27677
28269
|
keys() {
|
|
27678
28270
|
this.#keysCache ??= this.#effectiveIndices.map((i) => this.#adapter.key(this.#items[i], i));
|
|
@@ -27765,6 +28357,7 @@ var DataGridListSource = class {
|
|
|
27765
28357
|
this.#groupByKey = grouped.groupByKey;
|
|
27766
28358
|
}
|
|
27767
28359
|
this.#keysCache = void 0;
|
|
28360
|
+
this.#indexCache = void 0;
|
|
27768
28361
|
}
|
|
27769
28362
|
};
|
|
27770
28363
|
//#endregion
|
|
@@ -27791,6 +28384,15 @@ function sameKeys(a, b) {
|
|
|
27791
28384
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
27792
28385
|
return true;
|
|
27793
28386
|
}
|
|
28387
|
+
/**
|
|
28388
|
+
* Set `attr` on exactly the elements in `now`, removing it from those in
|
|
28389
|
+
* `previous` that no longer qualify. Returns the new marked set.
|
|
28390
|
+
*/
|
|
28391
|
+
function syncMarker(attr, previous, now) {
|
|
28392
|
+
for (const el of previous) if (!now.has(el)) el.removeAttribute(attr);
|
|
28393
|
+
for (const el of now) if (!el.hasAttribute(attr)) el.setAttribute(attr, ``);
|
|
28394
|
+
return now;
|
|
28395
|
+
}
|
|
27794
28396
|
let GridListElement = class GridListElement extends LitElement {
|
|
27795
28397
|
constructor(..._args) {
|
|
27796
28398
|
super(..._args);
|
|
@@ -27825,6 +28427,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
27825
28427
|
this.#snapTimer = 0;
|
|
27826
28428
|
this.#items = [];
|
|
27827
28429
|
this.#itemWrap = `stop`;
|
|
28430
|
+
this.#cellIds = /* @__PURE__ */ new Map();
|
|
28431
|
+
this.#nextCellId = 0;
|
|
28432
|
+
this.#recycledKeys = /* @__PURE__ */ new Set();
|
|
28433
|
+
this.#windowSlots = [];
|
|
28434
|
+
this.#slottedItems = /* @__PURE__ */ new Map();
|
|
27828
28435
|
this.#entering = /* @__PURE__ */ new Set();
|
|
27829
28436
|
this.#reflowTimer = 0;
|
|
27830
28437
|
this.#sourceElement = new ElementGridListSource(this);
|
|
@@ -27948,6 +28555,8 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
27948
28555
|
}
|
|
27949
28556
|
});
|
|
27950
28557
|
this.#groupByChanged = false;
|
|
28558
|
+
this.#markedTickled = /* @__PURE__ */ new Set();
|
|
28559
|
+
this.#markedSelected = /* @__PURE__ */ new Set();
|
|
27951
28560
|
this.#selectedSnapshot = /* @__PURE__ */ new Set();
|
|
27952
28561
|
this.#onScroll = (e) => {
|
|
27953
28562
|
const el = e.target;
|
|
@@ -28437,6 +29046,14 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28437
29046
|
.cell.entering {
|
|
28438
29047
|
animation: ixfx-list-item-in var(--grid-list-item-move-duration, 200ms) ease both;
|
|
28439
29048
|
}
|
|
29049
|
+
/* A recycled cell has just been handed from one item to another (see
|
|
29050
|
+
#assignCellIds); it must not animate from the previous item's state
|
|
29051
|
+
or position. */
|
|
29052
|
+
.cell.recycled,
|
|
29053
|
+
.cell.recycled > .thumb,
|
|
29054
|
+
.cell.recycled > .thumb::after {
|
|
29055
|
+
transition: none !important;
|
|
29056
|
+
}
|
|
28440
29057
|
@media (prefers-reduced-motion: reduce) {
|
|
28441
29058
|
:host([reflowing]) .group-header { transition: none; }
|
|
28442
29059
|
:host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
|
|
@@ -28468,11 +29085,39 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28468
29085
|
#viewportRef;
|
|
28469
29086
|
#resizeObserver;
|
|
28470
29087
|
#mutationObserver;
|
|
29088
|
+
/**
|
|
29089
|
+
* Watches item (direct child) attributes that change the item set: `hidden`
|
|
29090
|
+
* drops an item from layout, `data-key` / `data-value` change its identity.
|
|
29091
|
+
* Separate from {@link #mutationObserver} because attribute observation of
|
|
29092
|
+
* children needs `subtree`, which must not apply to the child list.
|
|
29093
|
+
*/
|
|
29094
|
+
#itemAttrObserver;
|
|
28471
29095
|
#scrollRaf;
|
|
28472
29096
|
/** Debounce handle for {@link snapToItem} — fires once scrolling settles. */
|
|
28473
29097
|
#snapTimer;
|
|
28474
29098
|
#items;
|
|
28475
29099
|
#itemWrap;
|
|
29100
|
+
/**
|
|
29101
|
+
* Cell recycling. Each rendered key holds a stable cell id, and `repeat` is
|
|
29102
|
+
* keyed by that id rather than the item key, so a cell scrolled out of the
|
|
29103
|
+
* window hands its DOM to an item scrolling in instead of being destroyed and
|
|
29104
|
+
* recreated. Item-keyed `repeat` gets steadily slower in Chrome when every key
|
|
29105
|
+
* in the window is new (a scrollbar drag / fling), and recreating ~100 cells a
|
|
29106
|
+
* frame is costly in any case. See {@link #assignCellIds}.
|
|
29107
|
+
*/
|
|
29108
|
+
#cellIds;
|
|
29109
|
+
#nextCellId;
|
|
29110
|
+
/** Keys whose cell was taken over from another item in the latest render. */
|
|
29111
|
+
#recycledKeys;
|
|
29112
|
+
/**
|
|
29113
|
+
* Element mode: each cell has a fixed `<slot name="gl-cell-<id>">`, and the
|
|
29114
|
+
* light-DOM item it shows is pointed at it via its `slot` attribute. Moving
|
|
29115
|
+
* that attribute is far cheaper than renaming slots, which makes the browser
|
|
29116
|
+
* re-match every light-DOM child. `#windowSlots` is the assignment the latest
|
|
29117
|
+
* render wants (applied in `updated()`); `#slottedItems` is what's applied.
|
|
29118
|
+
*/
|
|
29119
|
+
#windowSlots;
|
|
29120
|
+
#slottedItems;
|
|
28476
29121
|
/** Items added mid-list that are still playing their enter animation. */
|
|
28477
29122
|
#entering;
|
|
28478
29123
|
/** Timer that ends the `[reflowing]` window opened by a mid-list insertion. */
|
|
@@ -28660,9 +29305,15 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28660
29305
|
this.addEventListener(`pointermove`, this.#onHostPointerMove, { passive: true });
|
|
28661
29306
|
this.addEventListener(`pointerleave`, this.#onHostPointerLeave);
|
|
28662
29307
|
this.#mutationObserver ??= new MutationObserver((records) => this.#onChildrenChanged(records));
|
|
28663
|
-
this.#mutationObserver.observe(this, {
|
|
28664
|
-
|
|
28665
|
-
|
|
29308
|
+
this.#mutationObserver.observe(this, { childList: true });
|
|
29309
|
+
this.#itemAttrObserver ??= new MutationObserver((records) => this.#onItemAttributesChanged(records));
|
|
29310
|
+
this.#itemAttrObserver.observe(this, {
|
|
29311
|
+
subtree: true,
|
|
29312
|
+
attributeFilter: [
|
|
29313
|
+
`hidden`,
|
|
29314
|
+
`data-key`,
|
|
29315
|
+
`data-value`
|
|
29316
|
+
]
|
|
28666
29317
|
});
|
|
28667
29318
|
this.#searchRegistry ??= createCommandRegistry();
|
|
28668
29319
|
this.#ensureSearch();
|
|
@@ -28678,6 +29329,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28678
29329
|
this.#resizeObserver?.disconnect();
|
|
28679
29330
|
this.#resizeObserver = void 0;
|
|
28680
29331
|
this.#mutationObserver?.disconnect();
|
|
29332
|
+
this.#itemAttrObserver?.disconnect();
|
|
28681
29333
|
this.#cancelScrollRaf();
|
|
28682
29334
|
this.#cancelSnapTimer();
|
|
28683
29335
|
this.#unregisterSearchCommands?.();
|
|
@@ -28689,6 +29341,10 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28689
29341
|
}
|
|
28690
29342
|
if (this.#nativeDragging || this.#nativeDrag.isDragging) this.#clearNativeDragState();
|
|
28691
29343
|
}
|
|
29344
|
+
/** Only attribute changes on items themselves matter, not on their content. */
|
|
29345
|
+
#onItemAttributesChanged(records) {
|
|
29346
|
+
if (records.some((r) => r.target.parentNode === this)) this.#onChildrenChanged([]);
|
|
29347
|
+
}
|
|
28692
29348
|
#onChildrenChanged(records) {
|
|
28693
29349
|
const previousLength = this.#items.length;
|
|
28694
29350
|
this.#syncItems();
|
|
@@ -28705,7 +29361,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28705
29361
|
const tickledId = this.tickled.tickledId;
|
|
28706
29362
|
if (tickledId && gone.includes(tickledId)) this.tickled.focus(void 0);
|
|
28707
29363
|
}
|
|
28708
|
-
this._syncItemStates();
|
|
29364
|
+
this._syncItemStates(true);
|
|
28709
29365
|
this.#noteChildMutation(previousLength, records);
|
|
28710
29366
|
this.requestUpdate();
|
|
28711
29367
|
this.updateComplete.then(() => this.#emitViewportChange());
|
|
@@ -28777,6 +29433,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28777
29433
|
const w = entry.contentRect.width;
|
|
28778
29434
|
const h = entry.contentRect.height;
|
|
28779
29435
|
if (w <= 0 || h <= 0) return;
|
|
29436
|
+
if (w === this._viewportWidth && h === this._viewportHeight) continue;
|
|
29437
|
+
const horizontal = this.orientation === `row`;
|
|
29438
|
+
const prevOffset = horizontal ? this._scrollLeft : this._scrollTop;
|
|
29439
|
+
const prevExtent = horizontal ? this._viewportWidth : this._viewportHeight;
|
|
29440
|
+
if (prevExtent > 0 && prevOffset > 1) this.#captureCentreAnchor(prevOffset, prevExtent, this.itemSize, this.gap, this.captionStyle, this.captionHeight, this.orientation);
|
|
28780
29441
|
this._viewportWidth = w;
|
|
28781
29442
|
this._viewportHeight = h;
|
|
28782
29443
|
}
|
|
@@ -28938,7 +29599,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28938
29599
|
}
|
|
28939
29600
|
updated(changed) {
|
|
28940
29601
|
super.updated(changed);
|
|
28941
|
-
this.#
|
|
29602
|
+
const pending = this.#mutationObserver?.takeRecords();
|
|
29603
|
+
const pendingAttrs = this.#itemAttrObserver?.takeRecords();
|
|
29604
|
+
if (pending !== void 0 && pending.length > 0) this.#onChildrenChanged(pending);
|
|
29605
|
+
else if (pendingAttrs !== void 0 && pendingAttrs.length > 0) this.#onItemAttributesChanged(pendingAttrs);
|
|
29606
|
+
if (this.#source instanceof ElementGridListSource) this.#applyCellSlots();
|
|
28942
29607
|
if (changed.has(`selectionMode`) || changed.has(`interactionMode`)) {
|
|
28943
29608
|
this.selection.clearAnchor();
|
|
28944
29609
|
this.#syncAriaMultiselectable();
|
|
@@ -29290,13 +29955,36 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29290
29955
|
this._scrollTop = next;
|
|
29291
29956
|
}
|
|
29292
29957
|
}
|
|
29293
|
-
|
|
29958
|
+
/** Light-DOM items currently carrying `data-tickled` / `data-selected`. */
|
|
29959
|
+
#markedTickled;
|
|
29960
|
+
#markedSelected;
|
|
29961
|
+
/**
|
|
29962
|
+
* Mirror cursor / selection onto the light-DOM items as `data-tickled` /
|
|
29963
|
+
* `data-selected`. Only the items whose state changed are touched, so a
|
|
29964
|
+
* cursor move costs O(selection), not O(items). `full` re-checks every item —
|
|
29965
|
+
* used when the child list changes, so markers set before the item was
|
|
29966
|
+
* ours (or left on a removed item) are corrected.
|
|
29967
|
+
*/
|
|
29968
|
+
_syncItemStates(full = false) {
|
|
29294
29969
|
const selected = this.selection.selectedKeys;
|
|
29295
|
-
|
|
29296
|
-
|
|
29297
|
-
|
|
29298
|
-
|
|
29970
|
+
const tickledId = this.tickled.tickledId;
|
|
29971
|
+
const elementFor = (key) => {
|
|
29972
|
+
if (isSyntheticKey(key)) return void 0;
|
|
29973
|
+
const index = this.#source.indexOfKey(key);
|
|
29974
|
+
return index < 0 ? void 0 : this.#items[index];
|
|
29975
|
+
};
|
|
29976
|
+
const tickledEl = tickledId === void 0 ? void 0 : elementFor(tickledId);
|
|
29977
|
+
const selectedEls = /* @__PURE__ */ new Set();
|
|
29978
|
+
for (const key of selected) {
|
|
29979
|
+
const el = elementFor(key);
|
|
29980
|
+
if (el) selectedEls.add(el);
|
|
29299
29981
|
}
|
|
29982
|
+
if (full) for (const el of this.#items) {
|
|
29983
|
+
this.#markedTickled.add(el);
|
|
29984
|
+
this.#markedSelected.add(el);
|
|
29985
|
+
}
|
|
29986
|
+
this.#markedTickled = syncMarker(`data-tickled`, this.#markedTickled, tickledEl ? /* @__PURE__ */ new Set([tickledEl]) : /* @__PURE__ */ new Set());
|
|
29987
|
+
this.#markedSelected = syncMarker(`data-selected`, this.#markedSelected, selectedEls);
|
|
29300
29988
|
this.setAttribute(`data-input-mode`, this.tickled.inputMode);
|
|
29301
29989
|
}
|
|
29302
29990
|
#emitSelect(selected, previous) {
|
|
@@ -29366,8 +30054,57 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29366
30054
|
`;
|
|
29367
30055
|
}
|
|
29368
30056
|
#renderDataCells(win) {
|
|
30057
|
+
const slice = this.#windowSlice(win);
|
|
30058
|
+
return repeat(slice, (index) => this.#cellIds.get(this.#source.keyAt(index)), (index) => this.#renderDataCell(index, win));
|
|
30059
|
+
}
|
|
30060
|
+
#cellSlotName(key) {
|
|
30061
|
+
return `gl-cell-${this.#cellIds.get(key)}`;
|
|
30062
|
+
}
|
|
30063
|
+
/**
|
|
30064
|
+
* Element mode: point each rendered item's `slot` at its cell, and clear it on
|
|
30065
|
+
* items that left the window (or the DOM) so they project nowhere.
|
|
30066
|
+
*/
|
|
30067
|
+
#applyCellSlots() {
|
|
30068
|
+
const next = /* @__PURE__ */ new Map();
|
|
30069
|
+
for (const [el, name] of this.#windowSlots) {
|
|
30070
|
+
next.set(el, name);
|
|
30071
|
+
if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
|
|
30072
|
+
}
|
|
30073
|
+
for (const el of this.#slottedItems.keys()) if (!next.has(el)) el.removeAttribute(`slot`);
|
|
30074
|
+
this.#slottedItems = next;
|
|
30075
|
+
}
|
|
30076
|
+
/** Effective indices in the rendered window, with cell ids assigned to their keys. */
|
|
30077
|
+
#windowSlice(win) {
|
|
29369
30078
|
const slice = win.ranges.flatMap(([start, end]) => Array.from({ length: end - start }, (_, i) => start + i));
|
|
29370
|
-
|
|
30079
|
+
this.#assignCellIds(slice.map((index) => this.#source.keyAt(index)));
|
|
30080
|
+
return slice;
|
|
30081
|
+
}
|
|
30082
|
+
/**
|
|
30083
|
+
* Give every key in the window a cell id: keys already rendered keep theirs;
|
|
30084
|
+
* new keys take the ids freed by keys that left the window (recycling that
|
|
30085
|
+
* cell's DOM), and only mint a fresh id when none is free.
|
|
30086
|
+
*/
|
|
30087
|
+
#assignCellIds(keys) {
|
|
30088
|
+
const inWindow = new Set(keys);
|
|
30089
|
+
const free = [];
|
|
30090
|
+
for (const [key, id] of this.#cellIds) if (!inWindow.has(key)) free.push(id);
|
|
30091
|
+
const next = /* @__PURE__ */ new Map();
|
|
30092
|
+
for (const key of keys) {
|
|
30093
|
+
const id = this.#cellIds.get(key);
|
|
30094
|
+
if (id !== void 0) next.set(key, id);
|
|
30095
|
+
}
|
|
30096
|
+
const recycled = /* @__PURE__ */ new Set();
|
|
30097
|
+
for (const key of keys) {
|
|
30098
|
+
if (next.has(key)) continue;
|
|
30099
|
+
const id = free.pop();
|
|
30100
|
+
if (id === void 0) next.set(key, this.#nextCellId++);
|
|
30101
|
+
else {
|
|
30102
|
+
next.set(key, id);
|
|
30103
|
+
recycled.add(key);
|
|
30104
|
+
}
|
|
30105
|
+
}
|
|
30106
|
+
this.#cellIds = next;
|
|
30107
|
+
this.#recycledKeys = recycled;
|
|
29371
30108
|
}
|
|
29372
30109
|
#renderDataCell(index, win) {
|
|
29373
30110
|
const key = this.#source.keyAt(index);
|
|
@@ -29387,10 +30124,12 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29387
30124
|
const icon = appearanceAbsent(rawIcon) ? void 0 : rawIcon;
|
|
29388
30125
|
return html`
|
|
29389
30126
|
<div
|
|
29390
|
-
class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${marquee ? ` marquee` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
|
|
30127
|
+
class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${marquee ? ` marquee` : ``}${this.#recycledKeys.has(key) ? ` recycled` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
|
|
29391
30128
|
part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
|
|
29392
30129
|
role="option"
|
|
29393
30130
|
aria-selected=${selected ? `true` : `false`}
|
|
30131
|
+
aria-setsize=${this.#source.count}
|
|
30132
|
+
aria-posinset=${index + 1}
|
|
29394
30133
|
style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
|
|
29395
30134
|
data-index=${index}
|
|
29396
30135
|
data-key=${key}
|
|
@@ -29467,8 +30206,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29467
30206
|
current.startLoop?.();
|
|
29468
30207
|
}
|
|
29469
30208
|
#renderElementCells(win) {
|
|
29470
|
-
const slice = win
|
|
29471
|
-
|
|
30209
|
+
const slice = this.#windowSlice(win);
|
|
30210
|
+
this.#windowSlots = slice.map((index) => [this.#items[index], this.#cellSlotName(this.#source.keyAt(index))]);
|
|
30211
|
+
return repeat(slice, (index) => this.#cellIds.get(this.#source.keyAt(index)), (index) => this.#renderCell(this.#items[index], index, win));
|
|
29472
30212
|
}
|
|
29473
30213
|
#renderHeaders(win) {
|
|
29474
30214
|
if (win.headers.length === 0) return nothing;
|
|
@@ -29477,6 +30217,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29477
30217
|
<ixfx-list-group-header
|
|
29478
30218
|
class="group-header"
|
|
29479
30219
|
part="group-header"
|
|
30220
|
+
role="presentation"
|
|
29480
30221
|
data-group-key=${h.key}
|
|
29481
30222
|
label=${h.label}
|
|
29482
30223
|
count=${h.count}
|
|
@@ -29505,10 +30246,12 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29505
30246
|
const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
|
|
29506
30247
|
return html`
|
|
29507
30248
|
<div
|
|
29508
|
-
class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}${marquee ? ` marquee` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
|
|
30249
|
+
class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}${marquee ? ` marquee` : ``}${this.#recycledKeys.has(key) ? ` recycled` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
|
|
29509
30250
|
part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
|
|
29510
30251
|
role="option"
|
|
29511
30252
|
aria-selected=${selected ? `true` : `false`}
|
|
30253
|
+
aria-setsize=${this.#source.count}
|
|
30254
|
+
aria-posinset=${index + 1}
|
|
29512
30255
|
style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
|
|
29513
30256
|
data-index=${index}
|
|
29514
30257
|
data-key=${key}
|
|
@@ -29521,7 +30264,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29521
30264
|
@pointerenter=${() => this.#handleCellPointerEnter(key)}
|
|
29522
30265
|
>
|
|
29523
30266
|
<div class="thumb" part="thumb" style=${thumbStyle}>
|
|
29524
|
-
|
|
30267
|
+
<slot name=${this.#cellSlotName(key)}></slot>
|
|
29525
30268
|
</div>
|
|
29526
30269
|
${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
|
|
29527
30270
|
</div>
|
|
@@ -30373,15 +31116,10 @@ var TreeDropController = class {
|
|
|
30373
31116
|
if (clientY > rows[rows.length - 1].rect.bottom) return rows[rows.length - 1];
|
|
30374
31117
|
return rows.find((r) => clientY >= r.rect.top && clientY <= r.rect.bottom) ?? rows[rows.length - 1];
|
|
30375
31118
|
}
|
|
30376
|
-
#parentRow(rows, index) {
|
|
30377
|
-
const target = rows[index];
|
|
30378
|
-
for (let i = index - 1; i >= 0; i--) if (rows[i].depth === target.depth - 1) return rows[i];
|
|
30379
|
-
}
|
|
30380
31119
|
#resolveIntent(event, rows, source) {
|
|
30381
31120
|
if (rows.length === 0) return void 0;
|
|
30382
31121
|
const row = this.#rowAtPointer(rows, event.clientY);
|
|
30383
31122
|
if (!row) return void 0;
|
|
30384
|
-
const rowIndex = rows.indexOf(row);
|
|
30385
31123
|
const f = clamp$4((event.clientY - row.rect.top) / row.rect.height, 0, 1);
|
|
30386
31124
|
let position = f < .25 ? `before` : f > .75 ? `after` : `inside`;
|
|
30387
31125
|
const isLeaf = row.node.item.isLeaf === true;
|
|
@@ -30404,18 +31142,21 @@ var TreeDropController = class {
|
|
|
30404
31142
|
const indent = this.#config.getIndentPx() || 1;
|
|
30405
31143
|
const treeLeft = rows[0].rect.left;
|
|
30406
31144
|
const effDepth = clamp$4(Math.round((event.clientX - treeLeft) / indent), 0, row.depth);
|
|
30407
|
-
let
|
|
30408
|
-
|
|
30409
|
-
|
|
30410
|
-
|
|
30411
|
-
|
|
30412
|
-
|
|
30413
|
-
|
|
30414
|
-
|
|
31145
|
+
let anchor = {
|
|
31146
|
+
node: row.node,
|
|
31147
|
+
depth: row.depth,
|
|
31148
|
+
parentKey: this.#config.getRowInfo(row.key)?.parentKey
|
|
31149
|
+
};
|
|
31150
|
+
referenceKey = row.key;
|
|
31151
|
+
for (let up = row.depth - effDepth; up > 0 && anchor.parentKey !== void 0; up--) {
|
|
31152
|
+
const parentInfo = this.#config.getRowInfo(anchor.parentKey);
|
|
31153
|
+
if (!parentInfo) break;
|
|
31154
|
+
referenceKey = anchor.parentKey;
|
|
31155
|
+
anchor = parentInfo;
|
|
31156
|
+
}
|
|
30415
31157
|
depth = anchor.depth;
|
|
30416
|
-
|
|
30417
|
-
|
|
30418
|
-
const refIdx = (parent ? parent.node.children ?? [] : rows.filter((r) => r.depth === 0).map((r) => r.node)).findIndex((n) => n.item.key === anchor.key);
|
|
31158
|
+
parentKey = anchor.parentKey;
|
|
31159
|
+
const refIdx = this.#config.getChildren(parentKey).findIndex((n) => n.item.key === referenceKey);
|
|
30419
31160
|
index = (refIdx < 0 ? 0 : refIdx) + (position === `after` ? 1 : 0);
|
|
30420
31161
|
}
|
|
30421
31162
|
if (source === `internal`) {
|
|
@@ -30490,9 +31231,333 @@ var TreeDropController = class {
|
|
|
30490
31231
|
}
|
|
30491
31232
|
};
|
|
30492
31233
|
//#endregion
|
|
31234
|
+
//#region src/tree/tree-flat.ts
|
|
31235
|
+
/**
|
|
31236
|
+
* Flatten the visible rows under `root` (root itself excluded).
|
|
31237
|
+
*
|
|
31238
|
+
* Iterative implementation using an explicit stack to avoid recursion depth
|
|
31239
|
+
* limits with deep trees. `posInSet` / `setSize` count only shown siblings.
|
|
31240
|
+
*
|
|
31241
|
+
* @param root - The root node (never included in output; its children are depth 0)
|
|
31242
|
+
* @param options - Callbacks determining expansion and visibility
|
|
31243
|
+
* @returns FlatTree with rows in display order and a key→index map
|
|
31244
|
+
*/
|
|
31245
|
+
function flattenTree(root, options) {
|
|
31246
|
+
const rows = [];
|
|
31247
|
+
const indexByKey = /* @__PURE__ */ new Map();
|
|
31248
|
+
const stack = [];
|
|
31249
|
+
const pushChildren = (children, depth, parentKey) => {
|
|
31250
|
+
if (!children || children.length === 0) return;
|
|
31251
|
+
const shown = children.filter((c) => options.isShown(c, depth));
|
|
31252
|
+
for (let i = shown.length - 1; i >= 0; i--) stack.push({
|
|
31253
|
+
node: shown[i],
|
|
31254
|
+
depth,
|
|
31255
|
+
parentKey,
|
|
31256
|
+
posInSet: i + 1,
|
|
31257
|
+
setSize: shown.length
|
|
31258
|
+
});
|
|
31259
|
+
};
|
|
31260
|
+
pushChildren(root.children, 0, void 0);
|
|
31261
|
+
while (stack.length > 0) {
|
|
31262
|
+
const { node, depth, parentKey, posInSet, setSize } = stack.pop();
|
|
31263
|
+
indexByKey.set(node.item.key, rows.length);
|
|
31264
|
+
rows.push({
|
|
31265
|
+
node,
|
|
31266
|
+
depth,
|
|
31267
|
+
parentKey,
|
|
31268
|
+
posInSet,
|
|
31269
|
+
setSize
|
|
31270
|
+
});
|
|
31271
|
+
if (node.children && node.children.length > 0 && options.isExpanded(node, depth)) pushChildren(node.children, depth + 1, node.item.key);
|
|
31272
|
+
}
|
|
31273
|
+
return {
|
|
31274
|
+
rows,
|
|
31275
|
+
indexByKey
|
|
31276
|
+
};
|
|
31277
|
+
}
|
|
31278
|
+
/**
|
|
31279
|
+
* Build a whole-tree index over the currently loaded nodes.
|
|
31280
|
+
* This walks the entire loaded tree once and is cached on structure version.
|
|
31281
|
+
*
|
|
31282
|
+
* @param root - The root node
|
|
31283
|
+
* @returns TreeIndex with parent map and icon depth set
|
|
31284
|
+
*/
|
|
31285
|
+
function indexTree(root) {
|
|
31286
|
+
const parentByKey = /* @__PURE__ */ new Map();
|
|
31287
|
+
const depthsWithIcons = /* @__PURE__ */ new Set();
|
|
31288
|
+
const stack = [];
|
|
31289
|
+
if (root.children) for (const child of root.children) {
|
|
31290
|
+
parentByKey.set(child.item.key, root.item.key);
|
|
31291
|
+
stack.push({
|
|
31292
|
+
node: child,
|
|
31293
|
+
depth: 0,
|
|
31294
|
+
parentKey: root.item.key
|
|
31295
|
+
});
|
|
31296
|
+
}
|
|
31297
|
+
while (stack.length > 0) {
|
|
31298
|
+
const { node, depth, parentKey } = stack.pop();
|
|
31299
|
+
parentByKey.set(node.item.key, parentKey);
|
|
31300
|
+
if (node.item.icon) depthsWithIcons.add(depth);
|
|
31301
|
+
if (node.children) for (let i = node.children.length - 1; i >= 0; i--) {
|
|
31302
|
+
const child = node.children[i];
|
|
31303
|
+
stack.push({
|
|
31304
|
+
node: child,
|
|
31305
|
+
depth: depth + 1,
|
|
31306
|
+
parentKey: node.item.key
|
|
31307
|
+
});
|
|
31308
|
+
}
|
|
31309
|
+
}
|
|
31310
|
+
return {
|
|
31311
|
+
parentByKey,
|
|
31312
|
+
depthsWithIcons
|
|
31313
|
+
};
|
|
31314
|
+
}
|
|
31315
|
+
/**
|
|
31316
|
+
* Compute which nodes match a filter predicate.
|
|
31317
|
+
* Returns the set of keys for nodes that match OR have a matching descendant.
|
|
31318
|
+
*
|
|
31319
|
+
* One iterative post-order pass; every node is visited (no short-circuiting).
|
|
31320
|
+
*/
|
|
31321
|
+
function computeFilterMatches(root, predicate) {
|
|
31322
|
+
const matches = /* @__PURE__ */ new Set();
|
|
31323
|
+
if (!root.children) return matches;
|
|
31324
|
+
const stack = [];
|
|
31325
|
+
const enter = (node, depth) => {
|
|
31326
|
+
stack.push({
|
|
31327
|
+
node,
|
|
31328
|
+
depth,
|
|
31329
|
+
next: 0,
|
|
31330
|
+
matched: predicate(node, depth)
|
|
31331
|
+
});
|
|
31332
|
+
};
|
|
31333
|
+
for (const child of root.children) {
|
|
31334
|
+
enter(child, 0);
|
|
31335
|
+
while (stack.length > 0) {
|
|
31336
|
+
const frame = stack[stack.length - 1];
|
|
31337
|
+
const children = frame.node.children;
|
|
31338
|
+
if (children && frame.next < children.length) {
|
|
31339
|
+
enter(children[frame.next++], frame.depth + 1);
|
|
31340
|
+
continue;
|
|
31341
|
+
}
|
|
31342
|
+
stack.pop();
|
|
31343
|
+
if (frame.matched) {
|
|
31344
|
+
matches.add(frame.node.item.key);
|
|
31345
|
+
if (stack.length > 0) stack[stack.length - 1].matched = true;
|
|
31346
|
+
}
|
|
31347
|
+
}
|
|
31348
|
+
}
|
|
31349
|
+
return matches;
|
|
31350
|
+
}
|
|
31351
|
+
/**
|
|
31352
|
+
* Merge ghost rows back near their previous position.
|
|
31353
|
+
* Each ghost goes immediately before the first row that followed it last render
|
|
31354
|
+
* and still exists; otherwise at the end. Ghosts sharing an anchor keep their
|
|
31355
|
+
* previous relative order. O(rows + prevKeys).
|
|
31356
|
+
*
|
|
31357
|
+
* @param rows - Current flat rows (with node and depth)
|
|
31358
|
+
* @param prevKeys - Previous render's row keys in order
|
|
31359
|
+
* @param ghosts - Map of ghost keys to their row data
|
|
31360
|
+
* @returns Rows with ghosts woven in at their original relative positions
|
|
31361
|
+
*/
|
|
31362
|
+
function weaveGhosts(rows, prevKeys, ghosts) {
|
|
31363
|
+
if (ghosts.size === 0) return [...rows];
|
|
31364
|
+
const present = /* @__PURE__ */ new Set();
|
|
31365
|
+
for (const r of rows) present.add(r.node.item.key);
|
|
31366
|
+
const END = Symbol(`end`);
|
|
31367
|
+
const before = /* @__PURE__ */ new Map();
|
|
31368
|
+
let nextSurvivor = END;
|
|
31369
|
+
for (let i = prevKeys.length - 1; i >= 0; i--) {
|
|
31370
|
+
const key = prevKeys[i];
|
|
31371
|
+
if (present.has(key)) {
|
|
31372
|
+
nextSurvivor = key;
|
|
31373
|
+
continue;
|
|
31374
|
+
}
|
|
31375
|
+
const ghost = ghosts.get(key);
|
|
31376
|
+
if (!ghost) continue;
|
|
31377
|
+
const list = before.get(nextSurvivor);
|
|
31378
|
+
if (list) list.push(ghost);
|
|
31379
|
+
else before.set(nextSurvivor, [ghost]);
|
|
31380
|
+
}
|
|
31381
|
+
const result = [];
|
|
31382
|
+
for (const row of rows) {
|
|
31383
|
+
const list = before.get(row.node.item.key);
|
|
31384
|
+
if (list) for (let i = list.length - 1; i >= 0; i--) result.push(list[i]);
|
|
31385
|
+
result.push(row);
|
|
31386
|
+
}
|
|
31387
|
+
const tail = before.get(END);
|
|
31388
|
+
if (tail) for (let i = tail.length - 1; i >= 0; i--) result.push(tail[i]);
|
|
31389
|
+
return result;
|
|
31390
|
+
}
|
|
31391
|
+
/**
|
|
31392
|
+
* Compute checkbox states for all nodes in a single iterative post-order pass.
|
|
31393
|
+
* A leaf (or a branch with no children) uses `selected.has(node)`; otherwise a
|
|
31394
|
+
* branch is checked if all children are checked, unchecked if all are
|
|
31395
|
+
* unchecked, else indeterminate.
|
|
31396
|
+
*
|
|
31397
|
+
* @param root - The root node
|
|
31398
|
+
* @param selected - Set of selected nodes
|
|
31399
|
+
* @returns Map from node key to checkbox state
|
|
31400
|
+
*/
|
|
31401
|
+
function computeCheckboxStates(root, selected) {
|
|
31402
|
+
const states = /* @__PURE__ */ new Map();
|
|
31403
|
+
if (!root.children) return states;
|
|
31404
|
+
const stack = [];
|
|
31405
|
+
for (const top of root.children) {
|
|
31406
|
+
stack.push({
|
|
31407
|
+
node: top,
|
|
31408
|
+
next: 0,
|
|
31409
|
+
checked: 0,
|
|
31410
|
+
unchecked: 0
|
|
31411
|
+
});
|
|
31412
|
+
while (stack.length > 0) {
|
|
31413
|
+
const frame = stack[stack.length - 1];
|
|
31414
|
+
const { node } = frame;
|
|
31415
|
+
const children = node.children;
|
|
31416
|
+
const isLeafLike = node.item.isLeaf || !children?.length;
|
|
31417
|
+
if (!isLeafLike && frame.next < children.length) {
|
|
31418
|
+
stack.push({
|
|
31419
|
+
node: children[frame.next++],
|
|
31420
|
+
next: 0,
|
|
31421
|
+
checked: 0,
|
|
31422
|
+
unchecked: 0
|
|
31423
|
+
});
|
|
31424
|
+
continue;
|
|
31425
|
+
}
|
|
31426
|
+
stack.pop();
|
|
31427
|
+
let state;
|
|
31428
|
+
if (isLeafLike) state = selected.has(node) ? `checked` : `unchecked`;
|
|
31429
|
+
else if (frame.checked === children.length) state = `checked`;
|
|
31430
|
+
else if (frame.unchecked === children.length) state = `unchecked`;
|
|
31431
|
+
else state = `indeterminate`;
|
|
31432
|
+
states.set(node.item.key, state);
|
|
31433
|
+
if (stack.length > 0) {
|
|
31434
|
+
const parent = stack[stack.length - 1];
|
|
31435
|
+
if (state === `checked`) parent.checked++;
|
|
31436
|
+
else if (state === `unchecked`) parent.unchecked++;
|
|
31437
|
+
}
|
|
31438
|
+
}
|
|
31439
|
+
}
|
|
31440
|
+
return states;
|
|
31441
|
+
}
|
|
31442
|
+
function withKey(set, key) {
|
|
31443
|
+
if (set.has(key)) return set;
|
|
31444
|
+
const next = new Set(set);
|
|
31445
|
+
next.add(key);
|
|
31446
|
+
return next;
|
|
31447
|
+
}
|
|
31448
|
+
function withoutKey(set, key) {
|
|
31449
|
+
if (!set.has(key)) return set;
|
|
31450
|
+
const next = new Set(set);
|
|
31451
|
+
next.delete(key);
|
|
31452
|
+
return next;
|
|
31453
|
+
}
|
|
31454
|
+
/** Remove every key for which `pred` returns true. */
|
|
31455
|
+
function withoutKeys(set, pred) {
|
|
31456
|
+
let next;
|
|
31457
|
+
for (const key of set) if (pred(key)) {
|
|
31458
|
+
next ??= new Set(set);
|
|
31459
|
+
next.delete(key);
|
|
31460
|
+
}
|
|
31461
|
+
return next ?? set;
|
|
31462
|
+
}
|
|
31463
|
+
function treeStride(params) {
|
|
31464
|
+
return Math.max(1, params.rowHeight + params.gap);
|
|
31465
|
+
}
|
|
31466
|
+
/** Content-space top of the row box at `index`. */
|
|
31467
|
+
function treeRowTop(params, index) {
|
|
31468
|
+
return params.listOffset + index * treeStride(params) + params.gap;
|
|
31469
|
+
}
|
|
31470
|
+
function treeRowBottom(params, index) {
|
|
31471
|
+
return treeRowTop(params, index) + params.rowHeight;
|
|
31472
|
+
}
|
|
31473
|
+
/** The window of rows to render for a given scroll offset. Always `0 <= start <= end <= count`. */
|
|
31474
|
+
function computeTreeWindow(params, scrollTop, overscan = 5) {
|
|
31475
|
+
const { count } = params;
|
|
31476
|
+
if (count <= 0) return {
|
|
31477
|
+
start: 0,
|
|
31478
|
+
end: 0
|
|
31479
|
+
};
|
|
31480
|
+
const stride = treeStride(params);
|
|
31481
|
+
const top = Math.max(0, scrollTop - params.listOffset);
|
|
31482
|
+
const firstVisible = Math.floor(top / stride);
|
|
31483
|
+
const lastVisible = Math.ceil((top + params.viewportHeight) / stride);
|
|
31484
|
+
const start = Math.min(count, Math.max(0, firstVisible - overscan));
|
|
31485
|
+
return {
|
|
31486
|
+
start,
|
|
31487
|
+
end: Math.min(count, Math.max(start, lastVisible + overscan))
|
|
31488
|
+
};
|
|
31489
|
+
}
|
|
31490
|
+
/**
|
|
31491
|
+
* The scroll offset that brings row `index` fully into view with the least
|
|
31492
|
+
* movement, leaving `topInset` px at the top clear (the sticky-header overlay).
|
|
31493
|
+
* Returns `scrollTop` unchanged when the row is already visible.
|
|
31494
|
+
*/
|
|
31495
|
+
function treeScrollTopToReveal(params, index, scrollTop, topInset = 0) {
|
|
31496
|
+
if (index < 0 || index >= params.count) return scrollTop;
|
|
31497
|
+
const top = treeRowTop(params, index);
|
|
31498
|
+
const bottom = top + params.rowHeight;
|
|
31499
|
+
if (top < scrollTop + topInset) return index === 0 ? 0 : Math.max(0, top - topInset);
|
|
31500
|
+
if (bottom > scrollTop + params.viewportHeight) return Math.max(0, bottom - params.viewportHeight);
|
|
31501
|
+
return scrollTop;
|
|
31502
|
+
}
|
|
31503
|
+
/** Rows that fit one viewport (PageUp / PageDown step), at least 1. */
|
|
31504
|
+
function treePageSize(params) {
|
|
31505
|
+
return Math.max(1, Math.floor(params.viewportHeight / treeStride(params)) - 1);
|
|
31506
|
+
}
|
|
31507
|
+
/** Arithmetic `RowGeometryReader` over flat-row indices (the windowed counterpart of reading `offsetTop`). */
|
|
31508
|
+
function treeRowReader(params) {
|
|
31509
|
+
return {
|
|
31510
|
+
count: params.count,
|
|
31511
|
+
topOf: (i) => treeRowTop(params, i),
|
|
31512
|
+
bottomOf: (i) => treeRowBottom(params, i)
|
|
31513
|
+
};
|
|
31514
|
+
}
|
|
31515
|
+
//#endregion
|
|
31516
|
+
//#region src/tree/tree-row-geometry.ts
|
|
31517
|
+
/**
|
|
31518
|
+
* First index whose bottom is greater than `y` (binary search; rows are
|
|
31519
|
+
* monotonic). Returns `reader.count` when no row qualifies.
|
|
31520
|
+
*/
|
|
31521
|
+
function firstRowBelow(reader, y) {
|
|
31522
|
+
let lo = 0;
|
|
31523
|
+
let hi = reader.count;
|
|
31524
|
+
while (lo < hi) {
|
|
31525
|
+
const mid = lo + hi >>> 1;
|
|
31526
|
+
if (reader.bottomOf(mid) > y) hi = mid;
|
|
31527
|
+
else lo = mid + 1;
|
|
31528
|
+
}
|
|
31529
|
+
return lo;
|
|
31530
|
+
}
|
|
31531
|
+
/**
|
|
31532
|
+
* Inclusive index range of rows that overlap `[top, bottom]`, widened by
|
|
31533
|
+
* `margin` rows on each side. Returns `undefined` when there are no rows.
|
|
31534
|
+
*/
|
|
31535
|
+
function rowRangeOverlapping(reader, top, bottom, margin = 0) {
|
|
31536
|
+
if (reader.count === 0) return void 0;
|
|
31537
|
+
const firstHit = Math.min(firstRowBelow(reader, top), reader.count - 1);
|
|
31538
|
+
let lo = firstHit;
|
|
31539
|
+
let hi = reader.count;
|
|
31540
|
+
while (lo < hi) {
|
|
31541
|
+
const mid = lo + hi >>> 1;
|
|
31542
|
+
if (reader.topOf(mid) >= bottom) hi = mid;
|
|
31543
|
+
else lo = mid + 1;
|
|
31544
|
+
}
|
|
31545
|
+
const lastHit = Math.max(firstHit, lo - 1);
|
|
31546
|
+
return {
|
|
31547
|
+
first: Math.max(0, firstHit - margin),
|
|
31548
|
+
last: Math.min(reader.count - 1, lastHit + margin)
|
|
31549
|
+
};
|
|
31550
|
+
}
|
|
31551
|
+
//#endregion
|
|
30493
31552
|
//#region src/tree/tree-base.ts
|
|
30494
31553
|
/** Above this many rows entering / leaving at once, skip the animation entirely. */
|
|
30495
31554
|
const MAX_ANIMATED_ROWS = 50;
|
|
31555
|
+
const EMPTY_FLAT = {
|
|
31556
|
+
rows: [],
|
|
31557
|
+
indexByKey: /* @__PURE__ */ new Map()
|
|
31558
|
+
};
|
|
31559
|
+
/** Viewport height assumed until the container has been measured (or when it reads 0). */
|
|
31560
|
+
const FALLBACK_VIEWPORT_HEIGHT = 600;
|
|
30496
31561
|
let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
30497
31562
|
constructor(..._args) {
|
|
30498
31563
|
super(..._args);
|
|
@@ -30506,11 +31571,33 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30506
31571
|
this.dndExpandHoverMs = 500;
|
|
30507
31572
|
this.reorderable = true;
|
|
30508
31573
|
this.stickyHeaders = true;
|
|
31574
|
+
this.virtualize = `auto`;
|
|
31575
|
+
this.virtualizeThreshold = 200;
|
|
30509
31576
|
this.applyInternalDrop = true;
|
|
30510
31577
|
this._expandedKeys = /* @__PURE__ */ new Set();
|
|
31578
|
+
this.#structureVersion = 0;
|
|
31579
|
+
this._selectionVersion = 0;
|
|
31580
|
+
this._scrollTop = 0;
|
|
31581
|
+
this.#metrics = {
|
|
31582
|
+
rowHeight: 30,
|
|
31583
|
+
gap: 1,
|
|
31584
|
+
listOffset: 0
|
|
31585
|
+
};
|
|
31586
|
+
this.#viewportH = 0;
|
|
31587
|
+
this.#virtualScrollRaf = 0;
|
|
31588
|
+
this.#winStart = 0;
|
|
31589
|
+
this.#winEnd = 0;
|
|
31590
|
+
this.#scrollOnlyUpdate = false;
|
|
31591
|
+
this.#onVirtualScroll = () => {
|
|
31592
|
+
if (this.#virtualScrollRaf !== 0) return;
|
|
31593
|
+
this.#virtualScrollRaf = requestAnimationFrame(() => {
|
|
31594
|
+
this.#virtualScrollRaf = 0;
|
|
31595
|
+
this.#syncWindowToScroll();
|
|
31596
|
+
});
|
|
31597
|
+
};
|
|
30511
31598
|
this.tickled = new TickledController(this, {
|
|
30512
31599
|
getItems: () => {
|
|
30513
|
-
return this.#
|
|
31600
|
+
return this.#getFlat().rows.map(({ node }) => ({
|
|
30514
31601
|
id: node.item.key,
|
|
30515
31602
|
enabled: true
|
|
30516
31603
|
}));
|
|
@@ -30527,6 +31614,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30527
31614
|
}) });
|
|
30528
31615
|
this.#config = { indent: 20 };
|
|
30529
31616
|
this.#renderedKeys = [];
|
|
31617
|
+
this.#renderedRowKeys = [];
|
|
31618
|
+
this.#renderedRowIndex = /* @__PURE__ */ new Map();
|
|
30530
31619
|
this.#lastNodeByKey = /* @__PURE__ */ new Map();
|
|
30531
31620
|
this.#enteringKeys = /* @__PURE__ */ new Set();
|
|
30532
31621
|
this.#leaving = /* @__PURE__ */ new Map();
|
|
@@ -30550,9 +31639,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30550
31639
|
this.#stickyDirty = true;
|
|
30551
31640
|
this.#stickyRaf = 0;
|
|
30552
31641
|
this.#stickyScrollBound = false;
|
|
31642
|
+
this.#revealToken = 0;
|
|
30553
31643
|
this.#onKeyDown = (event) => {
|
|
30554
31644
|
if (this.search.isOpen) return;
|
|
30555
|
-
const nodes = this.#
|
|
31645
|
+
const nodes = this.#getFlat().rows;
|
|
30556
31646
|
if (nodes.length === 0) return;
|
|
30557
31647
|
if ((event.ctrlKey || event.metaKey) && event.key === `a`) {
|
|
30558
31648
|
event.preventDefault();
|
|
@@ -30564,29 +31654,41 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30564
31654
|
`ArrowUp`,
|
|
30565
31655
|
`ArrowLeft`,
|
|
30566
31656
|
`ArrowRight`,
|
|
31657
|
+
`Home`,
|
|
31658
|
+
`End`,
|
|
31659
|
+
`PageUp`,
|
|
31660
|
+
`PageDown`,
|
|
30567
31661
|
`Enter`,
|
|
30568
31662
|
` `
|
|
30569
31663
|
].includes(event.key)) this.tickled.setInputMode(`keyboard`);
|
|
30570
31664
|
const currentIdx = this.#getFocusedIndex();
|
|
30571
31665
|
switch (event.key) {
|
|
30572
|
-
case `ArrowDown`:
|
|
31666
|
+
case `ArrowDown`:
|
|
30573
31667
|
event.preventDefault();
|
|
30574
|
-
|
|
30575
|
-
|
|
30576
|
-
|
|
30577
|
-
|
|
30578
|
-
|
|
30579
|
-
|
|
31668
|
+
this.#navigateToIndex(nodes, currentIdx, Math.min(currentIdx + 1, nodes.length - 1), event.shiftKey);
|
|
31669
|
+
break;
|
|
31670
|
+
case `ArrowUp`:
|
|
31671
|
+
event.preventDefault();
|
|
31672
|
+
this.#navigateToIndex(nodes, currentIdx, Math.max(currentIdx - 1, 0), event.shiftKey);
|
|
31673
|
+
break;
|
|
31674
|
+
case `Home`:
|
|
31675
|
+
event.preventDefault();
|
|
31676
|
+
this.#navigateToIndex(nodes, currentIdx, 0, event.shiftKey);
|
|
31677
|
+
break;
|
|
31678
|
+
case `End`:
|
|
31679
|
+
event.preventDefault();
|
|
31680
|
+
this.#navigateToIndex(nodes, currentIdx, nodes.length - 1, event.shiftKey);
|
|
31681
|
+
break;
|
|
31682
|
+
case `PageDown`: {
|
|
31683
|
+
event.preventDefault();
|
|
31684
|
+
const page = this.#pageSize();
|
|
31685
|
+
this.#navigateToIndex(nodes, currentIdx, Math.min(currentIdx + page, nodes.length - 1), event.shiftKey);
|
|
30580
31686
|
break;
|
|
30581
31687
|
}
|
|
30582
|
-
case `
|
|
31688
|
+
case `PageUp`: {
|
|
30583
31689
|
event.preventDefault();
|
|
30584
|
-
const
|
|
30585
|
-
|
|
30586
|
-
this.#setFocusedIndex(prevIdx);
|
|
30587
|
-
if (event.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`)) this.#extendRangeSelection(nodes, currentIdx, prevIdx);
|
|
30588
|
-
else if (this.interactionMode === `vscode`) this.#anchor = void 0;
|
|
30589
|
-
this.requestUpdate();
|
|
31690
|
+
const page = this.#pageSize();
|
|
31691
|
+
this.#navigateToIndex(nodes, currentIdx, Math.max(currentIdx - page, 0), event.shiftKey);
|
|
30590
31692
|
break;
|
|
30591
31693
|
}
|
|
30592
31694
|
case `ArrowRight`: {
|
|
@@ -30603,7 +31705,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30603
31705
|
event.preventDefault();
|
|
30604
31706
|
const current = nodes[currentIdx];
|
|
30605
31707
|
if (current && this._expandedKeys.has(current.node.item.key)) {
|
|
30606
|
-
this._expandedKeys
|
|
31708
|
+
this.#setExpanded(withoutKey(this._expandedKeys, current.node.item.key));
|
|
30607
31709
|
this.dispatchEvent(new CustomEvent(`collapse`, {
|
|
30608
31710
|
detail: {
|
|
30609
31711
|
node: current.node,
|
|
@@ -30657,7 +31759,9 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30657
31759
|
if (!li) return;
|
|
30658
31760
|
const key = li.dataset.key;
|
|
30659
31761
|
if (!key) return;
|
|
30660
|
-
const
|
|
31762
|
+
const flat = this.#getFlat();
|
|
31763
|
+
const idx = flat.indexByKey.get(key);
|
|
31764
|
+
const node = idx === void 0 ? void 0 : flat.rows[idx];
|
|
30661
31765
|
if (!node) return;
|
|
30662
31766
|
const detail = {
|
|
30663
31767
|
node: node.node,
|
|
@@ -30678,6 +31782,206 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30678
31782
|
get selectedNodes() {
|
|
30679
31783
|
return this._selectedNodes;
|
|
30680
31784
|
}
|
|
31785
|
+
#setExpanded(next) {
|
|
31786
|
+
if (next === this._expandedKeys) return;
|
|
31787
|
+
this._expandedKeys = next;
|
|
31788
|
+
this.#flat = void 0;
|
|
31789
|
+
this.#stickyDirty = true;
|
|
31790
|
+
}
|
|
31791
|
+
#flat;
|
|
31792
|
+
#flatRoot;
|
|
31793
|
+
#flatFilter;
|
|
31794
|
+
#structureVersion;
|
|
31795
|
+
#treeIndexCache;
|
|
31796
|
+
#filterCache;
|
|
31797
|
+
/**
|
|
31798
|
+
* Call after editing `node.children` / `node.item` in place (outside
|
|
31799
|
+
* `TreeDataModel`). Drops the cached whole-tree indexes and re-renders.
|
|
31800
|
+
*/
|
|
31801
|
+
invalidateStructure() {
|
|
31802
|
+
this.#structureVersion++;
|
|
31803
|
+
this.#flat = void 0;
|
|
31804
|
+
this.requestUpdate();
|
|
31805
|
+
}
|
|
31806
|
+
/** Visible rows in display order (cached for the current update). */
|
|
31807
|
+
#getFlat() {
|
|
31808
|
+
if (!this.root) return EMPTY_FLAT;
|
|
31809
|
+
if (this.#flat && this.#flatRoot === this.root && this.#flatFilter === this.filterPredicate) return this.#flat;
|
|
31810
|
+
const filter = this.#getFilter();
|
|
31811
|
+
this.#flat = flattenTree(this.root, {
|
|
31812
|
+
isExpanded: (node, depth) => this._isExpanded(node, depth),
|
|
31813
|
+
isShown: filter ? (node) => filter.matches.has(node.item.key) : () => true
|
|
31814
|
+
});
|
|
31815
|
+
this.#flatRoot = this.root;
|
|
31816
|
+
this.#flatFilter = this.filterPredicate;
|
|
31817
|
+
return this.#flat;
|
|
31818
|
+
}
|
|
31819
|
+
/** Whole-loaded-tree index (parents, icon depths); cached on `(root, structureVersion)`. */
|
|
31820
|
+
#getTreeIndex() {
|
|
31821
|
+
const root = this.root;
|
|
31822
|
+
if (!root) return {
|
|
31823
|
+
parentByKey: /* @__PURE__ */ new Map(),
|
|
31824
|
+
depthsWithIcons: /* @__PURE__ */ new Set()
|
|
31825
|
+
};
|
|
31826
|
+
const cached = this.#treeIndexCache;
|
|
31827
|
+
if (cached && cached.root === root && cached.version === this.#structureVersion) return cached.index;
|
|
31828
|
+
const index = indexTree(root);
|
|
31829
|
+
this.#treeIndexCache = {
|
|
31830
|
+
root,
|
|
31831
|
+
version: this.#structureVersion,
|
|
31832
|
+
index
|
|
31833
|
+
};
|
|
31834
|
+
return index;
|
|
31835
|
+
}
|
|
31836
|
+
/** Row height / gap / list offset as last measured from CSS + layout. */
|
|
31837
|
+
#metrics;
|
|
31838
|
+
/** Last non-zero container height (a hidden dock tab reads 0). */
|
|
31839
|
+
#viewportH;
|
|
31840
|
+
#virtualScrollRaf;
|
|
31841
|
+
#virtualScrollContainer;
|
|
31842
|
+
#resizeObserver;
|
|
31843
|
+
#winStart;
|
|
31844
|
+
#winEnd;
|
|
31845
|
+
/** Set in `willUpdate` when only the scroll offset changed; consumed in `updated`. */
|
|
31846
|
+
#scrollOnlyUpdate;
|
|
31847
|
+
/** True when the current visible row count should be windowed. */
|
|
31848
|
+
#virtualActive() {
|
|
31849
|
+
if (this.virtualize === `off`) return false;
|
|
31850
|
+
if (this.virtualize === `on`) return true;
|
|
31851
|
+
return this.#getFlat().rows.length > this.virtualizeThreshold;
|
|
31852
|
+
}
|
|
31853
|
+
#layoutParams(count) {
|
|
31854
|
+
return {
|
|
31855
|
+
count,
|
|
31856
|
+
rowHeight: this.#metrics.rowHeight,
|
|
31857
|
+
gap: this.#metrics.gap,
|
|
31858
|
+
listOffset: this.#metrics.listOffset,
|
|
31859
|
+
viewportHeight: this.#viewportH > 0 ? this.#viewportH : FALLBACK_VIEWPORT_HEIGHT
|
|
31860
|
+
};
|
|
31861
|
+
}
|
|
31862
|
+
/**
|
|
31863
|
+
* Re-read the CSS metrics and viewport height. Returns true when anything
|
|
31864
|
+
* that changes row positions moved (the caller re-renders).
|
|
31865
|
+
*/
|
|
31866
|
+
#measureMetrics() {
|
|
31867
|
+
const list = this.renderRoot.querySelector(`ul.tree-list`);
|
|
31868
|
+
const sample = list?.querySelector(`li.item:not(.leaving)`);
|
|
31869
|
+
const measuredHeight = sample?.getBoundingClientRect().height ?? 0;
|
|
31870
|
+
const rowHeight = measuredHeight > 0 ? measuredHeight : this.#getItemHeight();
|
|
31871
|
+
const measuredGap = sample ? Number.parseFloat(getComputedStyle(sample).marginTop) : NaN;
|
|
31872
|
+
const parsedGap = Number.isNaN(measuredGap) ? Number.parseFloat(getComputedStyle(this).getPropertyValue(`--tree-item-gap`)) : measuredGap;
|
|
31873
|
+
const gap = Number.isNaN(parsedGap) ? 1 : parsedGap;
|
|
31874
|
+
let listOffset = this.#metrics.listOffset;
|
|
31875
|
+
if (list) {
|
|
31876
|
+
const padTop = Number.parseFloat(getComputedStyle(list).paddingTop);
|
|
31877
|
+
listOffset = list.offsetTop + (Number.isNaN(padTop) ? 0 : padTop);
|
|
31878
|
+
}
|
|
31879
|
+
const height = this.#container()?.clientHeight ?? 0;
|
|
31880
|
+
const heightChanged = height > 0 && height !== this.#viewportH;
|
|
31881
|
+
if (height > 0) this.#viewportH = height;
|
|
31882
|
+
const prev = this.#metrics;
|
|
31883
|
+
const changed = prev.rowHeight !== rowHeight || prev.gap !== gap || prev.listOffset !== listOffset;
|
|
31884
|
+
this.#metrics = {
|
|
31885
|
+
rowHeight,
|
|
31886
|
+
gap,
|
|
31887
|
+
listOffset
|
|
31888
|
+
};
|
|
31889
|
+
return changed || heightChanged;
|
|
31890
|
+
}
|
|
31891
|
+
/** The window for the current scroll offset. */
|
|
31892
|
+
#currentWindow(count) {
|
|
31893
|
+
return computeTreeWindow(this.#layoutParams(count), this._scrollTop);
|
|
31894
|
+
}
|
|
31895
|
+
/** Pull the container's real scroll offset into state (programmatic and clamped scrolls included). */
|
|
31896
|
+
#syncScrollTopFromContainer() {
|
|
31897
|
+
const top = this.#container()?.scrollTop;
|
|
31898
|
+
if (top !== void 0 && top !== this._scrollTop) this._scrollTop = top;
|
|
31899
|
+
}
|
|
31900
|
+
#onVirtualScroll;
|
|
31901
|
+
/** Re-render only when the scroll has moved the window; scrolling inside it is free. */
|
|
31902
|
+
#syncWindowToScroll() {
|
|
31903
|
+
const container = this.#container();
|
|
31904
|
+
if (!container || !this.root || !this.#virtualActive()) return;
|
|
31905
|
+
const top = container.scrollTop;
|
|
31906
|
+
if (top === this._scrollTop) return;
|
|
31907
|
+
const next = computeTreeWindow(this.#layoutParams(this.#getFlat().rows.length), top);
|
|
31908
|
+
if (next.start !== this.#winStart || next.end !== this.#winEnd) this._scrollTop = top;
|
|
31909
|
+
}
|
|
31910
|
+
/** Bind the window's scroll listener and resize observer to the container once it exists. */
|
|
31911
|
+
#syncVirtualObservers() {
|
|
31912
|
+
const container = this.#container();
|
|
31913
|
+
if (container === this.#virtualScrollContainer) return;
|
|
31914
|
+
this.#virtualScrollContainer?.removeEventListener(`scroll`, this.#onVirtualScroll);
|
|
31915
|
+
this.#resizeObserver?.disconnect();
|
|
31916
|
+
this.#resizeObserver = void 0;
|
|
31917
|
+
this.#virtualScrollContainer = container;
|
|
31918
|
+
if (!container) return;
|
|
31919
|
+
container.addEventListener(`scroll`, this.#onVirtualScroll, { passive: true });
|
|
31920
|
+
if (typeof ResizeObserver !== `undefined`) {
|
|
31921
|
+
this.#resizeObserver = new ResizeObserver((entries) => {
|
|
31922
|
+
const height = entries.at(-1)?.contentRect.height ?? 0;
|
|
31923
|
+
if (height > 0 && height !== this.#viewportH && this.#virtualActive()) this.requestUpdate();
|
|
31924
|
+
});
|
|
31925
|
+
this.#resizeObserver.observe(container);
|
|
31926
|
+
}
|
|
31927
|
+
}
|
|
31928
|
+
#teardownVirtualObservers() {
|
|
31929
|
+
this.#virtualScrollContainer?.removeEventListener(`scroll`, this.#onVirtualScroll);
|
|
31930
|
+
this.#virtualScrollContainer = void 0;
|
|
31931
|
+
this.#resizeObserver?.disconnect();
|
|
31932
|
+
this.#resizeObserver = void 0;
|
|
31933
|
+
if (this.#virtualScrollRaf !== 0) {
|
|
31934
|
+
cancelAnimationFrame(this.#virtualScrollRaf);
|
|
31935
|
+
this.#virtualScrollRaf = 0;
|
|
31936
|
+
}
|
|
31937
|
+
}
|
|
31938
|
+
/**
|
|
31939
|
+
* Scroll flat row `index` into view (below the sticky overlay). Writes the
|
|
31940
|
+
* container's `scrollTop` and `_scrollTop` together, so the very next render
|
|
31941
|
+
* already contains the target row — no frame where it is missing.
|
|
31942
|
+
*/
|
|
31943
|
+
#revealIndex(index) {
|
|
31944
|
+
const container = this.#container();
|
|
31945
|
+
if (!container) return;
|
|
31946
|
+
const params = this.#layoutParams(this.#getFlat().rows.length);
|
|
31947
|
+
const inset = this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0;
|
|
31948
|
+
const next = treeScrollTopToReveal(params, index, container.scrollTop, inset);
|
|
31949
|
+
if (next === container.scrollTop && next === this._scrollTop) return;
|
|
31950
|
+
container.scrollTop = next;
|
|
31951
|
+
this._scrollTop = next;
|
|
31952
|
+
}
|
|
31953
|
+
/** Filter match sets, or `undefined` when no filter is active. */
|
|
31954
|
+
#getFilter() {
|
|
31955
|
+
const predicate = this.filterPredicate;
|
|
31956
|
+
const root = this.root;
|
|
31957
|
+
if (!predicate || !root) return void 0;
|
|
31958
|
+
const cached = this.#filterCache;
|
|
31959
|
+
if (cached && cached.predicate === predicate && cached.root === root && cached.version === this.#structureVersion) return cached;
|
|
31960
|
+
const matches = computeFilterMatches(root, predicate);
|
|
31961
|
+
const { parentByKey } = this.#getTreeIndex();
|
|
31962
|
+
const autoExpanded = /* @__PURE__ */ new Set();
|
|
31963
|
+
for (const key of matches) {
|
|
31964
|
+
const parent = parentByKey.get(key);
|
|
31965
|
+
if (parent !== void 0) autoExpanded.add(parent);
|
|
31966
|
+
}
|
|
31967
|
+
const next = {
|
|
31968
|
+
predicate,
|
|
31969
|
+
root,
|
|
31970
|
+
version: this.#structureVersion,
|
|
31971
|
+
matches,
|
|
31972
|
+
autoExpanded
|
|
31973
|
+
};
|
|
31974
|
+
this.#filterCache = next;
|
|
31975
|
+
return next;
|
|
31976
|
+
}
|
|
31977
|
+
/** Structure version for cache keys in subclasses (bumped on root / model change and lazy loads). */
|
|
31978
|
+
get _structureVersion() {
|
|
31979
|
+
return this.#structureVersion;
|
|
31980
|
+
}
|
|
31981
|
+
/** Depths at which any loaded node carries an icon (stable across expansion). */
|
|
31982
|
+
get _depthsWithIcons() {
|
|
31983
|
+
return this.#getTreeIndex().depthsWithIcons;
|
|
31984
|
+
}
|
|
30681
31985
|
#unregisterSearchCommands;
|
|
30682
31986
|
#effectiveSearchModes() {
|
|
30683
31987
|
const raw = this.searchModes?.trim();
|
|
@@ -30698,6 +32002,9 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30698
32002
|
#ownedRegistry;
|
|
30699
32003
|
#config;
|
|
30700
32004
|
#renderedKeys;
|
|
32005
|
+
/** Keys in the exact DOM order of the last render (ghost rows woven in). */
|
|
32006
|
+
#renderedRowKeys;
|
|
32007
|
+
#renderedRowIndex;
|
|
30701
32008
|
#lastNodeByKey;
|
|
30702
32009
|
#enteringKeys;
|
|
30703
32010
|
#leaving;
|
|
@@ -30732,73 +32039,65 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30732
32039
|
}
|
|
30733
32040
|
}
|
|
30734
32041
|
#onStickyScroll;
|
|
30735
|
-
/**
|
|
30736
|
-
|
|
30737
|
-
|
|
32042
|
+
/**
|
|
32043
|
+
* Row geometry over the rendered `<li>` elements (ghost rows included), in
|
|
32044
|
+
* container content coordinates. `#container` is `position: relative`, so
|
|
32045
|
+
* `offsetTop` is scroll-independent. Reads only — no layout writes.
|
|
32046
|
+
*/
|
|
32047
|
+
#rowReader(rows) {
|
|
32048
|
+
return {
|
|
32049
|
+
count: rows.length,
|
|
32050
|
+
topOf: (i) => rows[i].offsetTop,
|
|
32051
|
+
bottomOf: (i) => rows[i].offsetTop + rows[i].offsetHeight
|
|
32052
|
+
};
|
|
30738
32053
|
}
|
|
30739
32054
|
/**
|
|
30740
32055
|
* Recompute which ancestors stick to the top: the ancestors of the first
|
|
30741
32056
|
* row whose bottom clears the container top, minus any ancestor that is
|
|
30742
32057
|
* itself still visible below the overlay. Synchronous — reads current
|
|
30743
|
-
* geometry, renders nothing, expands nothing.
|
|
32058
|
+
* geometry, renders nothing, expands nothing. O(log N) offset reads + O(depth).
|
|
30744
32059
|
*/
|
|
30745
32060
|
#updateStickyHeaders() {
|
|
30746
32061
|
this.#stickyDirty = false;
|
|
30747
32062
|
const empty = /* @__PURE__ */ new Map();
|
|
30748
|
-
|
|
30749
|
-
|
|
30750
|
-
return;
|
|
30751
|
-
}
|
|
32063
|
+
const clear = () => this.#setStickyKeys([], empty);
|
|
32064
|
+
if (!this.stickyHeaders || !this.root) return clear();
|
|
30752
32065
|
const container = this.#container();
|
|
30753
|
-
|
|
30754
|
-
|
|
30755
|
-
|
|
30756
|
-
|
|
30757
|
-
const
|
|
30758
|
-
|
|
30759
|
-
|
|
30760
|
-
|
|
30761
|
-
|
|
30762
|
-
const
|
|
30763
|
-
const
|
|
30764
|
-
|
|
30765
|
-
|
|
30766
|
-
|
|
30767
|
-
|
|
30768
|
-
|
|
30769
|
-
walk(this.root);
|
|
30770
|
-
const containerTop = container.getBoundingClientRect().top;
|
|
30771
|
-
const visibleThreshold = containerTop + (this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0);
|
|
30772
|
-
let topKey;
|
|
30773
|
-
for (const { node } of nodes) {
|
|
30774
|
-
const li = this.#stickyRowEl(node.item.key);
|
|
30775
|
-
if (!li || li.classList.contains(`leaving`)) continue;
|
|
30776
|
-
if (li.getBoundingClientRect().bottom > containerTop) {
|
|
30777
|
-
topKey = node.item.key;
|
|
30778
|
-
break;
|
|
30779
|
-
}
|
|
30780
|
-
}
|
|
30781
|
-
if (topKey === void 0) {
|
|
30782
|
-
this.#setStickyKeys([], empty);
|
|
30783
|
-
return;
|
|
30784
|
-
}
|
|
30785
|
-
const rootKey = this.root.item.key;
|
|
32066
|
+
const list = this.renderRoot.querySelector(`ul.tree-list`);
|
|
32067
|
+
if (!container || !list) return clear();
|
|
32068
|
+
const flat = this.#getFlat();
|
|
32069
|
+
const virtual = this.#virtualActive();
|
|
32070
|
+
const rows = list.children;
|
|
32071
|
+
const keys = this.#renderedRowKeys;
|
|
32072
|
+
if (flat.rows.length === 0) return clear();
|
|
32073
|
+
if (!virtual && (rows.length === 0 || rows.length !== keys.length)) return clear();
|
|
32074
|
+
const reader = virtual ? treeRowReader(this.#layoutParams(flat.rows.length)) : this.#rowReader(rows);
|
|
32075
|
+
const scrollTop = container.scrollTop;
|
|
32076
|
+
const visibleThreshold = scrollTop + (this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0);
|
|
32077
|
+
let topIdx = firstRowBelow(reader, scrollTop);
|
|
32078
|
+
if (!virtual) while (topIdx < rows.length && rows[topIdx].classList.contains(`leaving`)) topIdx++;
|
|
32079
|
+
if (topIdx >= reader.count) return clear();
|
|
32080
|
+
const topFlat = virtual ? topIdx : flat.indexByKey.get(keys[topIdx]);
|
|
32081
|
+
if (topFlat === void 0) return clear();
|
|
30786
32082
|
const chain = [];
|
|
30787
|
-
let cursor =
|
|
30788
|
-
while (cursor !== void 0
|
|
30789
|
-
|
|
30790
|
-
|
|
32083
|
+
let cursor = flat.rows[topFlat].parentKey;
|
|
32084
|
+
while (cursor !== void 0) {
|
|
32085
|
+
const idx = flat.indexByKey.get(cursor);
|
|
32086
|
+
if (idx === void 0) break;
|
|
32087
|
+
chain.unshift(flat.rows[idx]);
|
|
32088
|
+
cursor = flat.rows[idx].parentKey;
|
|
30791
32089
|
}
|
|
30792
|
-
const byKey = new Map(nodes.map((entry) => [entry.node.item.key, entry]));
|
|
30793
32090
|
const entries = /* @__PURE__ */ new Map();
|
|
30794
32091
|
const stuck = [];
|
|
30795
|
-
for (const
|
|
30796
|
-
const
|
|
30797
|
-
|
|
30798
|
-
|
|
30799
|
-
if (!entry) continue;
|
|
32092
|
+
for (const row of chain) {
|
|
32093
|
+
const key = row.node.item.key;
|
|
32094
|
+
const domIdx = virtual ? flat.indexByKey.get(key) : this.#renderedRowIndex.get(key);
|
|
32095
|
+
if (domIdx === void 0 || reader.bottomOf(domIdx) > visibleThreshold) continue;
|
|
30800
32096
|
stuck.push(key);
|
|
30801
|
-
entries.set(key,
|
|
32097
|
+
entries.set(key, {
|
|
32098
|
+
node: row.node,
|
|
32099
|
+
depth: row.depth
|
|
32100
|
+
});
|
|
30802
32101
|
}
|
|
30803
32102
|
this.#setStickyKeys(stuck, entries);
|
|
30804
32103
|
}
|
|
@@ -30811,12 +32110,14 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30811
32110
|
#renderStickyHeaders() {
|
|
30812
32111
|
if (!this.stickyHeaders || this._stickyKeys.length === 0) return nothing;
|
|
30813
32112
|
return html`
|
|
30814
|
-
<div class="sticky-
|
|
30815
|
-
|
|
32113
|
+
<div class="sticky-anchor">
|
|
32114
|
+
<div class="sticky-headers" part="sticky-headers">
|
|
32115
|
+
${this._stickyKeys.map((key) => {
|
|
30816
32116
|
const entry = this.#stickyEntries.get(key);
|
|
30817
32117
|
if (!entry) return nothing;
|
|
30818
32118
|
return this.#renderStickyHeader(entry.node, entry.depth);
|
|
30819
32119
|
})}
|
|
32120
|
+
</div>
|
|
30820
32121
|
</div>
|
|
30821
32122
|
`;
|
|
30822
32123
|
}
|
|
@@ -30888,15 +32189,44 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30888
32189
|
}
|
|
30889
32190
|
willUpdate(changedProps) {
|
|
30890
32191
|
super.willUpdate(changedProps);
|
|
30891
|
-
this.#
|
|
32192
|
+
const scrollOnly = this.#flat !== void 0 && changedProps.size === 1 && changedProps.has(`_scrollTop`) && this.#virtualActive();
|
|
32193
|
+
this.#scrollOnlyUpdate = scrollOnly;
|
|
32194
|
+
if (scrollOnly) {
|
|
32195
|
+
this.#syncScrollTopFromContainer();
|
|
32196
|
+
return;
|
|
32197
|
+
}
|
|
32198
|
+
this.#flat = void 0;
|
|
32199
|
+
if (changedProps.has(`root`) || changedProps.has(`model`)) this.#structureVersion++;
|
|
32200
|
+
this.#diffVisibleNodes(changedProps.has(`filterPredicate`));
|
|
32201
|
+
const virtual = this.#virtualActive();
|
|
32202
|
+
this.toggleAttribute(`data-virtual`, virtual);
|
|
32203
|
+
if (virtual) {
|
|
32204
|
+
this.#measureMetrics();
|
|
32205
|
+
this.#syncScrollTopFromContainer();
|
|
32206
|
+
}
|
|
30892
32207
|
if (changedProps.has(`root`) || changedProps.has(`model`) || changedProps.has(`filterPredicate`) || changedProps.has(`stickyHeaders`)) this.#stickyDirty = true;
|
|
30893
32208
|
if (this.#stickyDirty) this.#updateStickyHeaders();
|
|
30894
32209
|
}
|
|
30895
|
-
|
|
30896
|
-
|
|
32210
|
+
/**
|
|
32211
|
+
* `filterChanged`: typing in a filter adds / removes whole rows from the DOM.
|
|
32212
|
+
* That is not an expand / collapse, so skip the enter / leave animations
|
|
32213
|
+
* (and any ghosts) for this update; `#renderedKeys` is still refreshed.
|
|
32214
|
+
*/
|
|
32215
|
+
#diffVisibleNodes(filterChanged) {
|
|
32216
|
+
const nodes = this.#getFlat().rows;
|
|
32217
|
+
if (this.#virtualActive()) {
|
|
32218
|
+
this.#enteringKeys.clear();
|
|
32219
|
+
this.#cancelAllLeavingAnimations();
|
|
32220
|
+
this.#leaving.clear();
|
|
32221
|
+
this.#renderedKeys = [];
|
|
32222
|
+
return;
|
|
32223
|
+
}
|
|
30897
32224
|
const nowKeys = new Set(nodes.map((n) => n.node.item.key));
|
|
30898
32225
|
this.#enteringKeys.clear();
|
|
30899
|
-
if (
|
|
32226
|
+
if (filterChanged) {
|
|
32227
|
+
this.#cancelAllLeavingAnimations();
|
|
32228
|
+
this.#leaving.clear();
|
|
32229
|
+
} else if (this.#treeAnimReady && this.#renderedKeys.length > 0) {
|
|
30900
32230
|
const prevSet = new Set(this.#renderedKeys);
|
|
30901
32231
|
for (const { node } of nodes) if (!prevSet.has(node.item.key)) this.#enteringKeys.add(node.item.key);
|
|
30902
32232
|
if (this.#enteringKeys.size > MAX_ANIMATED_ROWS) this.#enteringKeys.clear();
|
|
@@ -30919,30 +32249,12 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30919
32249
|
return this.#leaving.has(node.item.key);
|
|
30920
32250
|
}
|
|
30921
32251
|
/**
|
|
30922
|
-
*
|
|
30923
|
-
*
|
|
30924
|
-
|
|
30925
|
-
|
|
30926
|
-
|
|
30927
|
-
|
|
30928
|
-
/** Splice the fading ghost rows back into the visible list near where they were. */
|
|
30929
|
-
#weaveLeaving(nodes) {
|
|
30930
|
-
if (this.#leaving.size === 0) return nodes;
|
|
30931
|
-
const result = [...nodes];
|
|
30932
|
-
const indexOfKey = (key) => result.findIndex((e) => e.node.item.key === key);
|
|
30933
|
-
for (const [key, entry] of this.#leaving) {
|
|
30934
|
-
const prevIdx = this.#renderedKeys.indexOf(key);
|
|
30935
|
-
let insertAt = result.length;
|
|
30936
|
-
for (let j = prevIdx + 1; j < this.#renderedKeys.length; j++) {
|
|
30937
|
-
const at = indexOfKey(this.#renderedKeys[j]);
|
|
30938
|
-
if (at !== -1) {
|
|
30939
|
-
insertAt = at;
|
|
30940
|
-
break;
|
|
30941
|
-
}
|
|
30942
|
-
}
|
|
30943
|
-
result.splice(insertAt, 0, entry);
|
|
30944
|
-
}
|
|
30945
|
-
return result;
|
|
32252
|
+
* Kept for subclass compatibility. Filtered-out rows are no longer rendered
|
|
32253
|
+
* at all (they are omitted from the flat list), so this is `false` for every
|
|
32254
|
+
* rendered row.
|
|
32255
|
+
*/
|
|
32256
|
+
_isFilteredOut(_node, _depth) {
|
|
32257
|
+
return false;
|
|
30946
32258
|
}
|
|
30947
32259
|
#runItemAnimations() {
|
|
30948
32260
|
if (this.#enteringKeys.size > 0) {
|
|
@@ -30982,7 +32294,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30982
32294
|
});
|
|
30983
32295
|
});
|
|
30984
32296
|
}
|
|
30985
|
-
const visible = this
|
|
32297
|
+
const visible = this.#getFlat().rows;
|
|
30986
32298
|
this.#renderedKeys = visible.map((n) => n.node.item.key);
|
|
30987
32299
|
this.#lastNodeByKey.clear();
|
|
30988
32300
|
for (const entry of visible) this.#lastNodeByKey.set(entry.node.item.key, entry);
|
|
@@ -30990,10 +32302,16 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30990
32302
|
}
|
|
30991
32303
|
updated(changedProps) {
|
|
30992
32304
|
this.#rowsCache = void 0;
|
|
32305
|
+
this.#syncVirtualObservers();
|
|
32306
|
+
if (this.#scrollOnlyUpdate) {
|
|
32307
|
+
this.#scrollOnlyUpdate = false;
|
|
32308
|
+
this.#repaintSearchHighlights();
|
|
32309
|
+
return;
|
|
32310
|
+
}
|
|
30993
32311
|
this.#syncStickyScroll();
|
|
30994
32312
|
if (changedProps.has(`root`) && this.root) {
|
|
30995
32313
|
this.#abortPending();
|
|
30996
|
-
this
|
|
32314
|
+
this.#setExpanded(/* @__PURE__ */ new Set());
|
|
30997
32315
|
this.#clearSelectionInternal();
|
|
30998
32316
|
this.tickled.clearTickled();
|
|
30999
32317
|
this._focusedDepth = 0;
|
|
@@ -31013,8 +32331,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31013
32331
|
this.requestUpdate();
|
|
31014
32332
|
} else if (event.type === `invalidate`) {
|
|
31015
32333
|
if (this._expandedKeys.has(event.key)) {
|
|
31016
|
-
this._expandedKeys
|
|
31017
|
-
this.#stickyDirty = true;
|
|
32334
|
+
this.#setExpanded(withoutKey(this._expandedKeys, event.key));
|
|
31018
32335
|
this.requestUpdate();
|
|
31019
32336
|
}
|
|
31020
32337
|
}
|
|
@@ -31028,7 +32345,16 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31028
32345
|
if (changedProps.has(`selectedNode`) && this.selectedNode) {
|
|
31029
32346
|
if (changedProps.get(`selectedNode`) !== this.selectedNode) this.tickled.setTickled(this.selectedNode.item.key);
|
|
31030
32347
|
}
|
|
31031
|
-
this.#
|
|
32348
|
+
if (this.#virtualActive()) {
|
|
32349
|
+
this.#renderedKeys = [];
|
|
32350
|
+
this.#lastNodeByKey.clear();
|
|
32351
|
+
if (this.#measureMetrics()) queueMicrotask(() => this.requestUpdate());
|
|
32352
|
+
this.#repaintSearchHighlights();
|
|
32353
|
+
} else this.#runItemAnimations();
|
|
32354
|
+
}
|
|
32355
|
+
/** Highlights are painted on rendered labels, so they move with the window. */
|
|
32356
|
+
#repaintSearchHighlights() {
|
|
32357
|
+
if (this.search.isOpen) this.search.repaintHighlights();
|
|
31032
32358
|
}
|
|
31033
32359
|
connectedCallback() {
|
|
31034
32360
|
super.connectedCallback();
|
|
@@ -31060,8 +32386,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31060
32386
|
return target.closest(`ul.tree-list li[data-key]`)?.getAttribute(`data-key`) ?? void 0;
|
|
31061
32387
|
},
|
|
31062
32388
|
prepareSelection: (originKey) => {
|
|
31063
|
-
const
|
|
31064
|
-
const
|
|
32389
|
+
const flat = this.#getFlat();
|
|
32390
|
+
const allNodes = flat.rows;
|
|
32391
|
+
const originIdx = flat.indexByKey.get(originKey);
|
|
32392
|
+
const originEntry = originIdx === void 0 ? void 0 : allNodes[originIdx];
|
|
31065
32393
|
if (!originEntry) return void 0;
|
|
31066
32394
|
const selectedNodes = new Set(this._selectedNodes);
|
|
31067
32395
|
if (!selectedNodes.has(originEntry.node)) selectedNodes.clear();
|
|
@@ -31150,6 +32478,18 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31150
32478
|
getDndMode: () => this.itemDnd,
|
|
31151
32479
|
getExpandHoverMs: () => this.dndExpandHoverMs,
|
|
31152
32480
|
getVisibleRows: () => this.#getVisibleRows(),
|
|
32481
|
+
getRowInfo: (key) => {
|
|
32482
|
+
const flat = this.#getFlat();
|
|
32483
|
+
const idx = flat.indexByKey.get(key);
|
|
32484
|
+
if (idx === void 0) return void 0;
|
|
32485
|
+
const { node, depth, parentKey } = flat.rows[idx];
|
|
32486
|
+
return {
|
|
32487
|
+
node,
|
|
32488
|
+
depth,
|
|
32489
|
+
parentKey
|
|
32490
|
+
};
|
|
32491
|
+
},
|
|
32492
|
+
getChildren: (parentKey) => this.#childrenOfKey(parentKey),
|
|
31153
32493
|
getIndentPx: () => this.#getIndentPx(),
|
|
31154
32494
|
getScrollContainer: () => this.#container(),
|
|
31155
32495
|
isSelfOrDescendant: (ancestorKey, key) => this.#isSelfOrDescendant(ancestorKey, key),
|
|
@@ -31222,44 +32562,121 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31222
32562
|
const parsed = Number.parseInt(getComputedStyle(this).getPropertyValue(`--tree-item-height`), 10);
|
|
31223
32563
|
return Number.isNaN(parsed) ? 30 : parsed;
|
|
31224
32564
|
}
|
|
31225
|
-
/**
|
|
31226
|
-
#
|
|
31227
|
-
if (
|
|
31228
|
-
const
|
|
31229
|
-
|
|
31230
|
-
|
|
31231
|
-
|
|
32565
|
+
/** Children of `parentKey` (the root's children when undefined), straight from the tree. */
|
|
32566
|
+
#childrenOfKey(parentKey) {
|
|
32567
|
+
if (parentKey === void 0) return this.root?.children ?? [];
|
|
32568
|
+
const flat = this.#getFlat();
|
|
32569
|
+
const idx = flat.indexByKey.get(parentKey);
|
|
32570
|
+
return idx === void 0 ? [] : flat.rows[idx].node.children ?? [];
|
|
32571
|
+
}
|
|
32572
|
+
/**
|
|
32573
|
+
* Windowed counterpart of the measured rows: rects come from the layout
|
|
32574
|
+
* arithmetic and one rendered row's horizontal extent, so nothing is looked
|
|
32575
|
+
* up per row and rows outside the window still resolve.
|
|
32576
|
+
*/
|
|
32577
|
+
#collectVirtualDropRows(container, list, out) {
|
|
32578
|
+
const flat = this.#getFlat();
|
|
32579
|
+
const params = this.#layoutParams(flat.rows.length);
|
|
32580
|
+
const range = rowRangeOverlapping(treeRowReader(params), container.scrollTop, container.scrollTop + container.clientHeight, 1);
|
|
32581
|
+
if (!range) return;
|
|
32582
|
+
const containerRect = container.getBoundingClientRect();
|
|
32583
|
+
const sample = list.querySelector(`li.item`)?.getBoundingClientRect() ?? containerRect;
|
|
32584
|
+
for (let i = range.first; i <= range.last; i++) {
|
|
32585
|
+
const { node, depth } = flat.rows[i];
|
|
32586
|
+
const top = containerRect.top - container.scrollTop + treeRowTop(params, i);
|
|
32587
|
+
const rect = {
|
|
32588
|
+
x: sample.left,
|
|
32589
|
+
y: top,
|
|
32590
|
+
left: sample.left,
|
|
32591
|
+
right: sample.left + sample.width,
|
|
32592
|
+
top,
|
|
32593
|
+
bottom: top + params.rowHeight,
|
|
32594
|
+
width: sample.width,
|
|
32595
|
+
height: params.rowHeight,
|
|
32596
|
+
toJSON: () => ({})
|
|
32597
|
+
};
|
|
32598
|
+
out.push({
|
|
31232
32599
|
key: node.item.key,
|
|
31233
32600
|
node,
|
|
31234
32601
|
depth,
|
|
31235
|
-
|
|
32602
|
+
index: i,
|
|
32603
|
+
rect
|
|
31236
32604
|
});
|
|
31237
32605
|
}
|
|
32606
|
+
}
|
|
32607
|
+
/**
|
|
32608
|
+
* Measured rows for the part of the list that intersects the scroll
|
|
32609
|
+
* container (plus one row of margin each side), cached for the duration of a
|
|
32610
|
+
* drag and invalidated on scroll / update. Each row carries its flat index;
|
|
32611
|
+
* structural lookups (parents, siblings) come from `getRowInfo` / `getChildren`.
|
|
32612
|
+
*/
|
|
32613
|
+
#getVisibleRows() {
|
|
32614
|
+
if (this.#rowsCache) return this.#rowsCache;
|
|
32615
|
+
const rows = [];
|
|
32616
|
+
const container = this.#container();
|
|
32617
|
+
const list = this.renderRoot.querySelector(`ul.tree-list`);
|
|
32618
|
+
const keys = this.#renderedRowKeys;
|
|
32619
|
+
if (container && list && this.#virtualActive()) this.#collectVirtualDropRows(container, list, rows);
|
|
32620
|
+
else if (container && list && list.children.length === keys.length) {
|
|
32621
|
+
const flat = this.#getFlat();
|
|
32622
|
+
const items = list.children;
|
|
32623
|
+
const range = rowRangeOverlapping(this.#rowReader(items), container.scrollTop, container.scrollTop + container.clientHeight, 1);
|
|
32624
|
+
if (range) for (let i = range.first; i <= range.last; i++) {
|
|
32625
|
+
const li = items[i];
|
|
32626
|
+
if (li.classList.contains(`leaving`)) continue;
|
|
32627
|
+
const flatIdx = flat.indexByKey.get(keys[i]);
|
|
32628
|
+
if (flatIdx === void 0) continue;
|
|
32629
|
+
const { node, depth } = flat.rows[flatIdx];
|
|
32630
|
+
rows.push({
|
|
32631
|
+
key: node.item.key,
|
|
32632
|
+
node,
|
|
32633
|
+
depth,
|
|
32634
|
+
index: flatIdx,
|
|
32635
|
+
rect: li.getBoundingClientRect()
|
|
32636
|
+
});
|
|
32637
|
+
}
|
|
32638
|
+
}
|
|
31238
32639
|
this.#rowsCache = rows;
|
|
31239
32640
|
return rows;
|
|
31240
32641
|
}
|
|
31241
32642
|
/** Route spring-load through the real expand path so lazy children load and `expand` fires with the right depth. */
|
|
31242
32643
|
#springLoadExpand(key) {
|
|
31243
32644
|
if (this._expandedKeys.has(key)) return;
|
|
31244
|
-
const
|
|
32645
|
+
const flat = this.#getFlat();
|
|
32646
|
+
const idx = flat.indexByKey.get(key);
|
|
32647
|
+
const entry = idx === void 0 ? void 0 : flat.rows[idx];
|
|
31245
32648
|
if (!entry || entry.node.item.isLeaf === true) return;
|
|
31246
32649
|
this.#toggleExpand(entry.node, entry.depth);
|
|
31247
32650
|
this.#rowsCache = void 0;
|
|
31248
32651
|
this.requestUpdate();
|
|
31249
32652
|
}
|
|
32653
|
+
/** O(depth): walk up the whole-tree parent map. */
|
|
31250
32654
|
#isSelfOrDescendant(ancestorKey, key) {
|
|
31251
32655
|
if (ancestorKey === key) return true;
|
|
31252
|
-
|
|
31253
|
-
let
|
|
31254
|
-
while (
|
|
31255
|
-
if (
|
|
31256
|
-
|
|
31257
|
-
parent = this.#findParentByKey(node.item.key);
|
|
32656
|
+
const { parentByKey } = this.#getTreeIndex();
|
|
32657
|
+
let cursor = parentByKey.get(key);
|
|
32658
|
+
while (cursor !== void 0) {
|
|
32659
|
+
if (cursor === ancestorKey) return true;
|
|
32660
|
+
cursor = parentByKey.get(cursor);
|
|
31258
32661
|
}
|
|
31259
32662
|
return false;
|
|
31260
32663
|
}
|
|
31261
32664
|
#findParentByKey(key) {
|
|
31262
|
-
|
|
32665
|
+
const parentKey = this.#getTreeIndex().parentByKey.get(key);
|
|
32666
|
+
if (parentKey === void 0 || parentKey === this.root?.item.key) return void 0;
|
|
32667
|
+
return this.#nodeByKey(parentKey);
|
|
32668
|
+
}
|
|
32669
|
+
/** Node for a key anywhere in the loaded tree (via the flat list when visible). */
|
|
32670
|
+
#nodeByKey(key) {
|
|
32671
|
+
const flat = this.#getFlat();
|
|
32672
|
+
const idx = flat.indexByKey.get(key);
|
|
32673
|
+
if (idx !== void 0) return flat.rows[idx].node;
|
|
32674
|
+
const { parentByKey } = this.#getTreeIndex();
|
|
32675
|
+
const path = [];
|
|
32676
|
+
for (let cursor = key; cursor !== void 0 && cursor !== this.root?.item.key; cursor = parentByKey.get(cursor)) path.unshift(cursor);
|
|
32677
|
+
let node = this.root;
|
|
32678
|
+
for (const k of path) node = node?.children?.find((c) => c.item.key === k);
|
|
32679
|
+
return node;
|
|
31263
32680
|
}
|
|
31264
32681
|
/**
|
|
31265
32682
|
* Decide whether a DataTransfer is acceptable and resolve the dragged set.
|
|
@@ -31283,7 +32700,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31283
32700
|
return {
|
|
31284
32701
|
source: `internal`,
|
|
31285
32702
|
keys,
|
|
31286
|
-
nodes: keys.map((k) => this.#
|
|
32703
|
+
nodes: keys.map((k) => this.#nodeByKey(k)).filter((n) => n !== void 0)
|
|
31287
32704
|
};
|
|
31288
32705
|
}
|
|
31289
32706
|
if (phase === `drop`) try {
|
|
@@ -31327,7 +32744,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31327
32744
|
}
|
|
31328
32745
|
#anyRowDraggable() {
|
|
31329
32746
|
if (this.itemDnd === `none`) return false;
|
|
31330
|
-
const allNodes = this.#
|
|
32747
|
+
const allNodes = this.#getFlat().rows;
|
|
31331
32748
|
if (allNodes.length === 0) return false;
|
|
31332
32749
|
if (!this.canDrag) return true;
|
|
31333
32750
|
return allNodes.some(({ node }) => this.canDrag({
|
|
@@ -31349,6 +32766,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31349
32766
|
this.#postDragSuppressTimer = void 0;
|
|
31350
32767
|
}
|
|
31351
32768
|
this.#container()?.removeEventListener(`scroll`, this.#onContainerScroll);
|
|
32769
|
+
this.#teardownVirtualObservers();
|
|
31352
32770
|
if (this.#stickyScrollBound) {
|
|
31353
32771
|
this.#stickyScrollContainer?.removeEventListener(`scroll`, this.#onStickyScroll);
|
|
31354
32772
|
this.#stickyScrollContainer = void 0;
|
|
@@ -31402,12 +32820,12 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31402
32820
|
}
|
|
31403
32821
|
selectAll() {
|
|
31404
32822
|
const prev = new Set(this._selectedNodes);
|
|
31405
|
-
if (this.interactionMode === `checked`) for (const { node } of this.#
|
|
32823
|
+
if (this.interactionMode === `checked`) for (const { node } of this.#getFlat().rows) {
|
|
31406
32824
|
if (!this.#canSelect(node)) continue;
|
|
31407
32825
|
this.#applyCheckedToggle(node, true);
|
|
31408
32826
|
}
|
|
31409
32827
|
else {
|
|
31410
|
-
for (const { node } of this.#
|
|
32828
|
+
for (const { node } of this.#getFlat().rows) {
|
|
31411
32829
|
if (!this.#canSelect(node)) continue;
|
|
31412
32830
|
this._selectedNodes.add(node);
|
|
31413
32831
|
}
|
|
@@ -31418,7 +32836,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31418
32836
|
}
|
|
31419
32837
|
deselectAll() {
|
|
31420
32838
|
const prev = new Set(this._selectedNodes);
|
|
31421
|
-
if (this.interactionMode === `checked`) for (const { node } of this.#
|
|
32839
|
+
if (this.interactionMode === `checked`) for (const { node } of this.#getFlat().rows) {
|
|
31422
32840
|
if (!this.#canSelect(node)) continue;
|
|
31423
32841
|
this.#applyCheckedToggle(node, false);
|
|
31424
32842
|
}
|
|
@@ -31439,7 +32857,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31439
32857
|
const found = children.find((n) => n.item.key === key);
|
|
31440
32858
|
if (!found) return false;
|
|
31441
32859
|
this.#applyExclusivity(found.item.key);
|
|
31442
|
-
this._expandedKeys
|
|
32860
|
+
this.#setExpanded(withKey(this._expandedKeys, found.item.key));
|
|
31443
32861
|
if (key === keys.at(-1)) {
|
|
31444
32862
|
this.select(found);
|
|
31445
32863
|
this.tickled.setTickled(found.item.key);
|
|
@@ -31464,7 +32882,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31464
32882
|
if (!path) return false;
|
|
31465
32883
|
for (let i = 0; i < path.length - 1; i++) {
|
|
31466
32884
|
this.#applyExclusivity(path[i]);
|
|
31467
|
-
this._expandedKeys
|
|
32885
|
+
this.#setExpanded(withKey(this._expandedKeys, path[i]));
|
|
31468
32886
|
}
|
|
31469
32887
|
this.tickled.setTickled(key);
|
|
31470
32888
|
this.#stickyDirty = true;
|
|
@@ -31500,10 +32918,12 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31500
32918
|
return true;
|
|
31501
32919
|
}
|
|
31502
32920
|
#clearSelectionInternal() {
|
|
32921
|
+
this._selectionVersion++;
|
|
31503
32922
|
this._selectedNodes.clear();
|
|
31504
32923
|
this.selectedNode = void 0;
|
|
31505
32924
|
}
|
|
31506
32925
|
#dispatchSelect(previous) {
|
|
32926
|
+
this._selectionVersion++;
|
|
31507
32927
|
const detail = {
|
|
31508
32928
|
selected: new Set(this._selectedNodes),
|
|
31509
32929
|
previous
|
|
@@ -31609,9 +33029,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31609
33029
|
return;
|
|
31610
33030
|
}
|
|
31611
33031
|
if (isShift && this.#anchor) {
|
|
31612
|
-
const
|
|
31613
|
-
const
|
|
31614
|
-
const
|
|
33032
|
+
const flat = this.#getFlat();
|
|
33033
|
+
const visible = flat.rows;
|
|
33034
|
+
const anchorIdx = flat.indexByKey.get(this.#anchor.node.item.key) ?? -1;
|
|
33035
|
+
const targetIdx = flat.indexByKey.get(node.item.key) ?? -1;
|
|
31615
33036
|
if (anchorIdx >= 0 && targetIdx >= 0) {
|
|
31616
33037
|
const prev = new Set(this._selectedNodes);
|
|
31617
33038
|
const from = Math.min(anchorIdx, targetIdx);
|
|
@@ -31661,9 +33082,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31661
33082
|
return;
|
|
31662
33083
|
}
|
|
31663
33084
|
if (isShift && this.#anchor) {
|
|
31664
|
-
const
|
|
31665
|
-
const
|
|
31666
|
-
const
|
|
33085
|
+
const flat = this.#getFlat();
|
|
33086
|
+
const visible = flat.rows;
|
|
33087
|
+
const anchorIdx = flat.indexByKey.get(this.#anchor.node.item.key) ?? -1;
|
|
33088
|
+
const targetIdx = flat.indexByKey.get(node.item.key) ?? -1;
|
|
31667
33089
|
if (anchorIdx >= 0 && targetIdx >= 0) {
|
|
31668
33090
|
const prev = new Set(this._selectedNodes);
|
|
31669
33091
|
const from = Math.min(anchorIdx, targetIdx);
|
|
@@ -31706,7 +33128,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31706
33128
|
const isExpanded = this._expandedKeys.has(node.item.key);
|
|
31707
33129
|
this.#stickyDirty = true;
|
|
31708
33130
|
if (isExpanded) {
|
|
31709
|
-
this._expandedKeys
|
|
33131
|
+
this.#setExpanded(withoutKey(this._expandedKeys, node.item.key));
|
|
31710
33132
|
this.dispatchEvent(new CustomEvent(`collapse`, {
|
|
31711
33133
|
detail: {
|
|
31712
33134
|
node,
|
|
@@ -31725,7 +33147,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31725
33147
|
}
|
|
31726
33148
|
} else {
|
|
31727
33149
|
this.#applyExclusivity(node.item.key);
|
|
31728
|
-
this._expandedKeys
|
|
33150
|
+
this.#setExpanded(withKey(this._expandedKeys, node.item.key));
|
|
31729
33151
|
this.dispatchEvent(new CustomEvent(`expand`, {
|
|
31730
33152
|
detail: {
|
|
31731
33153
|
node,
|
|
@@ -31749,7 +33171,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31749
33171
|
if (node.children) for (const child of node.children) this.#applyCheckedToggle(child, selected);
|
|
31750
33172
|
}
|
|
31751
33173
|
#getFlatIndexOf(node) {
|
|
31752
|
-
return this.#
|
|
33174
|
+
return this.#getFlat().indexByKey.get(node.item.key) ?? -1;
|
|
31753
33175
|
}
|
|
31754
33176
|
#extendRangeSelection(nodes, fromIdx, toIdx) {
|
|
31755
33177
|
if (!this.#anchor) {
|
|
@@ -31760,7 +33182,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31760
33182
|
};
|
|
31761
33183
|
}
|
|
31762
33184
|
if (!this.#anchor) return;
|
|
31763
|
-
const anchorIdx =
|
|
33185
|
+
const anchorIdx = this.#getFlat().indexByKey.get(this.#anchor.node.item.key) ?? -1;
|
|
31764
33186
|
if (anchorIdx < 0) return;
|
|
31765
33187
|
const from = Math.min(anchorIdx, toIdx);
|
|
31766
33188
|
const to = Math.max(anchorIdx, toIdx);
|
|
@@ -31796,8 +33218,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31796
33218
|
}, controller.signal), controller.signal)];
|
|
31797
33219
|
if (controller.signal.aborted) return;
|
|
31798
33220
|
node.children = children;
|
|
33221
|
+
this.#structureVersion++;
|
|
31799
33222
|
this.#applyExclusivity(node.item.key);
|
|
31800
|
-
this._expandedKeys
|
|
33223
|
+
this.#setExpanded(withKey(this._expandedKeys, node.item.key));
|
|
33224
|
+
this.#flat = void 0;
|
|
31801
33225
|
this.#stickyDirty = true;
|
|
31802
33226
|
this.#pendingNode = void 0;
|
|
31803
33227
|
this.#pendingController = void 0;
|
|
@@ -31813,6 +33237,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31813
33237
|
} catch {
|
|
31814
33238
|
if (controller.signal.aborted) return;
|
|
31815
33239
|
node.children = [];
|
|
33240
|
+
this.#structureVersion++;
|
|
33241
|
+
this.#flat = void 0;
|
|
31816
33242
|
this.#pendingNode = void 0;
|
|
31817
33243
|
this.#pendingController = void 0;
|
|
31818
33244
|
this.requestUpdate();
|
|
@@ -31824,33 +33250,44 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31824
33250
|
_isFocused(node) {
|
|
31825
33251
|
return this.tickled.isTickled(node.item.key);
|
|
31826
33252
|
}
|
|
31827
|
-
_isExpanded(node,
|
|
33253
|
+
_isExpanded(node, _depth = 0) {
|
|
31828
33254
|
if (this._expandedKeys.has(node.item.key)) return true;
|
|
31829
|
-
|
|
31830
|
-
return false;
|
|
31831
|
-
}
|
|
31832
|
-
#matchesFilter(node, depth) {
|
|
31833
|
-
if (!this.filterPredicate) return true;
|
|
31834
|
-
if (this.filterPredicate(node, depth)) return true;
|
|
31835
|
-
return node.children?.some((child) => this.#matchesFilter(child, depth + 1)) ?? false;
|
|
33255
|
+
return this.#getFilter()?.autoExpanded.has(node.item.key) ?? false;
|
|
31836
33256
|
}
|
|
31837
33257
|
get _pendingNode() {
|
|
31838
33258
|
return this.#pendingNode;
|
|
31839
33259
|
}
|
|
31840
33260
|
#scrollKeyIntoView(key) {
|
|
33261
|
+
if (this.#virtualActive()) {
|
|
33262
|
+
this.updateComplete.then(() => {
|
|
33263
|
+
const idx = this.#getFlat().indexByKey.get(key);
|
|
33264
|
+
if (idx !== void 0) this.#revealIndex(idx);
|
|
33265
|
+
});
|
|
33266
|
+
return;
|
|
33267
|
+
}
|
|
33268
|
+
const token = ++this.#revealToken;
|
|
31841
33269
|
requestAnimationFrame(() => {
|
|
31842
|
-
|
|
31843
|
-
const
|
|
31844
|
-
if (
|
|
31845
|
-
|
|
31846
|
-
|
|
31847
|
-
const relativeTop = elRect.top - containerRect.top;
|
|
31848
|
-
const relativeBottom = relativeTop + elRect.height;
|
|
31849
|
-
const overlayHeight = this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0;
|
|
31850
|
-
if (relativeTop < overlayHeight) container.scrollTop += relativeTop - overlayHeight;
|
|
31851
|
-
else if (relativeBottom > container.clientHeight) container.scrollTop += relativeBottom - container.clientHeight;
|
|
33270
|
+
this.#revealRowElement(key);
|
|
33271
|
+
const running = this.#container()?.getAnimations?.({ subtree: true }) ?? [];
|
|
33272
|
+
if (running.length > 0) Promise.allSettled(running.map((a) => a.finished)).then(() => {
|
|
33273
|
+
if (token === this.#revealToken) this.#revealRowElement(key);
|
|
33274
|
+
});
|
|
31852
33275
|
});
|
|
31853
33276
|
}
|
|
33277
|
+
#revealToken;
|
|
33278
|
+
/** Scroll the rendered row for `key` into the container, below the sticky overlay. No-op if it isn't rendered or is already visible. */
|
|
33279
|
+
#revealRowElement(key) {
|
|
33280
|
+
const container = this.#container();
|
|
33281
|
+
const el = this.shadowRoot?.querySelector(`ul.tree-list [data-key="${CSS.escape(key)}"]`);
|
|
33282
|
+
if (!el || !container) return;
|
|
33283
|
+
const elRect = el.getBoundingClientRect();
|
|
33284
|
+
const containerRect = container.getBoundingClientRect();
|
|
33285
|
+
const relativeTop = elRect.top - containerRect.top;
|
|
33286
|
+
const relativeBottom = relativeTop + elRect.height;
|
|
33287
|
+
const overlayHeight = this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0;
|
|
33288
|
+
if (relativeTop < overlayHeight) container.scrollTop += relativeTop - overlayHeight;
|
|
33289
|
+
else if (relativeBottom > container.clientHeight) container.scrollTop += relativeBottom - container.clientHeight;
|
|
33290
|
+
}
|
|
31854
33291
|
#abortPending() {
|
|
31855
33292
|
if (this.#pendingController) {
|
|
31856
33293
|
this.#pendingController.abort();
|
|
@@ -31864,14 +33301,15 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31864
33301
|
if (this.exclusivity === `depth`) this.#collapseSiblingsAtDepth(expandKey);
|
|
31865
33302
|
else if (this.exclusivity === `global`) {
|
|
31866
33303
|
const ancestorKeys = this.#findAncestorKeys(expandKey);
|
|
31867
|
-
|
|
33304
|
+
this.#setExpanded(withoutKeys(this._expandedKeys, (key) => !ancestorKeys.has(key) && key !== expandKey));
|
|
31868
33305
|
}
|
|
31869
33306
|
}
|
|
31870
33307
|
#collapseSiblingsAtDepth(expandKey) {
|
|
31871
33308
|
if (!this.root) return;
|
|
31872
33309
|
const parent = this.#findParent(this.root, expandKey);
|
|
31873
33310
|
if (!parent?.children) return;
|
|
31874
|
-
|
|
33311
|
+
const siblingKeys = new Set(parent.children.map((c) => c.item.key));
|
|
33312
|
+
this.#setExpanded(withoutKeys(this._expandedKeys, (key) => key !== expandKey && siblingKeys.has(key)));
|
|
31875
33313
|
}
|
|
31876
33314
|
#findAncestorKeys(targetKey) {
|
|
31877
33315
|
const keys = /* @__PURE__ */ new Set();
|
|
@@ -31895,30 +33333,13 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31895
33333
|
if (found) return found;
|
|
31896
33334
|
}
|
|
31897
33335
|
}
|
|
31898
|
-
#getVisibleNodes(node, depth) {
|
|
31899
|
-
const result = [];
|
|
31900
|
-
const visit = (n, d) => {
|
|
31901
|
-
result.push({
|
|
31902
|
-
node: n,
|
|
31903
|
-
depth: d
|
|
31904
|
-
});
|
|
31905
|
-
if (n.children && this._isExpanded(n, d)) for (const child of n.children) visit(child, d + 1);
|
|
31906
|
-
};
|
|
31907
|
-
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
31908
|
-
return result;
|
|
31909
|
-
}
|
|
31910
|
-
#getAllNodes() {
|
|
31911
|
-
if (!this.root) return [];
|
|
31912
|
-
return this.#getVisibleNodes(this.root, -1);
|
|
31913
|
-
}
|
|
31914
33336
|
#getFocusedIndex() {
|
|
31915
|
-
const nodes = this.#getAllNodes();
|
|
31916
33337
|
const tickledKey = this.tickled.tickledId;
|
|
31917
33338
|
if (!tickledKey) return 0;
|
|
31918
|
-
return
|
|
33339
|
+
return this.#getFlat().indexByKey.get(tickledKey) ?? 0;
|
|
31919
33340
|
}
|
|
31920
33341
|
#setFocusedIndex(index) {
|
|
31921
|
-
const nodes = this.#
|
|
33342
|
+
const nodes = this.#getFlat().rows;
|
|
31922
33343
|
if (index < 0 || index >= nodes.length) return;
|
|
31923
33344
|
const { node, depth } = nodes[index];
|
|
31924
33345
|
this.tickled.setTickled(node.item.key);
|
|
@@ -31932,6 +33353,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31932
33353
|
composed: true
|
|
31933
33354
|
}));
|
|
31934
33355
|
this.requestUpdate();
|
|
33356
|
+
if (this.#virtualActive()) {
|
|
33357
|
+
this.#revealIndex(index);
|
|
33358
|
+
return;
|
|
33359
|
+
}
|
|
31935
33360
|
this.updateComplete.then(() => this.#scrollTickledIntoView());
|
|
31936
33361
|
}
|
|
31937
33362
|
#scrollTickledIntoView() {
|
|
@@ -31942,12 +33367,25 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31942
33367
|
behavior: `smooth`
|
|
31943
33368
|
});
|
|
31944
33369
|
}
|
|
33370
|
+
/** Rows per PageUp / PageDown: one viewport, from the current CSS metrics. */
|
|
33371
|
+
#pageSize() {
|
|
33372
|
+
this.#measureMetrics();
|
|
33373
|
+
return treePageSize(this.#layoutParams(this.#getFlat().rows.length));
|
|
33374
|
+
}
|
|
33375
|
+
/** Move the cursor to `nextIdx`, applying the interaction mode's selection side effects. */
|
|
33376
|
+
#navigateToIndex(nodes, currentIdx, nextIdx, shiftKey) {
|
|
33377
|
+
if (this.interactionMode === `implicit`) this.select(nodes[nextIdx].node);
|
|
33378
|
+
this.#setFocusedIndex(nextIdx);
|
|
33379
|
+
if (shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`)) this.#extendRangeSelection(nodes, currentIdx, nextIdx);
|
|
33380
|
+
else if (this.interactionMode === `vscode`) this.#anchor = void 0;
|
|
33381
|
+
this.requestUpdate();
|
|
33382
|
+
}
|
|
31945
33383
|
#onKeyDown;
|
|
31946
33384
|
renderLoadingIndicator() {
|
|
31947
33385
|
return html`<span class="loading-spinner"></span>`;
|
|
31948
33386
|
}
|
|
31949
33387
|
render() {
|
|
31950
|
-
const nodes = this.#
|
|
33388
|
+
const nodes = this.#getFlat().rows;
|
|
31951
33389
|
return html`
|
|
31952
33390
|
<div
|
|
31953
33391
|
id="container"
|
|
@@ -31970,12 +33408,51 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31970
33408
|
${this.renderLoadingIndicator()}
|
|
31971
33409
|
</div>
|
|
31972
33410
|
`;
|
|
33411
|
+
if (this.#virtualActive()) return this.#renderWindow(nodes);
|
|
33412
|
+
const woven = weaveGhosts(nodes, this.#renderedRowKeys, this.#leaving);
|
|
33413
|
+
this.#renderedRowKeys = woven.map((e) => e.node.item.key);
|
|
33414
|
+
this.#renderedRowIndex = new Map(this.#renderedRowKeys.map((key, i) => [key, i]));
|
|
31973
33415
|
return html`
|
|
31974
33416
|
<ul class="tree-list" role="tree">
|
|
31975
|
-
${repeat(
|
|
33417
|
+
${repeat(woven, ({ node }) => node.item.key, ({ node, depth }) => this._renderNode(node, depth))}
|
|
31976
33418
|
</ul>
|
|
31977
33419
|
`;
|
|
31978
33420
|
}
|
|
33421
|
+
/**
|
|
33422
|
+
* Windowed render: the rows near the viewport between two spacers that hold
|
|
33423
|
+
* the rest of the scroll height. Keyed, so a row that stays in the window
|
|
33424
|
+
* keeps its element while others enter and leave at the edges.
|
|
33425
|
+
*/
|
|
33426
|
+
#renderWindow(nodes) {
|
|
33427
|
+
const params = this.#layoutParams(nodes.length);
|
|
33428
|
+
const { start, end } = this.#currentWindow(nodes.length);
|
|
33429
|
+
this.#winStart = start;
|
|
33430
|
+
this.#winEnd = end;
|
|
33431
|
+
this.#renderedRowKeys = [];
|
|
33432
|
+
this.#renderedRowIndex = /* @__PURE__ */ new Map();
|
|
33433
|
+
const stride = treeStride(params);
|
|
33434
|
+
return html`
|
|
33435
|
+
<ul class="tree-list virtual" role="tree" aria-rowcount=${nodes.length}>
|
|
33436
|
+
<li class="virtual-spacer" role="presentation" aria-hidden="true" style=${`height: ${start * stride}px`}></li>
|
|
33437
|
+
${repeat(nodes.slice(start, end), ({ node }) => node.item.key, ({ node, depth }) => this._renderNode(node, depth))}
|
|
33438
|
+
<li class="virtual-spacer" role="presentation" aria-hidden="true" style=${`height: ${(nodes.length - end) * stride}px`}></li>
|
|
33439
|
+
</ul>
|
|
33440
|
+
`;
|
|
33441
|
+
}
|
|
33442
|
+
/**
|
|
33443
|
+
* `{ posinset, setsize }` for `aria-posinset` / `aria-setsize`. A windowed
|
|
33444
|
+
* list has most siblings out of the DOM, so assistive tech cannot count them.
|
|
33445
|
+
*/
|
|
33446
|
+
_ariaSetInfo(node) {
|
|
33447
|
+
const flat = this.#getFlat();
|
|
33448
|
+
const idx = flat.indexByKey.get(node.item.key);
|
|
33449
|
+
if (idx === void 0) return void 0;
|
|
33450
|
+
const { posInSet, setSize } = flat.rows[idx];
|
|
33451
|
+
return {
|
|
33452
|
+
posInSet,
|
|
33453
|
+
setSize
|
|
33454
|
+
};
|
|
33455
|
+
}
|
|
31979
33456
|
/** Shared drag-and-drop render state for a row, used by every `_renderNode` variant. */
|
|
31980
33457
|
_dndRowState(node) {
|
|
31981
33458
|
const draggable = this.itemDnd !== `none` && (this.root?.children?.length ?? 0) > 0 && (this.itemDnd === `internal` || this.itemDnd === `both` || this.itemDnd === `external`) && (this.canDrag?.({
|
|
@@ -31999,17 +33476,18 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31999
33476
|
const isBranch = node.item.isLeaf !== true;
|
|
32000
33477
|
const isExpanded = this._isExpanded(node, depth);
|
|
32001
33478
|
const isPending = this.#pendingNode === node;
|
|
32002
|
-
const isFiltered = this._isFilteredOut(node, depth);
|
|
32003
33479
|
const isLeaving = this._isLeaving(node);
|
|
32004
33480
|
const dnd = this._dndRowState(node);
|
|
32005
33481
|
return html`
|
|
32006
33482
|
<li
|
|
32007
|
-
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${
|
|
33483
|
+
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${isLeaving ? `leaving` : ``} ${dnd.dragging ? `dragging` : ``} ${dnd.dropTarget && dnd.dropPosition === `inside` ? `drop-into` : ``}"
|
|
32008
33484
|
role="treeitem"
|
|
32009
33485
|
aria-selected=${selected}
|
|
32010
33486
|
aria-expanded=${isBranch ? isExpanded : nothing}
|
|
32011
33487
|
aria-hidden=${isLeaving ? `true` : nothing}
|
|
32012
33488
|
aria-level=${depth + 1}
|
|
33489
|
+
aria-setsize=${this._ariaSetInfo(node)?.setSize ?? nothing}
|
|
33490
|
+
aria-posinset=${this._ariaSetInfo(node)?.posInSet ?? nothing}
|
|
32013
33491
|
data-key=${node.item.key}
|
|
32014
33492
|
style=${`--depth: ${depth}`}
|
|
32015
33493
|
draggable=${dnd.draggable ? `true` : nothing}
|
|
@@ -32050,6 +33528,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32050
33528
|
--tree-muted-text: var(--surface-muted-text);
|
|
32051
33529
|
--tree-indent: 1ch;
|
|
32052
33530
|
--tree-item-height: 30px;
|
|
33531
|
+
/* Margin above each row. A windowed list is positioned by (height + gap), so keep it in px. */
|
|
33532
|
+
--tree-item-gap: 1px;
|
|
32053
33533
|
--tree-caret-size: 30px;
|
|
32054
33534
|
--tree-radius: var(--radius-s);
|
|
32055
33535
|
--tree-drop-line: var(--accent);
|
|
@@ -32073,6 +33553,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32073
33553
|
The scroll lives here, not on :host, so the search popover (a sibling of
|
|
32074
33554
|
#container, positioned against :host) stays put while the list scrolls. */
|
|
32075
33555
|
#container {
|
|
33556
|
+
/* offsetParent for rows: row offsets are scroll-independent (sticky / DnD geometry). */
|
|
33557
|
+
position: relative;
|
|
32076
33558
|
outline: none;
|
|
32077
33559
|
min-height: 100%;
|
|
32078
33560
|
max-height: 100%;
|
|
@@ -32119,7 +33601,40 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32119
33601
|
font-size: var(--text-m);
|
|
32120
33602
|
border-radius: var(--tree-radius);
|
|
32121
33603
|
transition: background var(--transition, 0.15s ease-out);
|
|
32122
|
-
margin:
|
|
33604
|
+
margin: var(--tree-item-gap) 0;
|
|
33605
|
+
}
|
|
33606
|
+
|
|
33607
|
+
/* Windowed list: rows are placed purely by stride arithmetic — a margin above
|
|
33608
|
+
each row only (no collapsing), spacers hold the offscreen height. */
|
|
33609
|
+
.tree-list.virtual .item {
|
|
33610
|
+
margin: var(--tree-item-gap) 0 0;
|
|
33611
|
+
}
|
|
33612
|
+
|
|
33613
|
+
.virtual-spacer {
|
|
33614
|
+
display: block;
|
|
33615
|
+
margin: 0;
|
|
33616
|
+
padding: 0;
|
|
33617
|
+
list-style: none;
|
|
33618
|
+
pointer-events: none;
|
|
33619
|
+
}
|
|
33620
|
+
|
|
33621
|
+
.sticky-anchor {
|
|
33622
|
+
display: contents;
|
|
33623
|
+
}
|
|
33624
|
+
|
|
33625
|
+
:host([data-virtual]) .sticky-anchor {
|
|
33626
|
+
position: sticky;
|
|
33627
|
+
top: 0;
|
|
33628
|
+
z-index: 5;
|
|
33629
|
+
display: block;
|
|
33630
|
+
height: 0;
|
|
33631
|
+
}
|
|
33632
|
+
|
|
33633
|
+
:host([data-virtual]) .sticky-headers {
|
|
33634
|
+
position: absolute;
|
|
33635
|
+
top: 0;
|
|
33636
|
+
left: 0;
|
|
33637
|
+
right: 0;
|
|
32123
33638
|
}
|
|
32124
33639
|
|
|
32125
33640
|
.tree-caret {
|
|
@@ -32174,10 +33689,6 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32174
33689
|
to { transform: rotate(360deg); }
|
|
32175
33690
|
}
|
|
32176
33691
|
|
|
32177
|
-
.item.filtered {
|
|
32178
|
-
display: none;
|
|
32179
|
-
}
|
|
32180
|
-
|
|
32181
33692
|
/* Fading-out ghost row (see #runItemAnimations) — inert while it collapses. */
|
|
32182
33693
|
.item.leaving {
|
|
32183
33694
|
pointer-events: none;
|
|
@@ -32288,6 +33799,14 @@ __decorate([property({
|
|
|
32288
33799
|
toAttribute: (value) => value ? null : `false`
|
|
32289
33800
|
}
|
|
32290
33801
|
})], TreeBaseElement.prototype, "stickyHeaders", void 0);
|
|
33802
|
+
__decorate([property({
|
|
33803
|
+
reflect: true,
|
|
33804
|
+
attribute: `virtualize`
|
|
33805
|
+
})], TreeBaseElement.prototype, "virtualize", void 0);
|
|
33806
|
+
__decorate([property({
|
|
33807
|
+
type: Number,
|
|
33808
|
+
attribute: `virtualize-threshold`
|
|
33809
|
+
})], TreeBaseElement.prototype, "virtualizeThreshold", void 0);
|
|
32291
33810
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "dragPreviewRenderer", void 0);
|
|
32292
33811
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "dragPreviewSource", void 0);
|
|
32293
33812
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "canDrag", void 0);
|
|
@@ -32295,33 +33814,33 @@ __decorate([property({ attribute: false })], TreeBaseElement.prototype, "canDrop
|
|
|
32295
33814
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "parseExternalDrop", void 0);
|
|
32296
33815
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "applyInternalDrop", void 0);
|
|
32297
33816
|
__decorate([state()], TreeBaseElement.prototype, "_expandedKeys", void 0);
|
|
33817
|
+
__decorate([state()], TreeBaseElement.prototype, "_scrollTop", void 0);
|
|
32298
33818
|
__decorate([state()], TreeBaseElement.prototype, "_focusedDepth", void 0);
|
|
32299
33819
|
__decorate([state()], TreeBaseElement.prototype, "_stickyKeys", void 0);
|
|
32300
33820
|
TreeBaseElement = __decorate([safeCustomElement(`ixfx-tree-base`)], TreeBaseElement);
|
|
32301
33821
|
//#endregion
|
|
32302
33822
|
//#region src/tree/tree-list.ts
|
|
32303
|
-
function computeCheckboxState$1(node, selectedNodes) {
|
|
32304
|
-
if (node.item.isLeaf || !node.children?.length) return selectedNodes.has(node) ? `checked` : `unchecked`;
|
|
32305
|
-
const childStates = node.children.map((c) => computeCheckboxState$1(c, selectedNodes));
|
|
32306
|
-
const allChecked = childStates.every((s) => s === `checked`);
|
|
32307
|
-
const allUnchecked = childStates.every((s) => s === `unchecked`);
|
|
32308
|
-
if (allChecked) return `checked`;
|
|
32309
|
-
if (allUnchecked) return `unchecked`;
|
|
32310
|
-
return `indeterminate`;
|
|
32311
|
-
}
|
|
32312
33823
|
let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
32313
|
-
|
|
32314
|
-
|
|
32315
|
-
|
|
32316
|
-
|
|
33824
|
+
/** Checkbox tri-states for the whole loaded tree, rebuilt only when the structure or selection changes. */
|
|
33825
|
+
#checkboxCache;
|
|
33826
|
+
willUpdate(changedProps) {
|
|
33827
|
+
super.willUpdate(changedProps);
|
|
33828
|
+
this.#syncCheckboxStates();
|
|
32317
33829
|
}
|
|
32318
|
-
#
|
|
32319
|
-
this
|
|
32320
|
-
|
|
32321
|
-
|
|
32322
|
-
|
|
33830
|
+
#syncCheckboxStates() {
|
|
33831
|
+
const root = this.root;
|
|
33832
|
+
if (this.interactionMode !== `checked` || !root) {
|
|
33833
|
+
this.#checkboxCache = void 0;
|
|
33834
|
+
return;
|
|
33835
|
+
}
|
|
33836
|
+
const c = this.#checkboxCache;
|
|
33837
|
+
if (c && c.root === root && c.structureVersion === this._structureVersion && c.selectionVersion === this._selectionVersion) return;
|
|
33838
|
+
this.#checkboxCache = {
|
|
33839
|
+
root,
|
|
33840
|
+
structureVersion: this._structureVersion,
|
|
33841
|
+
selectionVersion: this._selectionVersion,
|
|
33842
|
+
states: computeCheckboxStates(root, this._selectedNodes)
|
|
32323
33843
|
};
|
|
32324
|
-
if (this.root) visit(this.root, -1);
|
|
32325
33844
|
}
|
|
32326
33845
|
renderNodeContent(node, depth) {
|
|
32327
33846
|
const isBranch = node.item.isLeaf !== true;
|
|
@@ -32339,7 +33858,7 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
|
32339
33858
|
</span>
|
|
32340
33859
|
`;
|
|
32341
33860
|
if (this.interactionMode === `checked`) {
|
|
32342
|
-
const state =
|
|
33861
|
+
const state = this.#checkboxCache?.states.get(node.item.key) ?? `unchecked`;
|
|
32343
33862
|
return html`${this.#renderCheckbox(node, depth, state)}${caretAndLabel}`;
|
|
32344
33863
|
}
|
|
32345
33864
|
return caretAndLabel;
|
|
@@ -32373,19 +33892,20 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
|
32373
33892
|
const isBranch = node.item.isLeaf !== true;
|
|
32374
33893
|
const isExpanded = this._isExpanded(node, depth);
|
|
32375
33894
|
const isPending = this._pendingNode === node;
|
|
32376
|
-
const hasIconAtDepth = this
|
|
33895
|
+
const hasIconAtDepth = this._depthsWithIcons.has(depth);
|
|
32377
33896
|
const isLeaving = this._isLeaving(node);
|
|
32378
|
-
const isFiltered = this._isFilteredOut(node, depth);
|
|
32379
33897
|
const dnd = this._dndRowState(node);
|
|
32380
33898
|
return html`
|
|
32381
33899
|
<li
|
|
32382
|
-
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``} ${
|
|
33900
|
+
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``} ${dnd.dragging ? `dragging` : ``} ${dnd.dropTarget && dnd.dropPosition === `inside` ? `drop-into` : ``}"
|
|
32383
33901
|
data-key=${node.item.key}
|
|
32384
33902
|
role="treeitem"
|
|
32385
33903
|
aria-selected=${selected}
|
|
32386
33904
|
aria-expanded=${isBranch ? isExpanded : nothing}
|
|
32387
33905
|
aria-hidden=${isLeaving ? `true` : nothing}
|
|
32388
33906
|
aria-level=${depth + 1}
|
|
33907
|
+
aria-setsize=${this._ariaSetInfo(node)?.setSize ?? nothing}
|
|
33908
|
+
aria-posinset=${this._ariaSetInfo(node)?.posInSet ?? nothing}
|
|
32389
33909
|
style=${`--depth: ${depth}`}
|
|
32390
33910
|
draggable=${dnd.draggable ? `true` : nothing}
|
|
32391
33911
|
part=${dnd.dropTarget ? `drop-target` : nothing}
|
|
@@ -40891,86 +42411,30 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
40891
42411
|
return;
|
|
40892
42412
|
}
|
|
40893
42413
|
switch (event.key) {
|
|
40894
|
-
case `ArrowDown`:
|
|
42414
|
+
case `ArrowDown`:
|
|
40895
42415
|
event.preventDefault();
|
|
40896
|
-
|
|
40897
|
-
let targetIndex = 0;
|
|
40898
|
-
if (current && current.depth === currentDepth) targetIndex = Math.min(current.index + 1, col.length - 1);
|
|
40899
|
-
const targetNode = col[targetIndex];
|
|
40900
|
-
if (!targetNode) break;
|
|
40901
|
-
this.#setTickledNode(targetNode, currentDepth);
|
|
40902
|
-
if (this.interactionMode === `standard` || this.interactionMode === `vscode`) {
|
|
40903
|
-
const prevIndex = current?.index ?? 0;
|
|
40904
|
-
if (event.shiftKey) {
|
|
40905
|
-
if (!this.#anchor || this.#anchor.depth !== currentDepth) {
|
|
40906
|
-
const anchorNode = col[prevIndex];
|
|
40907
|
-
if (anchorNode) this.#anchor = {
|
|
40908
|
-
key: anchorNode.item.key,
|
|
40909
|
-
depth: currentDepth,
|
|
40910
|
-
index: prevIndex,
|
|
40911
|
-
selectionSnapshot: this.interactionMode === `vscode` ? new Set(this._selectedKeys) : void 0
|
|
40912
|
-
};
|
|
40913
|
-
}
|
|
40914
|
-
if (this.#anchor) {
|
|
40915
|
-
const from = Math.min(this.#anchor.index, targetIndex);
|
|
40916
|
-
const to = Math.max(this.#anchor.index, targetIndex);
|
|
40917
|
-
const rangeKeys = col.slice(from, to + 1).map((n) => n.item.key);
|
|
40918
|
-
if (this.interactionMode === `vscode`) {
|
|
40919
|
-
const snap = this.#anchor.selectionSnapshot;
|
|
40920
|
-
for (const k of rangeKeys) if (snap.has(k)) this._selectedKeys.delete(k);
|
|
40921
|
-
else this._selectedKeys.add(k);
|
|
40922
|
-
} else this._selectedKeys = new Set(rangeKeys);
|
|
40923
|
-
}
|
|
40924
|
-
} else if (this.interactionMode === `standard`) this.#anchor = {
|
|
40925
|
-
key: targetNode.item.key,
|
|
40926
|
-
depth: currentDepth,
|
|
40927
|
-
index: targetIndex
|
|
40928
|
-
};
|
|
40929
|
-
else this.#anchor = void 0;
|
|
40930
|
-
}
|
|
40931
|
-
this.requestUpdate();
|
|
42416
|
+
this.#moveCursor(col, currentDepth, event, (current) => Math.min(current + 1, col.length - 1));
|
|
40932
42417
|
break;
|
|
40933
|
-
|
|
40934
|
-
case `ArrowUp`: {
|
|
42418
|
+
case `ArrowUp`:
|
|
40935
42419
|
event.preventDefault();
|
|
40936
|
-
|
|
40937
|
-
|
|
40938
|
-
|
|
40939
|
-
|
|
40940
|
-
|
|
40941
|
-
|
|
40942
|
-
|
|
40943
|
-
|
|
40944
|
-
|
|
40945
|
-
|
|
40946
|
-
|
|
40947
|
-
|
|
40948
|
-
|
|
40949
|
-
|
|
40950
|
-
|
|
40951
|
-
|
|
40952
|
-
|
|
40953
|
-
}
|
|
40954
|
-
if (this.#anchor) {
|
|
40955
|
-
const from = Math.min(this.#anchor.index, targetIndex);
|
|
40956
|
-
const to = Math.max(this.#anchor.index, targetIndex);
|
|
40957
|
-
const rangeKeys = col.slice(from, to + 1).map((n) => n.item.key);
|
|
40958
|
-
if (this.interactionMode === `vscode`) {
|
|
40959
|
-
const snap = this.#anchor.selectionSnapshot;
|
|
40960
|
-
for (const k of rangeKeys) if (snap.has(k)) this._selectedKeys.delete(k);
|
|
40961
|
-
else this._selectedKeys.add(k);
|
|
40962
|
-
} else this._selectedKeys = new Set(rangeKeys);
|
|
40963
|
-
}
|
|
40964
|
-
} else if (this.interactionMode === `standard`) this.#anchor = {
|
|
40965
|
-
key: targetNode.item.key,
|
|
40966
|
-
depth: currentDepth,
|
|
40967
|
-
index: targetIndex
|
|
40968
|
-
};
|
|
40969
|
-
else this.#anchor = void 0;
|
|
40970
|
-
}
|
|
40971
|
-
this.requestUpdate();
|
|
42420
|
+
this.#moveCursor(col, currentDepth, event, (current) => Math.max(current - 1, 0));
|
|
42421
|
+
break;
|
|
42422
|
+
case `Home`:
|
|
42423
|
+
event.preventDefault();
|
|
42424
|
+
this.#moveCursor(col, currentDepth, event, () => 0, true);
|
|
42425
|
+
break;
|
|
42426
|
+
case `End`:
|
|
42427
|
+
event.preventDefault();
|
|
42428
|
+
this.#moveCursor(col, currentDepth, event, () => col.length - 1, true);
|
|
42429
|
+
break;
|
|
42430
|
+
case `PageUp`:
|
|
42431
|
+
event.preventDefault();
|
|
42432
|
+
this.#moveCursor(col, currentDepth, event, (current) => Math.max(current - this.#pageSize(currentDepth), 0), true);
|
|
42433
|
+
break;
|
|
42434
|
+
case `PageDown`:
|
|
42435
|
+
event.preventDefault();
|
|
42436
|
+
this.#moveCursor(col, currentDepth, event, (current) => Math.min(current + this.#pageSize(currentDepth), col.length - 1), true);
|
|
40972
42437
|
break;
|
|
40973
|
-
}
|
|
40974
42438
|
case `ArrowRight`: {
|
|
40975
42439
|
event.preventDefault();
|
|
40976
42440
|
const tickled = this.#getTickledNodeAndDepth();
|
|
@@ -41063,14 +42527,28 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41063
42527
|
window.addEventListener(`pointermove`, this.#onResizeMove);
|
|
41064
42528
|
window.addEventListener(`pointerup`, this.#onResizeEnd);
|
|
41065
42529
|
};
|
|
42530
|
+
this.#resizeRaf = null;
|
|
42531
|
+
this.#resizePendingWidth = 0;
|
|
41066
42532
|
this.#onResizeMove = (event) => {
|
|
41067
42533
|
if (this.#resizingDepth < 0) return;
|
|
41068
|
-
|
|
41069
|
-
|
|
41070
|
-
|
|
41071
|
-
|
|
42534
|
+
this.#resizePendingWidth = Math.max(32, this.#resizeStartWidth + (event.clientX - this.#resizeStartX));
|
|
42535
|
+
this.#resizeRaf ??= requestAnimationFrame(this.#applyResizeWidth);
|
|
42536
|
+
};
|
|
42537
|
+
this.#applyResizeWidth = () => {
|
|
42538
|
+
this.#resizeRaf = null;
|
|
42539
|
+
if (this.#resizingDepth < 0) return;
|
|
42540
|
+
(this.shadowRoot?.querySelectorAll(`.column`)[this.#resizingDepth])?.setAttribute(`style`, this.#explicitWidthStyle(this.#resizePendingWidth));
|
|
41072
42541
|
};
|
|
41073
42542
|
this.#onResizeEnd = () => {
|
|
42543
|
+
if (this.#resizeRaf !== null) {
|
|
42544
|
+
cancelAnimationFrame(this.#resizeRaf);
|
|
42545
|
+
this.#resizeRaf = null;
|
|
42546
|
+
}
|
|
42547
|
+
if (this.#resizingDepth >= 0 && this.#resizePendingWidth > 0) {
|
|
42548
|
+
this.#columnWidths.set(this.#resizingDepth, this.#resizePendingWidth);
|
|
42549
|
+
this.requestUpdate();
|
|
42550
|
+
}
|
|
42551
|
+
this.#resizePendingWidth = 0;
|
|
41074
42552
|
this.#resizingDepth = -1;
|
|
41075
42553
|
window.removeEventListener(`pointermove`, this.#onResizeMove);
|
|
41076
42554
|
window.removeEventListener(`pointerup`, this.#onResizeEnd);
|
|
@@ -41677,7 +43155,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41677
43155
|
this.#handleStandardClick(node, depth, event);
|
|
41678
43156
|
break;
|
|
41679
43157
|
case `manual`: break;
|
|
41680
|
-
case `checked`: this.#expandNode(node, depth);
|
|
43158
|
+
case `checked`: this.#expandNode(node, depth, true);
|
|
41681
43159
|
}
|
|
41682
43160
|
}
|
|
41683
43161
|
#handleImplicitClick(node, depth) {
|
|
@@ -41687,7 +43165,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41687
43165
|
this._selectedKeys = /* @__PURE__ */ new Set([node.item.key]);
|
|
41688
43166
|
this._selectedNodes.clear();
|
|
41689
43167
|
this._selectedNodes.add(node);
|
|
41690
|
-
this.#expandNode(node, depth);
|
|
43168
|
+
this.#expandNode(node, depth, true);
|
|
41691
43169
|
if (!previous.has(node)) this.#dispatchSelectEvent(previous);
|
|
41692
43170
|
}
|
|
41693
43171
|
#handleStandardClick(node, depth, event) {
|
|
@@ -41730,16 +43208,22 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41730
43208
|
} : void 0;
|
|
41731
43209
|
this._selectedByDepth.set(depth, node);
|
|
41732
43210
|
this.#lastSelectedNode = node;
|
|
41733
|
-
this.#expandNode(node, depth);
|
|
43211
|
+
this.#expandNode(node, depth, true);
|
|
41734
43212
|
if (!previous.has(node) || previous.size !== 1) this.#dispatchSelectEvent(previous);
|
|
41735
43213
|
}
|
|
41736
|
-
|
|
43214
|
+
/**
|
|
43215
|
+
* Open the column for `node`. `immediate` skips the lazy-load debounce: use
|
|
43216
|
+
* it for deliberate clicks, where the user has committed to this node. The
|
|
43217
|
+
* debounce is kept for keyboard navigation, which can sweep over many
|
|
43218
|
+
* branches in quick succession.
|
|
43219
|
+
*/
|
|
43220
|
+
#expandNode(node, depth, immediate = false) {
|
|
41737
43221
|
if (node.item.isLeaf) {
|
|
41738
43222
|
this._columns = this._columns.slice(0, depth + 1);
|
|
41739
43223
|
this.requestUpdate();
|
|
41740
43224
|
return;
|
|
41741
43225
|
}
|
|
41742
|
-
if (node.children === void 0) this.#loadUnloadedChildren(node, depth, true);
|
|
43226
|
+
if (node.children === void 0) this.#loadUnloadedChildren(node, depth, true, immediate);
|
|
41743
43227
|
else {
|
|
41744
43228
|
const newColumns = this._columns.slice(0, depth + 1);
|
|
41745
43229
|
newColumns.push(node.children);
|
|
@@ -41764,10 +43248,11 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41764
43248
|
* own depth: its column for expansions, -1 for a view root (it sits above
|
|
41765
43249
|
* column 0, so loaders computing child depth as `depth + 1` stay correct).
|
|
41766
43250
|
*/
|
|
41767
|
-
#loadUnloadedChildren(node, parentDepth, fireExpandEvent) {
|
|
43251
|
+
#loadUnloadedChildren(node, parentDepth, fireExpandEvent, immediate = false) {
|
|
41768
43252
|
const nodeDepth = parentDepth + 1;
|
|
41769
43253
|
this._columns = this._columns.slice(0, nodeDepth);
|
|
41770
43254
|
this._loadingDepth = nodeDepth;
|
|
43255
|
+
this.#pendingController?.abort();
|
|
41771
43256
|
this.#pendingNode = node;
|
|
41772
43257
|
const controller = new AbortController();
|
|
41773
43258
|
this.#pendingController = controller;
|
|
@@ -41798,15 +43283,20 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41798
43283
|
this.requestUpdate();
|
|
41799
43284
|
};
|
|
41800
43285
|
if (this.model || this.loadChildren) {
|
|
41801
|
-
if (this.#expandTimer !== void 0)
|
|
41802
|
-
|
|
43286
|
+
if (this.#expandTimer !== void 0) {
|
|
43287
|
+
clearTimeout(this.#expandTimer);
|
|
43288
|
+
this.#expandTimer = void 0;
|
|
43289
|
+
}
|
|
43290
|
+
const load = () => {
|
|
41803
43291
|
this.#expandTimer = void 0;
|
|
41804
43292
|
if (this.#pendingNode !== node) return;
|
|
41805
43293
|
(this.model ? this.model.getChildren(node, nodeDepth - 1, controller.signal) : collectDataResult(this.loadChildren({
|
|
41806
43294
|
node,
|
|
41807
43295
|
depth: nodeDepth - 1
|
|
41808
43296
|
}, controller.signal), controller.signal)).then((children) => applyChildren([...children])).catch(handleError);
|
|
41809
|
-
}
|
|
43297
|
+
};
|
|
43298
|
+
if (immediate) load();
|
|
43299
|
+
else this.#expandTimer = setTimeout(load, EXPAND_DEBOUNCE_MS);
|
|
41810
43300
|
} else {
|
|
41811
43301
|
this._loadingDepth = void 0;
|
|
41812
43302
|
this.#pendingNode = void 0;
|
|
@@ -41817,10 +43307,12 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41817
43307
|
}
|
|
41818
43308
|
}
|
|
41819
43309
|
#onCheckboxClick(node) {
|
|
43310
|
+
const previous = new Set(this._selectedNodes);
|
|
41820
43311
|
const currentlySelected = this._selectedKeys.has(node.item.key);
|
|
41821
43312
|
if (!!node.children?.length ? this.#allDescendantsSelected(node) : currentlySelected) this.#applyCheckedToggle(node, false);
|
|
41822
43313
|
else this.#applyCheckedToggle(node, true);
|
|
41823
43314
|
this.requestUpdate();
|
|
43315
|
+
this.#dispatchSelectEvent(previous);
|
|
41824
43316
|
}
|
|
41825
43317
|
#allDescendantsSelected(node) {
|
|
41826
43318
|
if (!this._selectedKeys.has(node.item.key)) return false;
|
|
@@ -41857,7 +43349,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41857
43349
|
index
|
|
41858
43350
|
};
|
|
41859
43351
|
}
|
|
41860
|
-
#scrollNavigatedItemIntoView() {
|
|
43352
|
+
#scrollNavigatedItemIntoView(behavior = `smooth`) {
|
|
41861
43353
|
requestAnimationFrame(() => {
|
|
41862
43354
|
const tickled = this.#getTickledNodeAndDepth();
|
|
41863
43355
|
if (!tickled) return;
|
|
@@ -41865,7 +43357,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41865
43357
|
if (!container) return;
|
|
41866
43358
|
container.querySelectorAll(`.item`)[tickled.index]?.scrollIntoView({
|
|
41867
43359
|
block: `nearest`,
|
|
41868
|
-
behavior
|
|
43360
|
+
behavior
|
|
41869
43361
|
});
|
|
41870
43362
|
});
|
|
41871
43363
|
}
|
|
@@ -41880,9 +43372,67 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41880
43372
|
composed: true
|
|
41881
43373
|
}));
|
|
41882
43374
|
}
|
|
43375
|
+
/**
|
|
43376
|
+
* Number of items a PageUp/PageDown moves: how many rows fit in the column's
|
|
43377
|
+
* visible area, less one so the previous page's last row stays in view.
|
|
43378
|
+
*/
|
|
43379
|
+
#pageSize(depth) {
|
|
43380
|
+
const content = this.shadowRoot?.querySelectorAll(`.column`)[depth]?.querySelector(`.column-content`);
|
|
43381
|
+
const item = content?.querySelector(`.item`);
|
|
43382
|
+
if (!content || !item || item.offsetHeight === 0) return 10;
|
|
43383
|
+
return Math.max(1, Math.floor(content.clientHeight / item.offsetHeight) - 1);
|
|
43384
|
+
}
|
|
43385
|
+
/**
|
|
43386
|
+
* Move the cursor within column `col` to the index returned by `target`
|
|
43387
|
+
* (given the current cursor index, or -1 when the cursor isn't in this
|
|
43388
|
+
* column, which behaves as "before the first item"). Handles Shift range
|
|
43389
|
+
* extension and anchor seating for `standard` / `vscode` modes.
|
|
43390
|
+
* `scroll` brings the new item into view (for jumps larger than one row).
|
|
43391
|
+
*/
|
|
43392
|
+
#moveCursor(col, currentDepth, event, target, scroll = false) {
|
|
43393
|
+
const current = this.#getTickledNodeAndDepth();
|
|
43394
|
+
const targetIndex = target(current !== void 0 && current.depth === currentDepth ? current.index : -1);
|
|
43395
|
+
const targetNode = col[targetIndex];
|
|
43396
|
+
if (!targetNode) return;
|
|
43397
|
+
this.#setTickledNode(targetNode, currentDepth);
|
|
43398
|
+
if (this.interactionMode === `standard` || this.interactionMode === `vscode`) {
|
|
43399
|
+
const prevIndex = current?.index ?? 0;
|
|
43400
|
+
if (event.shiftKey) {
|
|
43401
|
+
if (!this.#anchor || this.#anchor.depth !== currentDepth) {
|
|
43402
|
+
const anchorNode = col[prevIndex];
|
|
43403
|
+
if (anchorNode) this.#anchor = {
|
|
43404
|
+
key: anchorNode.item.key,
|
|
43405
|
+
depth: currentDepth,
|
|
43406
|
+
index: prevIndex,
|
|
43407
|
+
selectionSnapshot: this.interactionMode === `vscode` ? new Set(this._selectedKeys) : void 0
|
|
43408
|
+
};
|
|
43409
|
+
}
|
|
43410
|
+
if (this.#anchor) {
|
|
43411
|
+
const from = Math.min(this.#anchor.index, targetIndex);
|
|
43412
|
+
const to = Math.max(this.#anchor.index, targetIndex);
|
|
43413
|
+
const rangeKeys = col.slice(from, to + 1).map((n) => n.item.key);
|
|
43414
|
+
if (this.interactionMode === `vscode`) {
|
|
43415
|
+
const snap = this.#anchor.selectionSnapshot;
|
|
43416
|
+
for (const k of rangeKeys) if (snap.has(k)) this._selectedKeys.delete(k);
|
|
43417
|
+
else this._selectedKeys.add(k);
|
|
43418
|
+
} else this._selectedKeys = new Set(rangeKeys);
|
|
43419
|
+
}
|
|
43420
|
+
} else if (this.interactionMode === `standard`) this.#anchor = {
|
|
43421
|
+
key: targetNode.item.key,
|
|
43422
|
+
depth: currentDepth,
|
|
43423
|
+
index: targetIndex
|
|
43424
|
+
};
|
|
43425
|
+
else this.#anchor = void 0;
|
|
43426
|
+
}
|
|
43427
|
+
this.requestUpdate();
|
|
43428
|
+
if (scroll) this.#scrollNavigatedItemIntoView(`auto`);
|
|
43429
|
+
}
|
|
41883
43430
|
#onKeyDown;
|
|
41884
43431
|
#onResizeStart;
|
|
43432
|
+
#resizeRaf;
|
|
43433
|
+
#resizePendingWidth;
|
|
41885
43434
|
#onResizeMove;
|
|
43435
|
+
#applyResizeWidth;
|
|
41886
43436
|
#onResizeEnd;
|
|
41887
43437
|
#getColumnHeader(depth) {
|
|
41888
43438
|
if (depth === 0) return this.#displayRoot?.item.label ?? ``;
|
|
@@ -41896,9 +43446,12 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41896
43446
|
}
|
|
41897
43447
|
return ``;
|
|
41898
43448
|
}
|
|
43449
|
+
#explicitWidthStyle(width) {
|
|
43450
|
+
return `width:${width}px;flex-grow:0;flex-shrink:0;flex-basis:auto;max-width:none`;
|
|
43451
|
+
}
|
|
41899
43452
|
#columnStyle(depth, isLast) {
|
|
41900
43453
|
const w = this.#columnWidths.get(depth);
|
|
41901
|
-
if (w !== void 0) return
|
|
43454
|
+
if (w !== void 0) return this.#explicitWidthStyle(w);
|
|
41902
43455
|
if (this.stretchLastColumn && isLast) return `width:max-content;flex-grow:1;flex-shrink:0;flex-basis:auto;min-width:0`;
|
|
41903
43456
|
return `width:max-content;flex-grow:0;flex-shrink:0;flex-basis:auto`;
|
|
41904
43457
|
}
|
|
@@ -42148,8 +43701,24 @@ __decorate([state()], MillerBaseElement.prototype, "_selectedNodes", void 0);
|
|
|
42148
43701
|
MillerBaseElement = __decorate([safeCustomElement(`ixfx-miller-base`)], MillerBaseElement);
|
|
42149
43702
|
//#endregion
|
|
42150
43703
|
//#region src/miller/miller-list.ts
|
|
43704
|
+
const HAS_CHECKED = 1;
|
|
43705
|
+
const HAS_UNCHECKED = 2;
|
|
42151
43706
|
/**
|
|
42152
|
-
*
|
|
43707
|
+
* Walk the loaded subtree under `node`, returning which kinds of leaf were
|
|
43708
|
+
* seen (`HAS_CHECKED` | `HAS_UNCHECKED`). Stops as soon as both have been
|
|
43709
|
+
* seen, since the result can no longer change.
|
|
43710
|
+
*/
|
|
43711
|
+
function scanCheckState(node, selectedKeys) {
|
|
43712
|
+
if (node.item.isLeaf || !node.children?.length) return selectedKeys.has(node.item.key) ? HAS_CHECKED : HAS_UNCHECKED;
|
|
43713
|
+
let seen = 0;
|
|
43714
|
+
for (const child of node.children) {
|
|
43715
|
+
seen |= scanCheckState(child, selectedKeys);
|
|
43716
|
+
if (seen === 3) break;
|
|
43717
|
+
}
|
|
43718
|
+
return seen;
|
|
43719
|
+
}
|
|
43720
|
+
/**
|
|
43721
|
+
* Compute the checkbox state for a node based on `selectedKeys`.
|
|
42153
43722
|
*
|
|
42154
43723
|
* - Unloaded branch (`children === undefined`): treated as a leaf — we can only
|
|
42155
43724
|
* report on what is known.
|
|
@@ -42158,12 +43727,9 @@ MillerBaseElement = __decorate([safeCustomElement(`ixfx-miller-base`)], MillerBa
|
|
|
42158
43727
|
* indeterminate otherwise.
|
|
42159
43728
|
*/
|
|
42160
43729
|
function computeCheckboxState(node, selectedKeys) {
|
|
42161
|
-
|
|
42162
|
-
|
|
42163
|
-
|
|
42164
|
-
const allUnchecked = childStates.every((s) => s === `unchecked`);
|
|
42165
|
-
if (allChecked) return `checked`;
|
|
42166
|
-
if (allUnchecked) return `unchecked`;
|
|
43730
|
+
const seen = scanCheckState(node, selectedKeys);
|
|
43731
|
+
if (seen === HAS_CHECKED) return `checked`;
|
|
43732
|
+
if (seen === HAS_UNCHECKED) return `unchecked`;
|
|
42167
43733
|
return `indeterminate`;
|
|
42168
43734
|
}
|
|
42169
43735
|
let MillerListElement = class MillerListElement extends MillerBaseElement {
|
|
@@ -50378,9 +51944,12 @@ var TabController = class {
|
|
|
50378
51944
|
}
|
|
50379
51945
|
/** The tab's visible label text, excluding any `slot="toolbar"` content. */
|
|
50380
51946
|
#labelOf(item) {
|
|
50381
|
-
|
|
50382
|
-
|
|
50383
|
-
|
|
51947
|
+
let text = ``;
|
|
51948
|
+
for (const node of item.childNodes) {
|
|
51949
|
+
if (node instanceof Element && node.getAttribute(`slot`) === `toolbar`) continue;
|
|
51950
|
+
text += node.textContent ?? ``;
|
|
51951
|
+
}
|
|
51952
|
+
return text.trim();
|
|
50384
51953
|
}
|
|
50385
51954
|
#registerCommand(tab) {
|
|
50386
51955
|
if (!this.#commands) return;
|
|
@@ -50497,9 +52066,12 @@ let TabPanelsElement = class TabPanelsElement extends LitElement {
|
|
|
50497
52066
|
}
|
|
50498
52067
|
/** The tab's visible label text, excluding any `slot="toolbar"` content. */
|
|
50499
52068
|
#labelOf(item) {
|
|
50500
|
-
|
|
50501
|
-
|
|
50502
|
-
|
|
52069
|
+
let text = ``;
|
|
52070
|
+
for (const node of item.childNodes) {
|
|
52071
|
+
if (node instanceof Element && node.getAttribute(`slot`) === `toolbar`) continue;
|
|
52072
|
+
text += node.textContent ?? ``;
|
|
52073
|
+
}
|
|
52074
|
+
return text.trim();
|
|
50503
52075
|
}
|
|
50504
52076
|
static {
|
|
50505
52077
|
this.styles = css`
|
|
@@ -53368,7 +54940,7 @@ var TreeController = class {
|
|
|
53368
54940
|
const treeNode = this.#nodeToTree.get(node);
|
|
53369
54941
|
if (!treeNode) return;
|
|
53370
54942
|
node.children = this.#childrenOf(treeNode);
|
|
53371
|
-
this.#element.
|
|
54943
|
+
this.#element.invalidateStructure();
|
|
53372
54944
|
}, { signal });
|
|
53373
54945
|
this.#element.addEventListener(`collapse`, (_e) => {}, { signal });
|
|
53374
54946
|
}
|
|
@@ -53390,7 +54962,10 @@ var TreeController = class {
|
|
|
53390
54962
|
if (!node) return false;
|
|
53391
54963
|
if (node.children === void 0) {
|
|
53392
54964
|
const mapped = this.#nodeToTree.get(node);
|
|
53393
|
-
if (mapped)
|
|
54965
|
+
if (mapped) {
|
|
54966
|
+
node.children = this.#childrenOf(mapped);
|
|
54967
|
+
this.#element.invalidateStructure();
|
|
54968
|
+
}
|
|
53394
54969
|
}
|
|
53395
54970
|
currentColumn = node.children ?? [];
|
|
53396
54971
|
}
|