@ixfx/components 0.13.2 → 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 +97 -14
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +2183 -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-Ohxqsy0g.d.ts → flat-list-Y98IqH00.d.ts} +2 -2
- package/dist/{flat-list-Ohxqsy0g.d.ts.map → flat-list-Y98IqH00.d.ts.map} +1 -1
- package/dist/flat-list.d.ts +1 -1
- package/dist/flat-list.js +5 -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/grid-list.md +81 -4
- package/docs-user/index.json +1 -1
- 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/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);
|
|
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);
|
|
18742
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>` : ``}
|
|
@@ -26753,12 +27315,16 @@ let FlatListElement = class FlatListElement extends LitElement {
|
|
|
26753
27315
|
#onKeyDown;
|
|
26754
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. */
|
|
26755
27317
|
#onCrumbSelect;
|
|
27318
|
+
#markedEl;
|
|
26756
27319
|
/** Flag the marked row. Rows are rebuilt with the level, so this runs after each rebuild. */
|
|
26757
27320
|
async #applyMarked() {
|
|
26758
27321
|
const list = this.list;
|
|
26759
27322
|
if (!list) return;
|
|
26760
27323
|
await list.updateComplete;
|
|
26761
|
-
|
|
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`, ``);
|
|
26762
27328
|
}
|
|
26763
27329
|
/** After the inner list has rebuilt, place the cursor / selection / focus. */
|
|
26764
27330
|
async #settleList() {
|
|
@@ -27526,16 +28092,24 @@ function warnDuplicateKeys(keys) {
|
|
|
27526
28092
|
}
|
|
27527
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.`);
|
|
27528
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
|
+
}
|
|
27529
28103
|
var ElementGridListSource = class {
|
|
27530
28104
|
#host;
|
|
27531
28105
|
#items = [];
|
|
27532
|
-
#slotNames = /* @__PURE__ */ new WeakMap();
|
|
27533
28106
|
#syntheticKeys = /* @__PURE__ */ new WeakMap();
|
|
27534
28107
|
#seq = 0;
|
|
27535
28108
|
#predicate;
|
|
27536
28109
|
#searchKeys;
|
|
27537
28110
|
#effectiveIndices = [];
|
|
27538
28111
|
#keysCache;
|
|
28112
|
+
#indexCache;
|
|
27539
28113
|
#grouping;
|
|
27540
28114
|
#groups = [];
|
|
27541
28115
|
#groupByKey = /* @__PURE__ */ new Map();
|
|
@@ -27554,7 +28128,8 @@ var ElementGridListSource = class {
|
|
|
27554
28128
|
return eff === void 0 ? `` : this.#itemKey(this.#items[eff]);
|
|
27555
28129
|
}
|
|
27556
28130
|
indexOfKey(key) {
|
|
27557
|
-
|
|
28131
|
+
this.#indexCache ??= indexByKey(this.keys());
|
|
28132
|
+
return this.#indexCache.get(key) ?? -1;
|
|
27558
28133
|
}
|
|
27559
28134
|
keys() {
|
|
27560
28135
|
this.#keysCache ??= this.#effectiveIndices.map((i) => this.#itemKey(this.#items[i]));
|
|
@@ -27577,11 +28152,11 @@ var ElementGridListSource = class {
|
|
|
27577
28152
|
const eff = this.#effectiveIndices[index];
|
|
27578
28153
|
return eff !== void 0 && !this.#items[eff].hasAttribute(`not-checked`);
|
|
27579
28154
|
}
|
|
27580
|
-
|
|
27581
|
-
|
|
27582
|
-
|
|
27583
|
-
|
|
27584
|
-
}
|
|
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) {}
|
|
27585
28160
|
iconAt(_index) {}
|
|
27586
28161
|
iconColorAt(_index) {}
|
|
27587
28162
|
textColorAt(_index) {}
|
|
@@ -27603,10 +28178,6 @@ var ElementGridListSource = class {
|
|
|
27603
28178
|
}
|
|
27604
28179
|
refresh() {
|
|
27605
28180
|
this.#items = Array.from(this.#host.querySelectorAll(ITEM_SELECTOR$1)).filter((el) => !el.hidden);
|
|
27606
|
-
for (const el of this.#items) {
|
|
27607
|
-
const name = this.slotNameFor(el);
|
|
27608
|
-
if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
|
|
27609
|
-
}
|
|
27610
28181
|
warnDuplicateKeys(this.#items.map((el) => this.#itemKey(el)));
|
|
27611
28182
|
this.#recomputeEffective();
|
|
27612
28183
|
}
|
|
@@ -27618,16 +28189,6 @@ var ElementGridListSource = class {
|
|
|
27618
28189
|
rawElementAt(index) {
|
|
27619
28190
|
return this.#items[index];
|
|
27620
28191
|
}
|
|
27621
|
-
/** Stable per-element `<slot>` name. */
|
|
27622
|
-
slotNameFor(el) {
|
|
27623
|
-
let name = this.#slotNames.get(el);
|
|
27624
|
-
if (name === void 0) {
|
|
27625
|
-
this.#seq += 1;
|
|
27626
|
-
name = `gl-item-${this.#seq}`;
|
|
27627
|
-
this.#slotNames.set(el, name);
|
|
27628
|
-
}
|
|
27629
|
-
return name;
|
|
27630
|
-
}
|
|
27631
28192
|
resolveKeyToElement(key) {
|
|
27632
28193
|
if (isSyntheticKey(key)) return this.#items.find((el) => this.#syntheticKeys.get(el) === key);
|
|
27633
28194
|
return this.#items.find((el) => keyForElement(el) === key);
|
|
@@ -27645,6 +28206,7 @@ var ElementGridListSource = class {
|
|
|
27645
28206
|
}
|
|
27646
28207
|
this.#applyGroupingTo(effective);
|
|
27647
28208
|
this.#keysCache = void 0;
|
|
28209
|
+
this.#indexCache = void 0;
|
|
27648
28210
|
}
|
|
27649
28211
|
#applyGroupingTo(effective) {
|
|
27650
28212
|
if (this.#grouping === void 0) {
|
|
@@ -27680,6 +28242,7 @@ var DataGridListSource = class {
|
|
|
27680
28242
|
#searchKeys;
|
|
27681
28243
|
#effectiveIndices = [];
|
|
27682
28244
|
#keysCache;
|
|
28245
|
+
#indexCache;
|
|
27683
28246
|
#grouping;
|
|
27684
28247
|
#groups = [];
|
|
27685
28248
|
#groupByKey = /* @__PURE__ */ new Map();
|
|
@@ -27700,7 +28263,8 @@ var DataGridListSource = class {
|
|
|
27700
28263
|
return eff === void 0 ? `` : this.#adapter.key(this.#items[eff], eff);
|
|
27701
28264
|
}
|
|
27702
28265
|
indexOfKey(key) {
|
|
27703
|
-
|
|
28266
|
+
this.#indexCache ??= indexByKey(this.keys());
|
|
28267
|
+
return this.#indexCache.get(key) ?? -1;
|
|
27704
28268
|
}
|
|
27705
28269
|
keys() {
|
|
27706
28270
|
this.#keysCache ??= this.#effectiveIndices.map((i) => this.#adapter.key(this.#items[i], i));
|
|
@@ -27793,6 +28357,7 @@ var DataGridListSource = class {
|
|
|
27793
28357
|
this.#groupByKey = grouped.groupByKey;
|
|
27794
28358
|
}
|
|
27795
28359
|
this.#keysCache = void 0;
|
|
28360
|
+
this.#indexCache = void 0;
|
|
27796
28361
|
}
|
|
27797
28362
|
};
|
|
27798
28363
|
//#endregion
|
|
@@ -27819,6 +28384,15 @@ function sameKeys(a, b) {
|
|
|
27819
28384
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
27820
28385
|
return true;
|
|
27821
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
|
+
}
|
|
27822
28396
|
let GridListElement = class GridListElement extends LitElement {
|
|
27823
28397
|
constructor(..._args) {
|
|
27824
28398
|
super(..._args);
|
|
@@ -27853,6 +28427,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
27853
28427
|
this.#snapTimer = 0;
|
|
27854
28428
|
this.#items = [];
|
|
27855
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();
|
|
27856
28435
|
this.#entering = /* @__PURE__ */ new Set();
|
|
27857
28436
|
this.#reflowTimer = 0;
|
|
27858
28437
|
this.#sourceElement = new ElementGridListSource(this);
|
|
@@ -27976,6 +28555,8 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
27976
28555
|
}
|
|
27977
28556
|
});
|
|
27978
28557
|
this.#groupByChanged = false;
|
|
28558
|
+
this.#markedTickled = /* @__PURE__ */ new Set();
|
|
28559
|
+
this.#markedSelected = /* @__PURE__ */ new Set();
|
|
27979
28560
|
this.#selectedSnapshot = /* @__PURE__ */ new Set();
|
|
27980
28561
|
this.#onScroll = (e) => {
|
|
27981
28562
|
const el = e.target;
|
|
@@ -28465,6 +29046,14 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28465
29046
|
.cell.entering {
|
|
28466
29047
|
animation: ixfx-list-item-in var(--grid-list-item-move-duration, 200ms) ease both;
|
|
28467
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
|
+
}
|
|
28468
29057
|
@media (prefers-reduced-motion: reduce) {
|
|
28469
29058
|
:host([reflowing]) .group-header { transition: none; }
|
|
28470
29059
|
:host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
|
|
@@ -28496,11 +29085,39 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28496
29085
|
#viewportRef;
|
|
28497
29086
|
#resizeObserver;
|
|
28498
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;
|
|
28499
29095
|
#scrollRaf;
|
|
28500
29096
|
/** Debounce handle for {@link snapToItem} — fires once scrolling settles. */
|
|
28501
29097
|
#snapTimer;
|
|
28502
29098
|
#items;
|
|
28503
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;
|
|
28504
29121
|
/** Items added mid-list that are still playing their enter animation. */
|
|
28505
29122
|
#entering;
|
|
28506
29123
|
/** Timer that ends the `[reflowing]` window opened by a mid-list insertion. */
|
|
@@ -28688,9 +29305,15 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28688
29305
|
this.addEventListener(`pointermove`, this.#onHostPointerMove, { passive: true });
|
|
28689
29306
|
this.addEventListener(`pointerleave`, this.#onHostPointerLeave);
|
|
28690
29307
|
this.#mutationObserver ??= new MutationObserver((records) => this.#onChildrenChanged(records));
|
|
28691
|
-
this.#mutationObserver.observe(this, {
|
|
28692
|
-
|
|
28693
|
-
|
|
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
|
+
]
|
|
28694
29317
|
});
|
|
28695
29318
|
this.#searchRegistry ??= createCommandRegistry();
|
|
28696
29319
|
this.#ensureSearch();
|
|
@@ -28706,6 +29329,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28706
29329
|
this.#resizeObserver?.disconnect();
|
|
28707
29330
|
this.#resizeObserver = void 0;
|
|
28708
29331
|
this.#mutationObserver?.disconnect();
|
|
29332
|
+
this.#itemAttrObserver?.disconnect();
|
|
28709
29333
|
this.#cancelScrollRaf();
|
|
28710
29334
|
this.#cancelSnapTimer();
|
|
28711
29335
|
this.#unregisterSearchCommands?.();
|
|
@@ -28717,6 +29341,10 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28717
29341
|
}
|
|
28718
29342
|
if (this.#nativeDragging || this.#nativeDrag.isDragging) this.#clearNativeDragState();
|
|
28719
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
|
+
}
|
|
28720
29348
|
#onChildrenChanged(records) {
|
|
28721
29349
|
const previousLength = this.#items.length;
|
|
28722
29350
|
this.#syncItems();
|
|
@@ -28733,7 +29361,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28733
29361
|
const tickledId = this.tickled.tickledId;
|
|
28734
29362
|
if (tickledId && gone.includes(tickledId)) this.tickled.focus(void 0);
|
|
28735
29363
|
}
|
|
28736
|
-
this._syncItemStates();
|
|
29364
|
+
this._syncItemStates(true);
|
|
28737
29365
|
this.#noteChildMutation(previousLength, records);
|
|
28738
29366
|
this.requestUpdate();
|
|
28739
29367
|
this.updateComplete.then(() => this.#emitViewportChange());
|
|
@@ -28805,6 +29433,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28805
29433
|
const w = entry.contentRect.width;
|
|
28806
29434
|
const h = entry.contentRect.height;
|
|
28807
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);
|
|
28808
29441
|
this._viewportWidth = w;
|
|
28809
29442
|
this._viewportHeight = h;
|
|
28810
29443
|
}
|
|
@@ -28966,7 +29599,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
28966
29599
|
}
|
|
28967
29600
|
updated(changed) {
|
|
28968
29601
|
super.updated(changed);
|
|
28969
|
-
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();
|
|
28970
29607
|
if (changed.has(`selectionMode`) || changed.has(`interactionMode`)) {
|
|
28971
29608
|
this.selection.clearAnchor();
|
|
28972
29609
|
this.#syncAriaMultiselectable();
|
|
@@ -29318,13 +29955,36 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29318
29955
|
this._scrollTop = next;
|
|
29319
29956
|
}
|
|
29320
29957
|
}
|
|
29321
|
-
|
|
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) {
|
|
29322
29969
|
const selected = this.selection.selectedKeys;
|
|
29323
|
-
|
|
29324
|
-
|
|
29325
|
-
|
|
29326
|
-
|
|
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);
|
|
29327
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);
|
|
29328
29988
|
this.setAttribute(`data-input-mode`, this.tickled.inputMode);
|
|
29329
29989
|
}
|
|
29330
29990
|
#emitSelect(selected, previous) {
|
|
@@ -29394,8 +30054,57 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29394
30054
|
`;
|
|
29395
30055
|
}
|
|
29396
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) {
|
|
29397
30078
|
const slice = win.ranges.flatMap(([start, end]) => Array.from({ length: end - start }, (_, i) => start + i));
|
|
29398
|
-
|
|
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;
|
|
29399
30108
|
}
|
|
29400
30109
|
#renderDataCell(index, win) {
|
|
29401
30110
|
const key = this.#source.keyAt(index);
|
|
@@ -29415,10 +30124,12 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29415
30124
|
const icon = appearanceAbsent(rawIcon) ? void 0 : rawIcon;
|
|
29416
30125
|
return html`
|
|
29417
30126
|
<div
|
|
29418
|
-
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` : ``}"
|
|
29419
30128
|
part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
|
|
29420
30129
|
role="option"
|
|
29421
30130
|
aria-selected=${selected ? `true` : `false`}
|
|
30131
|
+
aria-setsize=${this.#source.count}
|
|
30132
|
+
aria-posinset=${index + 1}
|
|
29422
30133
|
style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
|
|
29423
30134
|
data-index=${index}
|
|
29424
30135
|
data-key=${key}
|
|
@@ -29495,8 +30206,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29495
30206
|
current.startLoop?.();
|
|
29496
30207
|
}
|
|
29497
30208
|
#renderElementCells(win) {
|
|
29498
|
-
const slice = win
|
|
29499
|
-
|
|
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));
|
|
29500
30212
|
}
|
|
29501
30213
|
#renderHeaders(win) {
|
|
29502
30214
|
if (win.headers.length === 0) return nothing;
|
|
@@ -29505,6 +30217,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29505
30217
|
<ixfx-list-group-header
|
|
29506
30218
|
class="group-header"
|
|
29507
30219
|
part="group-header"
|
|
30220
|
+
role="presentation"
|
|
29508
30221
|
data-group-key=${h.key}
|
|
29509
30222
|
label=${h.label}
|
|
29510
30223
|
count=${h.count}
|
|
@@ -29533,10 +30246,12 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29533
30246
|
const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
|
|
29534
30247
|
return html`
|
|
29535
30248
|
<div
|
|
29536
|
-
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` : ``}"
|
|
29537
30250
|
part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
|
|
29538
30251
|
role="option"
|
|
29539
30252
|
aria-selected=${selected ? `true` : `false`}
|
|
30253
|
+
aria-setsize=${this.#source.count}
|
|
30254
|
+
aria-posinset=${index + 1}
|
|
29540
30255
|
style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
|
|
29541
30256
|
data-index=${index}
|
|
29542
30257
|
data-key=${key}
|
|
@@ -29549,7 +30264,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
29549
30264
|
@pointerenter=${() => this.#handleCellPointerEnter(key)}
|
|
29550
30265
|
>
|
|
29551
30266
|
<div class="thumb" part="thumb" style=${thumbStyle}>
|
|
29552
|
-
|
|
30267
|
+
<slot name=${this.#cellSlotName(key)}></slot>
|
|
29553
30268
|
</div>
|
|
29554
30269
|
${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
|
|
29555
30270
|
</div>
|
|
@@ -30401,15 +31116,10 @@ var TreeDropController = class {
|
|
|
30401
31116
|
if (clientY > rows[rows.length - 1].rect.bottom) return rows[rows.length - 1];
|
|
30402
31117
|
return rows.find((r) => clientY >= r.rect.top && clientY <= r.rect.bottom) ?? rows[rows.length - 1];
|
|
30403
31118
|
}
|
|
30404
|
-
#parentRow(rows, index) {
|
|
30405
|
-
const target = rows[index];
|
|
30406
|
-
for (let i = index - 1; i >= 0; i--) if (rows[i].depth === target.depth - 1) return rows[i];
|
|
30407
|
-
}
|
|
30408
31119
|
#resolveIntent(event, rows, source) {
|
|
30409
31120
|
if (rows.length === 0) return void 0;
|
|
30410
31121
|
const row = this.#rowAtPointer(rows, event.clientY);
|
|
30411
31122
|
if (!row) return void 0;
|
|
30412
|
-
const rowIndex = rows.indexOf(row);
|
|
30413
31123
|
const f = clamp$4((event.clientY - row.rect.top) / row.rect.height, 0, 1);
|
|
30414
31124
|
let position = f < .25 ? `before` : f > .75 ? `after` : `inside`;
|
|
30415
31125
|
const isLeaf = row.node.item.isLeaf === true;
|
|
@@ -30432,18 +31142,21 @@ var TreeDropController = class {
|
|
|
30432
31142
|
const indent = this.#config.getIndentPx() || 1;
|
|
30433
31143
|
const treeLeft = rows[0].rect.left;
|
|
30434
31144
|
const effDepth = clamp$4(Math.round((event.clientX - treeLeft) / indent), 0, row.depth);
|
|
30435
|
-
let
|
|
30436
|
-
|
|
30437
|
-
|
|
30438
|
-
|
|
30439
|
-
|
|
30440
|
-
|
|
30441
|
-
|
|
30442
|
-
|
|
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
|
+
}
|
|
30443
31157
|
depth = anchor.depth;
|
|
30444
|
-
|
|
30445
|
-
|
|
30446
|
-
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);
|
|
30447
31160
|
index = (refIdx < 0 ? 0 : refIdx) + (position === `after` ? 1 : 0);
|
|
30448
31161
|
}
|
|
30449
31162
|
if (source === `internal`) {
|
|
@@ -30518,9 +31231,333 @@ var TreeDropController = class {
|
|
|
30518
31231
|
}
|
|
30519
31232
|
};
|
|
30520
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
|
|
30521
31552
|
//#region src/tree/tree-base.ts
|
|
30522
31553
|
/** Above this many rows entering / leaving at once, skip the animation entirely. */
|
|
30523
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;
|
|
30524
31561
|
let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
30525
31562
|
constructor(..._args) {
|
|
30526
31563
|
super(..._args);
|
|
@@ -30534,11 +31571,33 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30534
31571
|
this.dndExpandHoverMs = 500;
|
|
30535
31572
|
this.reorderable = true;
|
|
30536
31573
|
this.stickyHeaders = true;
|
|
31574
|
+
this.virtualize = `auto`;
|
|
31575
|
+
this.virtualizeThreshold = 200;
|
|
30537
31576
|
this.applyInternalDrop = true;
|
|
30538
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
|
+
};
|
|
30539
31598
|
this.tickled = new TickledController(this, {
|
|
30540
31599
|
getItems: () => {
|
|
30541
|
-
return this.#
|
|
31600
|
+
return this.#getFlat().rows.map(({ node }) => ({
|
|
30542
31601
|
id: node.item.key,
|
|
30543
31602
|
enabled: true
|
|
30544
31603
|
}));
|
|
@@ -30555,6 +31614,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30555
31614
|
}) });
|
|
30556
31615
|
this.#config = { indent: 20 };
|
|
30557
31616
|
this.#renderedKeys = [];
|
|
31617
|
+
this.#renderedRowKeys = [];
|
|
31618
|
+
this.#renderedRowIndex = /* @__PURE__ */ new Map();
|
|
30558
31619
|
this.#lastNodeByKey = /* @__PURE__ */ new Map();
|
|
30559
31620
|
this.#enteringKeys = /* @__PURE__ */ new Set();
|
|
30560
31621
|
this.#leaving = /* @__PURE__ */ new Map();
|
|
@@ -30578,9 +31639,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30578
31639
|
this.#stickyDirty = true;
|
|
30579
31640
|
this.#stickyRaf = 0;
|
|
30580
31641
|
this.#stickyScrollBound = false;
|
|
31642
|
+
this.#revealToken = 0;
|
|
30581
31643
|
this.#onKeyDown = (event) => {
|
|
30582
31644
|
if (this.search.isOpen) return;
|
|
30583
|
-
const nodes = this.#
|
|
31645
|
+
const nodes = this.#getFlat().rows;
|
|
30584
31646
|
if (nodes.length === 0) return;
|
|
30585
31647
|
if ((event.ctrlKey || event.metaKey) && event.key === `a`) {
|
|
30586
31648
|
event.preventDefault();
|
|
@@ -30592,29 +31654,41 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30592
31654
|
`ArrowUp`,
|
|
30593
31655
|
`ArrowLeft`,
|
|
30594
31656
|
`ArrowRight`,
|
|
31657
|
+
`Home`,
|
|
31658
|
+
`End`,
|
|
31659
|
+
`PageUp`,
|
|
31660
|
+
`PageDown`,
|
|
30595
31661
|
`Enter`,
|
|
30596
31662
|
` `
|
|
30597
31663
|
].includes(event.key)) this.tickled.setInputMode(`keyboard`);
|
|
30598
31664
|
const currentIdx = this.#getFocusedIndex();
|
|
30599
31665
|
switch (event.key) {
|
|
30600
|
-
case `ArrowDown`:
|
|
31666
|
+
case `ArrowDown`:
|
|
30601
31667
|
event.preventDefault();
|
|
30602
|
-
|
|
30603
|
-
|
|
30604
|
-
|
|
30605
|
-
|
|
30606
|
-
|
|
30607
|
-
|
|
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);
|
|
30608
31686
|
break;
|
|
30609
31687
|
}
|
|
30610
|
-
case `
|
|
31688
|
+
case `PageUp`: {
|
|
30611
31689
|
event.preventDefault();
|
|
30612
|
-
const
|
|
30613
|
-
|
|
30614
|
-
this.#setFocusedIndex(prevIdx);
|
|
30615
|
-
if (event.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`)) this.#extendRangeSelection(nodes, currentIdx, prevIdx);
|
|
30616
|
-
else if (this.interactionMode === `vscode`) this.#anchor = void 0;
|
|
30617
|
-
this.requestUpdate();
|
|
31690
|
+
const page = this.#pageSize();
|
|
31691
|
+
this.#navigateToIndex(nodes, currentIdx, Math.max(currentIdx - page, 0), event.shiftKey);
|
|
30618
31692
|
break;
|
|
30619
31693
|
}
|
|
30620
31694
|
case `ArrowRight`: {
|
|
@@ -30631,7 +31705,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30631
31705
|
event.preventDefault();
|
|
30632
31706
|
const current = nodes[currentIdx];
|
|
30633
31707
|
if (current && this._expandedKeys.has(current.node.item.key)) {
|
|
30634
|
-
this._expandedKeys
|
|
31708
|
+
this.#setExpanded(withoutKey(this._expandedKeys, current.node.item.key));
|
|
30635
31709
|
this.dispatchEvent(new CustomEvent(`collapse`, {
|
|
30636
31710
|
detail: {
|
|
30637
31711
|
node: current.node,
|
|
@@ -30685,7 +31759,9 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30685
31759
|
if (!li) return;
|
|
30686
31760
|
const key = li.dataset.key;
|
|
30687
31761
|
if (!key) return;
|
|
30688
|
-
const
|
|
31762
|
+
const flat = this.#getFlat();
|
|
31763
|
+
const idx = flat.indexByKey.get(key);
|
|
31764
|
+
const node = idx === void 0 ? void 0 : flat.rows[idx];
|
|
30689
31765
|
if (!node) return;
|
|
30690
31766
|
const detail = {
|
|
30691
31767
|
node: node.node,
|
|
@@ -30706,6 +31782,206 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30706
31782
|
get selectedNodes() {
|
|
30707
31783
|
return this._selectedNodes;
|
|
30708
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
|
+
}
|
|
30709
31985
|
#unregisterSearchCommands;
|
|
30710
31986
|
#effectiveSearchModes() {
|
|
30711
31987
|
const raw = this.searchModes?.trim();
|
|
@@ -30726,6 +32002,9 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30726
32002
|
#ownedRegistry;
|
|
30727
32003
|
#config;
|
|
30728
32004
|
#renderedKeys;
|
|
32005
|
+
/** Keys in the exact DOM order of the last render (ghost rows woven in). */
|
|
32006
|
+
#renderedRowKeys;
|
|
32007
|
+
#renderedRowIndex;
|
|
30729
32008
|
#lastNodeByKey;
|
|
30730
32009
|
#enteringKeys;
|
|
30731
32010
|
#leaving;
|
|
@@ -30760,73 +32039,65 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30760
32039
|
}
|
|
30761
32040
|
}
|
|
30762
32041
|
#onStickyScroll;
|
|
30763
|
-
/**
|
|
30764
|
-
|
|
30765
|
-
|
|
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
|
+
};
|
|
30766
32053
|
}
|
|
30767
32054
|
/**
|
|
30768
32055
|
* Recompute which ancestors stick to the top: the ancestors of the first
|
|
30769
32056
|
* row whose bottom clears the container top, minus any ancestor that is
|
|
30770
32057
|
* itself still visible below the overlay. Synchronous — reads current
|
|
30771
|
-
* geometry, renders nothing, expands nothing.
|
|
32058
|
+
* geometry, renders nothing, expands nothing. O(log N) offset reads + O(depth).
|
|
30772
32059
|
*/
|
|
30773
32060
|
#updateStickyHeaders() {
|
|
30774
32061
|
this.#stickyDirty = false;
|
|
30775
32062
|
const empty = /* @__PURE__ */ new Map();
|
|
30776
|
-
|
|
30777
|
-
|
|
30778
|
-
return;
|
|
30779
|
-
}
|
|
32063
|
+
const clear = () => this.#setStickyKeys([], empty);
|
|
32064
|
+
if (!this.stickyHeaders || !this.root) return clear();
|
|
30780
32065
|
const container = this.#container();
|
|
30781
|
-
|
|
30782
|
-
|
|
30783
|
-
|
|
30784
|
-
|
|
30785
|
-
const
|
|
30786
|
-
|
|
30787
|
-
|
|
30788
|
-
|
|
30789
|
-
|
|
30790
|
-
const
|
|
30791
|
-
const
|
|
30792
|
-
|
|
30793
|
-
|
|
30794
|
-
|
|
30795
|
-
|
|
30796
|
-
|
|
30797
|
-
walk(this.root);
|
|
30798
|
-
const containerTop = container.getBoundingClientRect().top;
|
|
30799
|
-
const visibleThreshold = containerTop + (this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0);
|
|
30800
|
-
let topKey;
|
|
30801
|
-
for (const { node } of nodes) {
|
|
30802
|
-
const li = this.#stickyRowEl(node.item.key);
|
|
30803
|
-
if (!li || li.classList.contains(`leaving`)) continue;
|
|
30804
|
-
if (li.getBoundingClientRect().bottom > containerTop) {
|
|
30805
|
-
topKey = node.item.key;
|
|
30806
|
-
break;
|
|
30807
|
-
}
|
|
30808
|
-
}
|
|
30809
|
-
if (topKey === void 0) {
|
|
30810
|
-
this.#setStickyKeys([], empty);
|
|
30811
|
-
return;
|
|
30812
|
-
}
|
|
30813
|
-
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();
|
|
30814
32082
|
const chain = [];
|
|
30815
|
-
let cursor =
|
|
30816
|
-
while (cursor !== void 0
|
|
30817
|
-
|
|
30818
|
-
|
|
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;
|
|
30819
32089
|
}
|
|
30820
|
-
const byKey = new Map(nodes.map((entry) => [entry.node.item.key, entry]));
|
|
30821
32090
|
const entries = /* @__PURE__ */ new Map();
|
|
30822
32091
|
const stuck = [];
|
|
30823
|
-
for (const
|
|
30824
|
-
const
|
|
30825
|
-
|
|
30826
|
-
|
|
30827
|
-
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;
|
|
30828
32096
|
stuck.push(key);
|
|
30829
|
-
entries.set(key,
|
|
32097
|
+
entries.set(key, {
|
|
32098
|
+
node: row.node,
|
|
32099
|
+
depth: row.depth
|
|
32100
|
+
});
|
|
30830
32101
|
}
|
|
30831
32102
|
this.#setStickyKeys(stuck, entries);
|
|
30832
32103
|
}
|
|
@@ -30839,12 +32110,14 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30839
32110
|
#renderStickyHeaders() {
|
|
30840
32111
|
if (!this.stickyHeaders || this._stickyKeys.length === 0) return nothing;
|
|
30841
32112
|
return html`
|
|
30842
|
-
<div class="sticky-
|
|
30843
|
-
|
|
32113
|
+
<div class="sticky-anchor">
|
|
32114
|
+
<div class="sticky-headers" part="sticky-headers">
|
|
32115
|
+
${this._stickyKeys.map((key) => {
|
|
30844
32116
|
const entry = this.#stickyEntries.get(key);
|
|
30845
32117
|
if (!entry) return nothing;
|
|
30846
32118
|
return this.#renderStickyHeader(entry.node, entry.depth);
|
|
30847
32119
|
})}
|
|
32120
|
+
</div>
|
|
30848
32121
|
</div>
|
|
30849
32122
|
`;
|
|
30850
32123
|
}
|
|
@@ -30916,15 +32189,44 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30916
32189
|
}
|
|
30917
32190
|
willUpdate(changedProps) {
|
|
30918
32191
|
super.willUpdate(changedProps);
|
|
30919
|
-
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
|
+
}
|
|
30920
32207
|
if (changedProps.has(`root`) || changedProps.has(`model`) || changedProps.has(`filterPredicate`) || changedProps.has(`stickyHeaders`)) this.#stickyDirty = true;
|
|
30921
32208
|
if (this.#stickyDirty) this.#updateStickyHeaders();
|
|
30922
32209
|
}
|
|
30923
|
-
|
|
30924
|
-
|
|
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
|
+
}
|
|
30925
32224
|
const nowKeys = new Set(nodes.map((n) => n.node.item.key));
|
|
30926
32225
|
this.#enteringKeys.clear();
|
|
30927
|
-
if (
|
|
32226
|
+
if (filterChanged) {
|
|
32227
|
+
this.#cancelAllLeavingAnimations();
|
|
32228
|
+
this.#leaving.clear();
|
|
32229
|
+
} else if (this.#treeAnimReady && this.#renderedKeys.length > 0) {
|
|
30928
32230
|
const prevSet = new Set(this.#renderedKeys);
|
|
30929
32231
|
for (const { node } of nodes) if (!prevSet.has(node.item.key)) this.#enteringKeys.add(node.item.key);
|
|
30930
32232
|
if (this.#enteringKeys.size > MAX_ANIMATED_ROWS) this.#enteringKeys.clear();
|
|
@@ -30947,30 +32249,12 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
30947
32249
|
return this.#leaving.has(node.item.key);
|
|
30948
32250
|
}
|
|
30949
32251
|
/**
|
|
30950
|
-
*
|
|
30951
|
-
*
|
|
30952
|
-
|
|
30953
|
-
|
|
30954
|
-
|
|
30955
|
-
|
|
30956
|
-
/** Splice the fading ghost rows back into the visible list near where they were. */
|
|
30957
|
-
#weaveLeaving(nodes) {
|
|
30958
|
-
if (this.#leaving.size === 0) return nodes;
|
|
30959
|
-
const result = [...nodes];
|
|
30960
|
-
const indexOfKey = (key) => result.findIndex((e) => e.node.item.key === key);
|
|
30961
|
-
for (const [key, entry] of this.#leaving) {
|
|
30962
|
-
const prevIdx = this.#renderedKeys.indexOf(key);
|
|
30963
|
-
let insertAt = result.length;
|
|
30964
|
-
for (let j = prevIdx + 1; j < this.#renderedKeys.length; j++) {
|
|
30965
|
-
const at = indexOfKey(this.#renderedKeys[j]);
|
|
30966
|
-
if (at !== -1) {
|
|
30967
|
-
insertAt = at;
|
|
30968
|
-
break;
|
|
30969
|
-
}
|
|
30970
|
-
}
|
|
30971
|
-
result.splice(insertAt, 0, entry);
|
|
30972
|
-
}
|
|
30973
|
-
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;
|
|
30974
32258
|
}
|
|
30975
32259
|
#runItemAnimations() {
|
|
30976
32260
|
if (this.#enteringKeys.size > 0) {
|
|
@@ -31010,7 +32294,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31010
32294
|
});
|
|
31011
32295
|
});
|
|
31012
32296
|
}
|
|
31013
|
-
const visible = this
|
|
32297
|
+
const visible = this.#getFlat().rows;
|
|
31014
32298
|
this.#renderedKeys = visible.map((n) => n.node.item.key);
|
|
31015
32299
|
this.#lastNodeByKey.clear();
|
|
31016
32300
|
for (const entry of visible) this.#lastNodeByKey.set(entry.node.item.key, entry);
|
|
@@ -31018,10 +32302,16 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31018
32302
|
}
|
|
31019
32303
|
updated(changedProps) {
|
|
31020
32304
|
this.#rowsCache = void 0;
|
|
32305
|
+
this.#syncVirtualObservers();
|
|
32306
|
+
if (this.#scrollOnlyUpdate) {
|
|
32307
|
+
this.#scrollOnlyUpdate = false;
|
|
32308
|
+
this.#repaintSearchHighlights();
|
|
32309
|
+
return;
|
|
32310
|
+
}
|
|
31021
32311
|
this.#syncStickyScroll();
|
|
31022
32312
|
if (changedProps.has(`root`) && this.root) {
|
|
31023
32313
|
this.#abortPending();
|
|
31024
|
-
this
|
|
32314
|
+
this.#setExpanded(/* @__PURE__ */ new Set());
|
|
31025
32315
|
this.#clearSelectionInternal();
|
|
31026
32316
|
this.tickled.clearTickled();
|
|
31027
32317
|
this._focusedDepth = 0;
|
|
@@ -31041,8 +32331,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31041
32331
|
this.requestUpdate();
|
|
31042
32332
|
} else if (event.type === `invalidate`) {
|
|
31043
32333
|
if (this._expandedKeys.has(event.key)) {
|
|
31044
|
-
this._expandedKeys
|
|
31045
|
-
this.#stickyDirty = true;
|
|
32334
|
+
this.#setExpanded(withoutKey(this._expandedKeys, event.key));
|
|
31046
32335
|
this.requestUpdate();
|
|
31047
32336
|
}
|
|
31048
32337
|
}
|
|
@@ -31056,7 +32345,16 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31056
32345
|
if (changedProps.has(`selectedNode`) && this.selectedNode) {
|
|
31057
32346
|
if (changedProps.get(`selectedNode`) !== this.selectedNode) this.tickled.setTickled(this.selectedNode.item.key);
|
|
31058
32347
|
}
|
|
31059
|
-
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();
|
|
31060
32358
|
}
|
|
31061
32359
|
connectedCallback() {
|
|
31062
32360
|
super.connectedCallback();
|
|
@@ -31088,8 +32386,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31088
32386
|
return target.closest(`ul.tree-list li[data-key]`)?.getAttribute(`data-key`) ?? void 0;
|
|
31089
32387
|
},
|
|
31090
32388
|
prepareSelection: (originKey) => {
|
|
31091
|
-
const
|
|
31092
|
-
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];
|
|
31093
32393
|
if (!originEntry) return void 0;
|
|
31094
32394
|
const selectedNodes = new Set(this._selectedNodes);
|
|
31095
32395
|
if (!selectedNodes.has(originEntry.node)) selectedNodes.clear();
|
|
@@ -31178,6 +32478,18 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31178
32478
|
getDndMode: () => this.itemDnd,
|
|
31179
32479
|
getExpandHoverMs: () => this.dndExpandHoverMs,
|
|
31180
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),
|
|
31181
32493
|
getIndentPx: () => this.#getIndentPx(),
|
|
31182
32494
|
getScrollContainer: () => this.#container(),
|
|
31183
32495
|
isSelfOrDescendant: (ancestorKey, key) => this.#isSelfOrDescendant(ancestorKey, key),
|
|
@@ -31250,44 +32562,121 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31250
32562
|
const parsed = Number.parseInt(getComputedStyle(this).getPropertyValue(`--tree-item-height`), 10);
|
|
31251
32563
|
return Number.isNaN(parsed) ? 30 : parsed;
|
|
31252
32564
|
}
|
|
31253
|
-
/**
|
|
31254
|
-
#
|
|
31255
|
-
if (
|
|
31256
|
-
const
|
|
31257
|
-
|
|
31258
|
-
|
|
31259
|
-
|
|
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({
|
|
31260
32599
|
key: node.item.key,
|
|
31261
32600
|
node,
|
|
31262
32601
|
depth,
|
|
31263
|
-
|
|
32602
|
+
index: i,
|
|
32603
|
+
rect
|
|
31264
32604
|
});
|
|
31265
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
|
+
}
|
|
31266
32639
|
this.#rowsCache = rows;
|
|
31267
32640
|
return rows;
|
|
31268
32641
|
}
|
|
31269
32642
|
/** Route spring-load through the real expand path so lazy children load and `expand` fires with the right depth. */
|
|
31270
32643
|
#springLoadExpand(key) {
|
|
31271
32644
|
if (this._expandedKeys.has(key)) return;
|
|
31272
|
-
const
|
|
32645
|
+
const flat = this.#getFlat();
|
|
32646
|
+
const idx = flat.indexByKey.get(key);
|
|
32647
|
+
const entry = idx === void 0 ? void 0 : flat.rows[idx];
|
|
31273
32648
|
if (!entry || entry.node.item.isLeaf === true) return;
|
|
31274
32649
|
this.#toggleExpand(entry.node, entry.depth);
|
|
31275
32650
|
this.#rowsCache = void 0;
|
|
31276
32651
|
this.requestUpdate();
|
|
31277
32652
|
}
|
|
32653
|
+
/** O(depth): walk up the whole-tree parent map. */
|
|
31278
32654
|
#isSelfOrDescendant(ancestorKey, key) {
|
|
31279
32655
|
if (ancestorKey === key) return true;
|
|
31280
|
-
|
|
31281
|
-
let
|
|
31282
|
-
while (
|
|
31283
|
-
if (
|
|
31284
|
-
|
|
31285
|
-
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);
|
|
31286
32661
|
}
|
|
31287
32662
|
return false;
|
|
31288
32663
|
}
|
|
31289
32664
|
#findParentByKey(key) {
|
|
31290
|
-
|
|
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;
|
|
31291
32680
|
}
|
|
31292
32681
|
/**
|
|
31293
32682
|
* Decide whether a DataTransfer is acceptable and resolve the dragged set.
|
|
@@ -31311,7 +32700,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31311
32700
|
return {
|
|
31312
32701
|
source: `internal`,
|
|
31313
32702
|
keys,
|
|
31314
|
-
nodes: keys.map((k) => this.#
|
|
32703
|
+
nodes: keys.map((k) => this.#nodeByKey(k)).filter((n) => n !== void 0)
|
|
31315
32704
|
};
|
|
31316
32705
|
}
|
|
31317
32706
|
if (phase === `drop`) try {
|
|
@@ -31355,7 +32744,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31355
32744
|
}
|
|
31356
32745
|
#anyRowDraggable() {
|
|
31357
32746
|
if (this.itemDnd === `none`) return false;
|
|
31358
|
-
const allNodes = this.#
|
|
32747
|
+
const allNodes = this.#getFlat().rows;
|
|
31359
32748
|
if (allNodes.length === 0) return false;
|
|
31360
32749
|
if (!this.canDrag) return true;
|
|
31361
32750
|
return allNodes.some(({ node }) => this.canDrag({
|
|
@@ -31377,6 +32766,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31377
32766
|
this.#postDragSuppressTimer = void 0;
|
|
31378
32767
|
}
|
|
31379
32768
|
this.#container()?.removeEventListener(`scroll`, this.#onContainerScroll);
|
|
32769
|
+
this.#teardownVirtualObservers();
|
|
31380
32770
|
if (this.#stickyScrollBound) {
|
|
31381
32771
|
this.#stickyScrollContainer?.removeEventListener(`scroll`, this.#onStickyScroll);
|
|
31382
32772
|
this.#stickyScrollContainer = void 0;
|
|
@@ -31430,12 +32820,12 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31430
32820
|
}
|
|
31431
32821
|
selectAll() {
|
|
31432
32822
|
const prev = new Set(this._selectedNodes);
|
|
31433
|
-
if (this.interactionMode === `checked`) for (const { node } of this.#
|
|
32823
|
+
if (this.interactionMode === `checked`) for (const { node } of this.#getFlat().rows) {
|
|
31434
32824
|
if (!this.#canSelect(node)) continue;
|
|
31435
32825
|
this.#applyCheckedToggle(node, true);
|
|
31436
32826
|
}
|
|
31437
32827
|
else {
|
|
31438
|
-
for (const { node } of this.#
|
|
32828
|
+
for (const { node } of this.#getFlat().rows) {
|
|
31439
32829
|
if (!this.#canSelect(node)) continue;
|
|
31440
32830
|
this._selectedNodes.add(node);
|
|
31441
32831
|
}
|
|
@@ -31446,7 +32836,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31446
32836
|
}
|
|
31447
32837
|
deselectAll() {
|
|
31448
32838
|
const prev = new Set(this._selectedNodes);
|
|
31449
|
-
if (this.interactionMode === `checked`) for (const { node } of this.#
|
|
32839
|
+
if (this.interactionMode === `checked`) for (const { node } of this.#getFlat().rows) {
|
|
31450
32840
|
if (!this.#canSelect(node)) continue;
|
|
31451
32841
|
this.#applyCheckedToggle(node, false);
|
|
31452
32842
|
}
|
|
@@ -31467,7 +32857,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31467
32857
|
const found = children.find((n) => n.item.key === key);
|
|
31468
32858
|
if (!found) return false;
|
|
31469
32859
|
this.#applyExclusivity(found.item.key);
|
|
31470
|
-
this._expandedKeys
|
|
32860
|
+
this.#setExpanded(withKey(this._expandedKeys, found.item.key));
|
|
31471
32861
|
if (key === keys.at(-1)) {
|
|
31472
32862
|
this.select(found);
|
|
31473
32863
|
this.tickled.setTickled(found.item.key);
|
|
@@ -31492,7 +32882,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31492
32882
|
if (!path) return false;
|
|
31493
32883
|
for (let i = 0; i < path.length - 1; i++) {
|
|
31494
32884
|
this.#applyExclusivity(path[i]);
|
|
31495
|
-
this._expandedKeys
|
|
32885
|
+
this.#setExpanded(withKey(this._expandedKeys, path[i]));
|
|
31496
32886
|
}
|
|
31497
32887
|
this.tickled.setTickled(key);
|
|
31498
32888
|
this.#stickyDirty = true;
|
|
@@ -31528,10 +32918,12 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31528
32918
|
return true;
|
|
31529
32919
|
}
|
|
31530
32920
|
#clearSelectionInternal() {
|
|
32921
|
+
this._selectionVersion++;
|
|
31531
32922
|
this._selectedNodes.clear();
|
|
31532
32923
|
this.selectedNode = void 0;
|
|
31533
32924
|
}
|
|
31534
32925
|
#dispatchSelect(previous) {
|
|
32926
|
+
this._selectionVersion++;
|
|
31535
32927
|
const detail = {
|
|
31536
32928
|
selected: new Set(this._selectedNodes),
|
|
31537
32929
|
previous
|
|
@@ -31637,9 +33029,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31637
33029
|
return;
|
|
31638
33030
|
}
|
|
31639
33031
|
if (isShift && this.#anchor) {
|
|
31640
|
-
const
|
|
31641
|
-
const
|
|
31642
|
-
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;
|
|
31643
33036
|
if (anchorIdx >= 0 && targetIdx >= 0) {
|
|
31644
33037
|
const prev = new Set(this._selectedNodes);
|
|
31645
33038
|
const from = Math.min(anchorIdx, targetIdx);
|
|
@@ -31689,9 +33082,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31689
33082
|
return;
|
|
31690
33083
|
}
|
|
31691
33084
|
if (isShift && this.#anchor) {
|
|
31692
|
-
const
|
|
31693
|
-
const
|
|
31694
|
-
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;
|
|
31695
33089
|
if (anchorIdx >= 0 && targetIdx >= 0) {
|
|
31696
33090
|
const prev = new Set(this._selectedNodes);
|
|
31697
33091
|
const from = Math.min(anchorIdx, targetIdx);
|
|
@@ -31734,7 +33128,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31734
33128
|
const isExpanded = this._expandedKeys.has(node.item.key);
|
|
31735
33129
|
this.#stickyDirty = true;
|
|
31736
33130
|
if (isExpanded) {
|
|
31737
|
-
this._expandedKeys
|
|
33131
|
+
this.#setExpanded(withoutKey(this._expandedKeys, node.item.key));
|
|
31738
33132
|
this.dispatchEvent(new CustomEvent(`collapse`, {
|
|
31739
33133
|
detail: {
|
|
31740
33134
|
node,
|
|
@@ -31753,7 +33147,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31753
33147
|
}
|
|
31754
33148
|
} else {
|
|
31755
33149
|
this.#applyExclusivity(node.item.key);
|
|
31756
|
-
this._expandedKeys
|
|
33150
|
+
this.#setExpanded(withKey(this._expandedKeys, node.item.key));
|
|
31757
33151
|
this.dispatchEvent(new CustomEvent(`expand`, {
|
|
31758
33152
|
detail: {
|
|
31759
33153
|
node,
|
|
@@ -31777,7 +33171,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31777
33171
|
if (node.children) for (const child of node.children) this.#applyCheckedToggle(child, selected);
|
|
31778
33172
|
}
|
|
31779
33173
|
#getFlatIndexOf(node) {
|
|
31780
|
-
return this.#
|
|
33174
|
+
return this.#getFlat().indexByKey.get(node.item.key) ?? -1;
|
|
31781
33175
|
}
|
|
31782
33176
|
#extendRangeSelection(nodes, fromIdx, toIdx) {
|
|
31783
33177
|
if (!this.#anchor) {
|
|
@@ -31788,7 +33182,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31788
33182
|
};
|
|
31789
33183
|
}
|
|
31790
33184
|
if (!this.#anchor) return;
|
|
31791
|
-
const anchorIdx =
|
|
33185
|
+
const anchorIdx = this.#getFlat().indexByKey.get(this.#anchor.node.item.key) ?? -1;
|
|
31792
33186
|
if (anchorIdx < 0) return;
|
|
31793
33187
|
const from = Math.min(anchorIdx, toIdx);
|
|
31794
33188
|
const to = Math.max(anchorIdx, toIdx);
|
|
@@ -31824,8 +33218,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31824
33218
|
}, controller.signal), controller.signal)];
|
|
31825
33219
|
if (controller.signal.aborted) return;
|
|
31826
33220
|
node.children = children;
|
|
33221
|
+
this.#structureVersion++;
|
|
31827
33222
|
this.#applyExclusivity(node.item.key);
|
|
31828
|
-
this._expandedKeys
|
|
33223
|
+
this.#setExpanded(withKey(this._expandedKeys, node.item.key));
|
|
33224
|
+
this.#flat = void 0;
|
|
31829
33225
|
this.#stickyDirty = true;
|
|
31830
33226
|
this.#pendingNode = void 0;
|
|
31831
33227
|
this.#pendingController = void 0;
|
|
@@ -31841,6 +33237,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31841
33237
|
} catch {
|
|
31842
33238
|
if (controller.signal.aborted) return;
|
|
31843
33239
|
node.children = [];
|
|
33240
|
+
this.#structureVersion++;
|
|
33241
|
+
this.#flat = void 0;
|
|
31844
33242
|
this.#pendingNode = void 0;
|
|
31845
33243
|
this.#pendingController = void 0;
|
|
31846
33244
|
this.requestUpdate();
|
|
@@ -31852,33 +33250,44 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31852
33250
|
_isFocused(node) {
|
|
31853
33251
|
return this.tickled.isTickled(node.item.key);
|
|
31854
33252
|
}
|
|
31855
|
-
_isExpanded(node,
|
|
33253
|
+
_isExpanded(node, _depth = 0) {
|
|
31856
33254
|
if (this._expandedKeys.has(node.item.key)) return true;
|
|
31857
|
-
|
|
31858
|
-
return false;
|
|
31859
|
-
}
|
|
31860
|
-
#matchesFilter(node, depth) {
|
|
31861
|
-
if (!this.filterPredicate) return true;
|
|
31862
|
-
if (this.filterPredicate(node, depth)) return true;
|
|
31863
|
-
return node.children?.some((child) => this.#matchesFilter(child, depth + 1)) ?? false;
|
|
33255
|
+
return this.#getFilter()?.autoExpanded.has(node.item.key) ?? false;
|
|
31864
33256
|
}
|
|
31865
33257
|
get _pendingNode() {
|
|
31866
33258
|
return this.#pendingNode;
|
|
31867
33259
|
}
|
|
31868
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;
|
|
31869
33269
|
requestAnimationFrame(() => {
|
|
31870
|
-
|
|
31871
|
-
const
|
|
31872
|
-
if (
|
|
31873
|
-
|
|
31874
|
-
|
|
31875
|
-
const relativeTop = elRect.top - containerRect.top;
|
|
31876
|
-
const relativeBottom = relativeTop + elRect.height;
|
|
31877
|
-
const overlayHeight = this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0;
|
|
31878
|
-
if (relativeTop < overlayHeight) container.scrollTop += relativeTop - overlayHeight;
|
|
31879
|
-
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
|
+
});
|
|
31880
33275
|
});
|
|
31881
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
|
+
}
|
|
31882
33291
|
#abortPending() {
|
|
31883
33292
|
if (this.#pendingController) {
|
|
31884
33293
|
this.#pendingController.abort();
|
|
@@ -31892,14 +33301,15 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31892
33301
|
if (this.exclusivity === `depth`) this.#collapseSiblingsAtDepth(expandKey);
|
|
31893
33302
|
else if (this.exclusivity === `global`) {
|
|
31894
33303
|
const ancestorKeys = this.#findAncestorKeys(expandKey);
|
|
31895
|
-
|
|
33304
|
+
this.#setExpanded(withoutKeys(this._expandedKeys, (key) => !ancestorKeys.has(key) && key !== expandKey));
|
|
31896
33305
|
}
|
|
31897
33306
|
}
|
|
31898
33307
|
#collapseSiblingsAtDepth(expandKey) {
|
|
31899
33308
|
if (!this.root) return;
|
|
31900
33309
|
const parent = this.#findParent(this.root, expandKey);
|
|
31901
33310
|
if (!parent?.children) return;
|
|
31902
|
-
|
|
33311
|
+
const siblingKeys = new Set(parent.children.map((c) => c.item.key));
|
|
33312
|
+
this.#setExpanded(withoutKeys(this._expandedKeys, (key) => key !== expandKey && siblingKeys.has(key)));
|
|
31903
33313
|
}
|
|
31904
33314
|
#findAncestorKeys(targetKey) {
|
|
31905
33315
|
const keys = /* @__PURE__ */ new Set();
|
|
@@ -31923,30 +33333,13 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31923
33333
|
if (found) return found;
|
|
31924
33334
|
}
|
|
31925
33335
|
}
|
|
31926
|
-
#getVisibleNodes(node, depth) {
|
|
31927
|
-
const result = [];
|
|
31928
|
-
const visit = (n, d) => {
|
|
31929
|
-
result.push({
|
|
31930
|
-
node: n,
|
|
31931
|
-
depth: d
|
|
31932
|
-
});
|
|
31933
|
-
if (n.children && this._isExpanded(n, d)) for (const child of n.children) visit(child, d + 1);
|
|
31934
|
-
};
|
|
31935
|
-
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
31936
|
-
return result;
|
|
31937
|
-
}
|
|
31938
|
-
#getAllNodes() {
|
|
31939
|
-
if (!this.root) return [];
|
|
31940
|
-
return this.#getVisibleNodes(this.root, -1);
|
|
31941
|
-
}
|
|
31942
33336
|
#getFocusedIndex() {
|
|
31943
|
-
const nodes = this.#getAllNodes();
|
|
31944
33337
|
const tickledKey = this.tickled.tickledId;
|
|
31945
33338
|
if (!tickledKey) return 0;
|
|
31946
|
-
return
|
|
33339
|
+
return this.#getFlat().indexByKey.get(tickledKey) ?? 0;
|
|
31947
33340
|
}
|
|
31948
33341
|
#setFocusedIndex(index) {
|
|
31949
|
-
const nodes = this.#
|
|
33342
|
+
const nodes = this.#getFlat().rows;
|
|
31950
33343
|
if (index < 0 || index >= nodes.length) return;
|
|
31951
33344
|
const { node, depth } = nodes[index];
|
|
31952
33345
|
this.tickled.setTickled(node.item.key);
|
|
@@ -31960,6 +33353,10 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31960
33353
|
composed: true
|
|
31961
33354
|
}));
|
|
31962
33355
|
this.requestUpdate();
|
|
33356
|
+
if (this.#virtualActive()) {
|
|
33357
|
+
this.#revealIndex(index);
|
|
33358
|
+
return;
|
|
33359
|
+
}
|
|
31963
33360
|
this.updateComplete.then(() => this.#scrollTickledIntoView());
|
|
31964
33361
|
}
|
|
31965
33362
|
#scrollTickledIntoView() {
|
|
@@ -31970,12 +33367,25 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31970
33367
|
behavior: `smooth`
|
|
31971
33368
|
});
|
|
31972
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
|
+
}
|
|
31973
33383
|
#onKeyDown;
|
|
31974
33384
|
renderLoadingIndicator() {
|
|
31975
33385
|
return html`<span class="loading-spinner"></span>`;
|
|
31976
33386
|
}
|
|
31977
33387
|
render() {
|
|
31978
|
-
const nodes = this.#
|
|
33388
|
+
const nodes = this.#getFlat().rows;
|
|
31979
33389
|
return html`
|
|
31980
33390
|
<div
|
|
31981
33391
|
id="container"
|
|
@@ -31998,12 +33408,51 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
31998
33408
|
${this.renderLoadingIndicator()}
|
|
31999
33409
|
</div>
|
|
32000
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]));
|
|
32001
33415
|
return html`
|
|
32002
33416
|
<ul class="tree-list" role="tree">
|
|
32003
|
-
${repeat(
|
|
33417
|
+
${repeat(woven, ({ node }) => node.item.key, ({ node, depth }) => this._renderNode(node, depth))}
|
|
33418
|
+
</ul>
|
|
33419
|
+
`;
|
|
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>
|
|
32004
33439
|
</ul>
|
|
32005
33440
|
`;
|
|
32006
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
|
+
}
|
|
32007
33456
|
/** Shared drag-and-drop render state for a row, used by every `_renderNode` variant. */
|
|
32008
33457
|
_dndRowState(node) {
|
|
32009
33458
|
const draggable = this.itemDnd !== `none` && (this.root?.children?.length ?? 0) > 0 && (this.itemDnd === `internal` || this.itemDnd === `both` || this.itemDnd === `external`) && (this.canDrag?.({
|
|
@@ -32027,17 +33476,18 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32027
33476
|
const isBranch = node.item.isLeaf !== true;
|
|
32028
33477
|
const isExpanded = this._isExpanded(node, depth);
|
|
32029
33478
|
const isPending = this.#pendingNode === node;
|
|
32030
|
-
const isFiltered = this._isFilteredOut(node, depth);
|
|
32031
33479
|
const isLeaving = this._isLeaving(node);
|
|
32032
33480
|
const dnd = this._dndRowState(node);
|
|
32033
33481
|
return html`
|
|
32034
33482
|
<li
|
|
32035
|
-
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` : ``}"
|
|
32036
33484
|
role="treeitem"
|
|
32037
33485
|
aria-selected=${selected}
|
|
32038
33486
|
aria-expanded=${isBranch ? isExpanded : nothing}
|
|
32039
33487
|
aria-hidden=${isLeaving ? `true` : nothing}
|
|
32040
33488
|
aria-level=${depth + 1}
|
|
33489
|
+
aria-setsize=${this._ariaSetInfo(node)?.setSize ?? nothing}
|
|
33490
|
+
aria-posinset=${this._ariaSetInfo(node)?.posInSet ?? nothing}
|
|
32041
33491
|
data-key=${node.item.key}
|
|
32042
33492
|
style=${`--depth: ${depth}`}
|
|
32043
33493
|
draggable=${dnd.draggable ? `true` : nothing}
|
|
@@ -32078,6 +33528,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32078
33528
|
--tree-muted-text: var(--surface-muted-text);
|
|
32079
33529
|
--tree-indent: 1ch;
|
|
32080
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;
|
|
32081
33533
|
--tree-caret-size: 30px;
|
|
32082
33534
|
--tree-radius: var(--radius-s);
|
|
32083
33535
|
--tree-drop-line: var(--accent);
|
|
@@ -32101,6 +33553,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32101
33553
|
The scroll lives here, not on :host, so the search popover (a sibling of
|
|
32102
33554
|
#container, positioned against :host) stays put while the list scrolls. */
|
|
32103
33555
|
#container {
|
|
33556
|
+
/* offsetParent for rows: row offsets are scroll-independent (sticky / DnD geometry). */
|
|
33557
|
+
position: relative;
|
|
32104
33558
|
outline: none;
|
|
32105
33559
|
min-height: 100%;
|
|
32106
33560
|
max-height: 100%;
|
|
@@ -32147,7 +33601,40 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32147
33601
|
font-size: var(--text-m);
|
|
32148
33602
|
border-radius: var(--tree-radius);
|
|
32149
33603
|
transition: background var(--transition, 0.15s ease-out);
|
|
32150
|
-
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;
|
|
32151
33638
|
}
|
|
32152
33639
|
|
|
32153
33640
|
.tree-caret {
|
|
@@ -32202,10 +33689,6 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
|
32202
33689
|
to { transform: rotate(360deg); }
|
|
32203
33690
|
}
|
|
32204
33691
|
|
|
32205
|
-
.item.filtered {
|
|
32206
|
-
display: none;
|
|
32207
|
-
}
|
|
32208
|
-
|
|
32209
33692
|
/* Fading-out ghost row (see #runItemAnimations) — inert while it collapses. */
|
|
32210
33693
|
.item.leaving {
|
|
32211
33694
|
pointer-events: none;
|
|
@@ -32316,6 +33799,14 @@ __decorate([property({
|
|
|
32316
33799
|
toAttribute: (value) => value ? null : `false`
|
|
32317
33800
|
}
|
|
32318
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);
|
|
32319
33810
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "dragPreviewRenderer", void 0);
|
|
32320
33811
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "dragPreviewSource", void 0);
|
|
32321
33812
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "canDrag", void 0);
|
|
@@ -32323,33 +33814,33 @@ __decorate([property({ attribute: false })], TreeBaseElement.prototype, "canDrop
|
|
|
32323
33814
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "parseExternalDrop", void 0);
|
|
32324
33815
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "applyInternalDrop", void 0);
|
|
32325
33816
|
__decorate([state()], TreeBaseElement.prototype, "_expandedKeys", void 0);
|
|
33817
|
+
__decorate([state()], TreeBaseElement.prototype, "_scrollTop", void 0);
|
|
32326
33818
|
__decorate([state()], TreeBaseElement.prototype, "_focusedDepth", void 0);
|
|
32327
33819
|
__decorate([state()], TreeBaseElement.prototype, "_stickyKeys", void 0);
|
|
32328
33820
|
TreeBaseElement = __decorate([safeCustomElement(`ixfx-tree-base`)], TreeBaseElement);
|
|
32329
33821
|
//#endregion
|
|
32330
33822
|
//#region src/tree/tree-list.ts
|
|
32331
|
-
function computeCheckboxState$1(node, selectedNodes) {
|
|
32332
|
-
if (node.item.isLeaf || !node.children?.length) return selectedNodes.has(node) ? `checked` : `unchecked`;
|
|
32333
|
-
const childStates = node.children.map((c) => computeCheckboxState$1(c, selectedNodes));
|
|
32334
|
-
const allChecked = childStates.every((s) => s === `checked`);
|
|
32335
|
-
const allUnchecked = childStates.every((s) => s === `unchecked`);
|
|
32336
|
-
if (allChecked) return `checked`;
|
|
32337
|
-
if (allUnchecked) return `unchecked`;
|
|
32338
|
-
return `indeterminate`;
|
|
32339
|
-
}
|
|
32340
33823
|
let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
32341
|
-
|
|
32342
|
-
|
|
32343
|
-
|
|
32344
|
-
|
|
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();
|
|
32345
33829
|
}
|
|
32346
|
-
#
|
|
32347
|
-
this
|
|
32348
|
-
|
|
32349
|
-
|
|
32350
|
-
|
|
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)
|
|
32351
33843
|
};
|
|
32352
|
-
if (this.root) visit(this.root, -1);
|
|
32353
33844
|
}
|
|
32354
33845
|
renderNodeContent(node, depth) {
|
|
32355
33846
|
const isBranch = node.item.isLeaf !== true;
|
|
@@ -32367,7 +33858,7 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
|
32367
33858
|
</span>
|
|
32368
33859
|
`;
|
|
32369
33860
|
if (this.interactionMode === `checked`) {
|
|
32370
|
-
const state =
|
|
33861
|
+
const state = this.#checkboxCache?.states.get(node.item.key) ?? `unchecked`;
|
|
32371
33862
|
return html`${this.#renderCheckbox(node, depth, state)}${caretAndLabel}`;
|
|
32372
33863
|
}
|
|
32373
33864
|
return caretAndLabel;
|
|
@@ -32401,19 +33892,20 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
|
32401
33892
|
const isBranch = node.item.isLeaf !== true;
|
|
32402
33893
|
const isExpanded = this._isExpanded(node, depth);
|
|
32403
33894
|
const isPending = this._pendingNode === node;
|
|
32404
|
-
const hasIconAtDepth = this
|
|
33895
|
+
const hasIconAtDepth = this._depthsWithIcons.has(depth);
|
|
32405
33896
|
const isLeaving = this._isLeaving(node);
|
|
32406
|
-
const isFiltered = this._isFilteredOut(node, depth);
|
|
32407
33897
|
const dnd = this._dndRowState(node);
|
|
32408
33898
|
return html`
|
|
32409
33899
|
<li
|
|
32410
|
-
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` : ``}"
|
|
32411
33901
|
data-key=${node.item.key}
|
|
32412
33902
|
role="treeitem"
|
|
32413
33903
|
aria-selected=${selected}
|
|
32414
33904
|
aria-expanded=${isBranch ? isExpanded : nothing}
|
|
32415
33905
|
aria-hidden=${isLeaving ? `true` : nothing}
|
|
32416
33906
|
aria-level=${depth + 1}
|
|
33907
|
+
aria-setsize=${this._ariaSetInfo(node)?.setSize ?? nothing}
|
|
33908
|
+
aria-posinset=${this._ariaSetInfo(node)?.posInSet ?? nothing}
|
|
32417
33909
|
style=${`--depth: ${depth}`}
|
|
32418
33910
|
draggable=${dnd.draggable ? `true` : nothing}
|
|
32419
33911
|
part=${dnd.dropTarget ? `drop-target` : nothing}
|
|
@@ -40919,86 +42411,30 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
40919
42411
|
return;
|
|
40920
42412
|
}
|
|
40921
42413
|
switch (event.key) {
|
|
40922
|
-
case `ArrowDown`:
|
|
42414
|
+
case `ArrowDown`:
|
|
40923
42415
|
event.preventDefault();
|
|
40924
|
-
|
|
40925
|
-
let targetIndex = 0;
|
|
40926
|
-
if (current && current.depth === currentDepth) targetIndex = Math.min(current.index + 1, col.length - 1);
|
|
40927
|
-
const targetNode = col[targetIndex];
|
|
40928
|
-
if (!targetNode) break;
|
|
40929
|
-
this.#setTickledNode(targetNode, currentDepth);
|
|
40930
|
-
if (this.interactionMode === `standard` || this.interactionMode === `vscode`) {
|
|
40931
|
-
const prevIndex = current?.index ?? 0;
|
|
40932
|
-
if (event.shiftKey) {
|
|
40933
|
-
if (!this.#anchor || this.#anchor.depth !== currentDepth) {
|
|
40934
|
-
const anchorNode = col[prevIndex];
|
|
40935
|
-
if (anchorNode) this.#anchor = {
|
|
40936
|
-
key: anchorNode.item.key,
|
|
40937
|
-
depth: currentDepth,
|
|
40938
|
-
index: prevIndex,
|
|
40939
|
-
selectionSnapshot: this.interactionMode === `vscode` ? new Set(this._selectedKeys) : void 0
|
|
40940
|
-
};
|
|
40941
|
-
}
|
|
40942
|
-
if (this.#anchor) {
|
|
40943
|
-
const from = Math.min(this.#anchor.index, targetIndex);
|
|
40944
|
-
const to = Math.max(this.#anchor.index, targetIndex);
|
|
40945
|
-
const rangeKeys = col.slice(from, to + 1).map((n) => n.item.key);
|
|
40946
|
-
if (this.interactionMode === `vscode`) {
|
|
40947
|
-
const snap = this.#anchor.selectionSnapshot;
|
|
40948
|
-
for (const k of rangeKeys) if (snap.has(k)) this._selectedKeys.delete(k);
|
|
40949
|
-
else this._selectedKeys.add(k);
|
|
40950
|
-
} else this._selectedKeys = new Set(rangeKeys);
|
|
40951
|
-
}
|
|
40952
|
-
} else if (this.interactionMode === `standard`) this.#anchor = {
|
|
40953
|
-
key: targetNode.item.key,
|
|
40954
|
-
depth: currentDepth,
|
|
40955
|
-
index: targetIndex
|
|
40956
|
-
};
|
|
40957
|
-
else this.#anchor = void 0;
|
|
40958
|
-
}
|
|
40959
|
-
this.requestUpdate();
|
|
42416
|
+
this.#moveCursor(col, currentDepth, event, (current) => Math.min(current + 1, col.length - 1));
|
|
40960
42417
|
break;
|
|
40961
|
-
|
|
40962
|
-
case `ArrowUp`: {
|
|
42418
|
+
case `ArrowUp`:
|
|
40963
42419
|
event.preventDefault();
|
|
40964
|
-
|
|
40965
|
-
|
|
40966
|
-
|
|
40967
|
-
|
|
40968
|
-
|
|
40969
|
-
|
|
40970
|
-
|
|
40971
|
-
|
|
40972
|
-
|
|
40973
|
-
|
|
40974
|
-
|
|
40975
|
-
|
|
40976
|
-
|
|
40977
|
-
|
|
40978
|
-
|
|
40979
|
-
|
|
40980
|
-
|
|
40981
|
-
}
|
|
40982
|
-
if (this.#anchor) {
|
|
40983
|
-
const from = Math.min(this.#anchor.index, targetIndex);
|
|
40984
|
-
const to = Math.max(this.#anchor.index, targetIndex);
|
|
40985
|
-
const rangeKeys = col.slice(from, to + 1).map((n) => n.item.key);
|
|
40986
|
-
if (this.interactionMode === `vscode`) {
|
|
40987
|
-
const snap = this.#anchor.selectionSnapshot;
|
|
40988
|
-
for (const k of rangeKeys) if (snap.has(k)) this._selectedKeys.delete(k);
|
|
40989
|
-
else this._selectedKeys.add(k);
|
|
40990
|
-
} else this._selectedKeys = new Set(rangeKeys);
|
|
40991
|
-
}
|
|
40992
|
-
} else if (this.interactionMode === `standard`) this.#anchor = {
|
|
40993
|
-
key: targetNode.item.key,
|
|
40994
|
-
depth: currentDepth,
|
|
40995
|
-
index: targetIndex
|
|
40996
|
-
};
|
|
40997
|
-
else this.#anchor = void 0;
|
|
40998
|
-
}
|
|
40999
|
-
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);
|
|
41000
42437
|
break;
|
|
41001
|
-
}
|
|
41002
42438
|
case `ArrowRight`: {
|
|
41003
42439
|
event.preventDefault();
|
|
41004
42440
|
const tickled = this.#getTickledNodeAndDepth();
|
|
@@ -41091,14 +42527,28 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41091
42527
|
window.addEventListener(`pointermove`, this.#onResizeMove);
|
|
41092
42528
|
window.addEventListener(`pointerup`, this.#onResizeEnd);
|
|
41093
42529
|
};
|
|
42530
|
+
this.#resizeRaf = null;
|
|
42531
|
+
this.#resizePendingWidth = 0;
|
|
41094
42532
|
this.#onResizeMove = (event) => {
|
|
41095
42533
|
if (this.#resizingDepth < 0) return;
|
|
41096
|
-
|
|
41097
|
-
|
|
41098
|
-
|
|
41099
|
-
|
|
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));
|
|
41100
42541
|
};
|
|
41101
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;
|
|
41102
42552
|
this.#resizingDepth = -1;
|
|
41103
42553
|
window.removeEventListener(`pointermove`, this.#onResizeMove);
|
|
41104
42554
|
window.removeEventListener(`pointerup`, this.#onResizeEnd);
|
|
@@ -41705,7 +43155,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41705
43155
|
this.#handleStandardClick(node, depth, event);
|
|
41706
43156
|
break;
|
|
41707
43157
|
case `manual`: break;
|
|
41708
|
-
case `checked`: this.#expandNode(node, depth);
|
|
43158
|
+
case `checked`: this.#expandNode(node, depth, true);
|
|
41709
43159
|
}
|
|
41710
43160
|
}
|
|
41711
43161
|
#handleImplicitClick(node, depth) {
|
|
@@ -41715,7 +43165,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41715
43165
|
this._selectedKeys = /* @__PURE__ */ new Set([node.item.key]);
|
|
41716
43166
|
this._selectedNodes.clear();
|
|
41717
43167
|
this._selectedNodes.add(node);
|
|
41718
|
-
this.#expandNode(node, depth);
|
|
43168
|
+
this.#expandNode(node, depth, true);
|
|
41719
43169
|
if (!previous.has(node)) this.#dispatchSelectEvent(previous);
|
|
41720
43170
|
}
|
|
41721
43171
|
#handleStandardClick(node, depth, event) {
|
|
@@ -41758,16 +43208,22 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41758
43208
|
} : void 0;
|
|
41759
43209
|
this._selectedByDepth.set(depth, node);
|
|
41760
43210
|
this.#lastSelectedNode = node;
|
|
41761
|
-
this.#expandNode(node, depth);
|
|
43211
|
+
this.#expandNode(node, depth, true);
|
|
41762
43212
|
if (!previous.has(node) || previous.size !== 1) this.#dispatchSelectEvent(previous);
|
|
41763
43213
|
}
|
|
41764
|
-
|
|
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) {
|
|
41765
43221
|
if (node.item.isLeaf) {
|
|
41766
43222
|
this._columns = this._columns.slice(0, depth + 1);
|
|
41767
43223
|
this.requestUpdate();
|
|
41768
43224
|
return;
|
|
41769
43225
|
}
|
|
41770
|
-
if (node.children === void 0) this.#loadUnloadedChildren(node, depth, true);
|
|
43226
|
+
if (node.children === void 0) this.#loadUnloadedChildren(node, depth, true, immediate);
|
|
41771
43227
|
else {
|
|
41772
43228
|
const newColumns = this._columns.slice(0, depth + 1);
|
|
41773
43229
|
newColumns.push(node.children);
|
|
@@ -41792,10 +43248,11 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41792
43248
|
* own depth: its column for expansions, -1 for a view root (it sits above
|
|
41793
43249
|
* column 0, so loaders computing child depth as `depth + 1` stay correct).
|
|
41794
43250
|
*/
|
|
41795
|
-
#loadUnloadedChildren(node, parentDepth, fireExpandEvent) {
|
|
43251
|
+
#loadUnloadedChildren(node, parentDepth, fireExpandEvent, immediate = false) {
|
|
41796
43252
|
const nodeDepth = parentDepth + 1;
|
|
41797
43253
|
this._columns = this._columns.slice(0, nodeDepth);
|
|
41798
43254
|
this._loadingDepth = nodeDepth;
|
|
43255
|
+
this.#pendingController?.abort();
|
|
41799
43256
|
this.#pendingNode = node;
|
|
41800
43257
|
const controller = new AbortController();
|
|
41801
43258
|
this.#pendingController = controller;
|
|
@@ -41826,15 +43283,20 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41826
43283
|
this.requestUpdate();
|
|
41827
43284
|
};
|
|
41828
43285
|
if (this.model || this.loadChildren) {
|
|
41829
|
-
if (this.#expandTimer !== void 0)
|
|
41830
|
-
|
|
43286
|
+
if (this.#expandTimer !== void 0) {
|
|
43287
|
+
clearTimeout(this.#expandTimer);
|
|
43288
|
+
this.#expandTimer = void 0;
|
|
43289
|
+
}
|
|
43290
|
+
const load = () => {
|
|
41831
43291
|
this.#expandTimer = void 0;
|
|
41832
43292
|
if (this.#pendingNode !== node) return;
|
|
41833
43293
|
(this.model ? this.model.getChildren(node, nodeDepth - 1, controller.signal) : collectDataResult(this.loadChildren({
|
|
41834
43294
|
node,
|
|
41835
43295
|
depth: nodeDepth - 1
|
|
41836
43296
|
}, controller.signal), controller.signal)).then((children) => applyChildren([...children])).catch(handleError);
|
|
41837
|
-
}
|
|
43297
|
+
};
|
|
43298
|
+
if (immediate) load();
|
|
43299
|
+
else this.#expandTimer = setTimeout(load, EXPAND_DEBOUNCE_MS);
|
|
41838
43300
|
} else {
|
|
41839
43301
|
this._loadingDepth = void 0;
|
|
41840
43302
|
this.#pendingNode = void 0;
|
|
@@ -41845,10 +43307,12 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41845
43307
|
}
|
|
41846
43308
|
}
|
|
41847
43309
|
#onCheckboxClick(node) {
|
|
43310
|
+
const previous = new Set(this._selectedNodes);
|
|
41848
43311
|
const currentlySelected = this._selectedKeys.has(node.item.key);
|
|
41849
43312
|
if (!!node.children?.length ? this.#allDescendantsSelected(node) : currentlySelected) this.#applyCheckedToggle(node, false);
|
|
41850
43313
|
else this.#applyCheckedToggle(node, true);
|
|
41851
43314
|
this.requestUpdate();
|
|
43315
|
+
this.#dispatchSelectEvent(previous);
|
|
41852
43316
|
}
|
|
41853
43317
|
#allDescendantsSelected(node) {
|
|
41854
43318
|
if (!this._selectedKeys.has(node.item.key)) return false;
|
|
@@ -41885,7 +43349,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41885
43349
|
index
|
|
41886
43350
|
};
|
|
41887
43351
|
}
|
|
41888
|
-
#scrollNavigatedItemIntoView() {
|
|
43352
|
+
#scrollNavigatedItemIntoView(behavior = `smooth`) {
|
|
41889
43353
|
requestAnimationFrame(() => {
|
|
41890
43354
|
const tickled = this.#getTickledNodeAndDepth();
|
|
41891
43355
|
if (!tickled) return;
|
|
@@ -41893,7 +43357,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41893
43357
|
if (!container) return;
|
|
41894
43358
|
container.querySelectorAll(`.item`)[tickled.index]?.scrollIntoView({
|
|
41895
43359
|
block: `nearest`,
|
|
41896
|
-
behavior
|
|
43360
|
+
behavior
|
|
41897
43361
|
});
|
|
41898
43362
|
});
|
|
41899
43363
|
}
|
|
@@ -41908,9 +43372,67 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41908
43372
|
composed: true
|
|
41909
43373
|
}));
|
|
41910
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
|
+
}
|
|
41911
43430
|
#onKeyDown;
|
|
41912
43431
|
#onResizeStart;
|
|
43432
|
+
#resizeRaf;
|
|
43433
|
+
#resizePendingWidth;
|
|
41913
43434
|
#onResizeMove;
|
|
43435
|
+
#applyResizeWidth;
|
|
41914
43436
|
#onResizeEnd;
|
|
41915
43437
|
#getColumnHeader(depth) {
|
|
41916
43438
|
if (depth === 0) return this.#displayRoot?.item.label ?? ``;
|
|
@@ -41924,9 +43446,12 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
|
41924
43446
|
}
|
|
41925
43447
|
return ``;
|
|
41926
43448
|
}
|
|
43449
|
+
#explicitWidthStyle(width) {
|
|
43450
|
+
return `width:${width}px;flex-grow:0;flex-shrink:0;flex-basis:auto;max-width:none`;
|
|
43451
|
+
}
|
|
41927
43452
|
#columnStyle(depth, isLast) {
|
|
41928
43453
|
const w = this.#columnWidths.get(depth);
|
|
41929
|
-
if (w !== void 0) return
|
|
43454
|
+
if (w !== void 0) return this.#explicitWidthStyle(w);
|
|
41930
43455
|
if (this.stretchLastColumn && isLast) return `width:max-content;flex-grow:1;flex-shrink:0;flex-basis:auto;min-width:0`;
|
|
41931
43456
|
return `width:max-content;flex-grow:0;flex-shrink:0;flex-basis:auto`;
|
|
41932
43457
|
}
|
|
@@ -42176,8 +43701,24 @@ __decorate([state()], MillerBaseElement.prototype, "_selectedNodes", void 0);
|
|
|
42176
43701
|
MillerBaseElement = __decorate([safeCustomElement(`ixfx-miller-base`)], MillerBaseElement);
|
|
42177
43702
|
//#endregion
|
|
42178
43703
|
//#region src/miller/miller-list.ts
|
|
43704
|
+
const HAS_CHECKED = 1;
|
|
43705
|
+
const HAS_UNCHECKED = 2;
|
|
43706
|
+
/**
|
|
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
|
+
}
|
|
42179
43720
|
/**
|
|
42180
|
-
*
|
|
43721
|
+
* Compute the checkbox state for a node based on `selectedKeys`.
|
|
42181
43722
|
*
|
|
42182
43723
|
* - Unloaded branch (`children === undefined`): treated as a leaf — we can only
|
|
42183
43724
|
* report on what is known.
|
|
@@ -42186,12 +43727,9 @@ MillerBaseElement = __decorate([safeCustomElement(`ixfx-miller-base`)], MillerBa
|
|
|
42186
43727
|
* indeterminate otherwise.
|
|
42187
43728
|
*/
|
|
42188
43729
|
function computeCheckboxState(node, selectedKeys) {
|
|
42189
|
-
|
|
42190
|
-
|
|
42191
|
-
|
|
42192
|
-
const allUnchecked = childStates.every((s) => s === `unchecked`);
|
|
42193
|
-
if (allChecked) return `checked`;
|
|
42194
|
-
if (allUnchecked) return `unchecked`;
|
|
43730
|
+
const seen = scanCheckState(node, selectedKeys);
|
|
43731
|
+
if (seen === HAS_CHECKED) return `checked`;
|
|
43732
|
+
if (seen === HAS_UNCHECKED) return `unchecked`;
|
|
42195
43733
|
return `indeterminate`;
|
|
42196
43734
|
}
|
|
42197
43735
|
let MillerListElement = class MillerListElement extends MillerBaseElement {
|
|
@@ -50406,9 +51944,12 @@ var TabController = class {
|
|
|
50406
51944
|
}
|
|
50407
51945
|
/** The tab's visible label text, excluding any `slot="toolbar"` content. */
|
|
50408
51946
|
#labelOf(item) {
|
|
50409
|
-
|
|
50410
|
-
|
|
50411
|
-
|
|
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();
|
|
50412
51953
|
}
|
|
50413
51954
|
#registerCommand(tab) {
|
|
50414
51955
|
if (!this.#commands) return;
|
|
@@ -50525,9 +52066,12 @@ let TabPanelsElement = class TabPanelsElement extends LitElement {
|
|
|
50525
52066
|
}
|
|
50526
52067
|
/** The tab's visible label text, excluding any `slot="toolbar"` content. */
|
|
50527
52068
|
#labelOf(item) {
|
|
50528
|
-
|
|
50529
|
-
|
|
50530
|
-
|
|
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();
|
|
50531
52075
|
}
|
|
50532
52076
|
static {
|
|
50533
52077
|
this.styles = css`
|
|
@@ -53396,7 +54940,7 @@ var TreeController = class {
|
|
|
53396
54940
|
const treeNode = this.#nodeToTree.get(node);
|
|
53397
54941
|
if (!treeNode) return;
|
|
53398
54942
|
node.children = this.#childrenOf(treeNode);
|
|
53399
|
-
this.#element.
|
|
54943
|
+
this.#element.invalidateStructure();
|
|
53400
54944
|
}, { signal });
|
|
53401
54945
|
this.#element.addEventListener(`collapse`, (_e) => {}, { signal });
|
|
53402
54946
|
}
|
|
@@ -53418,7 +54962,10 @@ var TreeController = class {
|
|
|
53418
54962
|
if (!node) return false;
|
|
53419
54963
|
if (node.children === void 0) {
|
|
53420
54964
|
const mapped = this.#nodeToTree.get(node);
|
|
53421
|
-
if (mapped)
|
|
54965
|
+
if (mapped) {
|
|
54966
|
+
node.children = this.#childrenOf(mapped);
|
|
54967
|
+
this.#element.invalidateStructure();
|
|
54968
|
+
}
|
|
53422
54969
|
}
|
|
53423
54970
|
currentColumn = node.children ?? [];
|
|
53424
54971
|
}
|