@ixfx/components 0.7.4 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.d.ts +536 -178
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +1324 -442
- package/bundle/index.js.map +1 -1
- package/dist/ac-token.js +1 -1
- package/dist/crumbs.d.ts +5 -1
- package/dist/crumbs.d.ts.map +1 -1
- package/dist/crumbs.js +10 -0
- package/dist/crumbs.js.map +1 -1
- package/dist/{data-grid-BddUjAti.d.ts → data-grid-CtJ35v8L.d.ts} +26 -1
- package/dist/data-grid-CtJ35v8L.d.ts.map +1 -0
- package/dist/data-grid.d.ts +1 -1
- package/dist/data-grid.js +88 -7
- package/dist/data-grid.js.map +1 -1
- package/dist/{detail-list-Br0WAnry.d.ts → detail-list-BS5dRDda.d.ts} +20 -12
- package/dist/detail-list-BS5dRDda.d.ts.map +1 -0
- package/dist/detail-list.d.ts +2 -2
- package/dist/detail-list.js +51 -135
- package/dist/detail-list.js.map +1 -1
- package/dist/{grid-list-B_orPsot.d.ts → grid-list-D-GbghSH.d.ts} +18 -1
- package/dist/grid-list-D-GbghSH.d.ts.map +1 -0
- package/dist/grid-list.d.ts +1 -1
- package/dist/grid-list.js +76 -15
- package/dist/grid-list.js.map +1 -1
- package/dist/incr-search-DpbSJtgj.js +962 -0
- package/dist/incr-search-DpbSJtgj.js.map +1 -0
- package/dist/incr-search.d.ts +2 -2
- package/dist/incr-search.js +2 -3
- package/dist/index-CBS4z1v-.d.ts +365 -0
- package/dist/index-CBS4z1v-.d.ts.map +1 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/{list-selection-controller-CUo-Hhcm.js → list-selection-controller-DTFI40li.js} +2 -76
- package/dist/list-selection-controller-DTFI40li.js.map +1 -0
- package/dist/miller.d.ts +81 -3
- package/dist/miller.d.ts.map +1 -1
- package/dist/miller.js +327 -68
- package/dist/miller.js.map +1 -1
- package/dist/narrowed-text.js +1 -2
- package/dist/narrowed-text.js.map +1 -1
- package/dist/{tree-B74zdI60.js → tree-DAii2A1g.js} +77 -15
- package/dist/tree-DAii2A1g.js.map +1 -0
- package/dist/{tree-component-BvV4muxx.d.ts → tree-component-F6qZ0Gdv.d.ts} +9 -1
- package/dist/{tree-component-BvV4muxx.d.ts.map → tree-component-F6qZ0Gdv.d.ts.map} +1 -1
- package/dist/tree.d.ts +22 -1
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +1 -1
- package/dist/{vertical-list-CQSEVeOT.d.ts → vertical-list-DwtCPy1b.d.ts} +17 -14
- package/dist/vertical-list-DwtCPy1b.d.ts.map +1 -0
- package/dist/vertical-list.d.ts +1 -1
- package/dist/vertical-list.js +59 -127
- package/dist/vertical-list.js.map +1 -1
- package/docs-user/data-grid.md +19 -0
- package/docs-user/detail-list.md +1 -1
- package/docs-user/grid-list.md +24 -0
- package/docs-user/incr-search.md +12 -2
- package/docs-user/index.json +2 -2
- package/docs-user/tree.md +22 -1
- package/docs-user/vertical-list.md +7 -1
- package/package.json +1 -1
- package/dist/data-grid-BddUjAti.d.ts.map +0 -1
- package/dist/detail-list-Br0WAnry.d.ts.map +0 -1
- package/dist/element-search-Y8SRfPGM.js +0 -185
- package/dist/element-search-Y8SRfPGM.js.map +0 -1
- package/dist/grid-list-B_orPsot.d.ts.map +0 -1
- package/dist/incr-search-vCzuao-2.js +0 -137
- package/dist/incr-search-vCzuao-2.js.map +0 -1
- package/dist/index-Cu5i8zZY.d.ts +0 -160
- package/dist/index-Cu5i8zZY.d.ts.map +0 -1
- package/dist/list-selection-controller-CUo-Hhcm.js.map +0 -1
- package/dist/tree-B74zdI60.js.map +0 -1
- package/dist/vertical-list-CQSEVeOT.d.ts.map +0 -1
package/bundle/index.js
CHANGED
|
@@ -2400,14 +2400,15 @@ function createIncrSearch(options = {}) {
|
|
|
2400
2400
|
}
|
|
2401
2401
|
//#endregion
|
|
2402
2402
|
//#region src/incr-search/controller-tree.ts
|
|
2403
|
-
const DEFAULT_HIGHLIGHT_KEY = `incr-search-highlight`;
|
|
2403
|
+
const DEFAULT_HIGHLIGHT_KEY$1 = `incr-search-highlight`;
|
|
2404
2404
|
/**
|
|
2405
|
-
*
|
|
2406
|
-
* (i.e. `ixfx-tree-list`, `ixfx-miller-list`, `ixfx-crumb-navigation`).
|
|
2405
|
+
* External-input filter-search controller for `TreeSearchHost` components.
|
|
2407
2406
|
*
|
|
2408
|
-
*
|
|
2409
|
-
*
|
|
2410
|
-
*
|
|
2407
|
+
* @deprecated `ixfx-tree-list` / `ixfx-miller-list` now have a built-in
|
|
2408
|
+
* incremental-search popover with navigate / select / filter modes — invoke
|
|
2409
|
+
* `el.registry.invoke('list.search.filter')` (see `docs/infra-incremental.md`)
|
|
2410
|
+
* instead of wiring an external `<input>`. This class remains for existing
|
|
2411
|
+
* consumers and covers filter mode only.
|
|
2411
2412
|
*
|
|
2412
2413
|
* @example Using with ixfx-tree-list:
|
|
2413
2414
|
* ```typescript
|
|
@@ -2461,7 +2462,7 @@ var IncrSearchTreeController = class {
|
|
|
2461
2462
|
this.#opts.input.removeEventListener(`keydown`, this.#onKeydown);
|
|
2462
2463
|
}
|
|
2463
2464
|
this.#el.filterPredicate = void 0;
|
|
2464
|
-
clearHighlights({ key: this.#opts.highlightKey ?? DEFAULT_HIGHLIGHT_KEY });
|
|
2465
|
+
clearHighlights({ key: this.#opts.highlightKey ?? DEFAULT_HIGHLIGHT_KEY$1 });
|
|
2465
2466
|
this.#labelMap.clear();
|
|
2466
2467
|
}
|
|
2467
2468
|
async setQuery(query) {
|
|
@@ -2484,7 +2485,7 @@ var IncrSearchTreeController = class {
|
|
|
2484
2485
|
const matched = new Set(state.results.map((r) => r.item.node.item.key));
|
|
2485
2486
|
const hasQuery = state.query.trim().length > 0;
|
|
2486
2487
|
this.#el.filterPredicate = hasQuery && matched.size > 0 ? (node, _depth) => matched.has(node.item.key) : void 0;
|
|
2487
|
-
const key = this.#opts.highlightKey ?? DEFAULT_HIGHLIGHT_KEY;
|
|
2488
|
+
const key = this.#opts.highlightKey ?? DEFAULT_HIGHLIGHT_KEY$1;
|
|
2488
2489
|
if (!this.#el.shadowRoot) return;
|
|
2489
2490
|
if (!state.query.trim()) {
|
|
2490
2491
|
clearHighlights({ key });
|
|
@@ -2498,11 +2499,149 @@ var IncrSearchTreeController = class {
|
|
|
2498
2499
|
}
|
|
2499
2500
|
};
|
|
2500
2501
|
//#endregion
|
|
2502
|
+
//#region src/interaction/keyed-list.ts
|
|
2503
|
+
/**
|
|
2504
|
+
* Helpers for key-based selection on the Element-flavoured list components
|
|
2505
|
+
* (`ixfx-vertical-list`, `ixfx-detail-list`, `ixfx-grid-list`).
|
|
2506
|
+
*
|
|
2507
|
+
* A "key" is a stable string the host puts on each item element — `data-key`
|
|
2508
|
+
* (preferred) or `data-value`. It survives the host rebuilding the item set
|
|
2509
|
+
* (re-fetch, re-sort, filter) where raw `Element` references do not.
|
|
2510
|
+
*/
|
|
2511
|
+
/**
|
|
2512
|
+
* The stable key for an item element: `data-key`, then `data-value`, else
|
|
2513
|
+
* `undefined`.
|
|
2514
|
+
*/
|
|
2515
|
+
function keyForElement(el) {
|
|
2516
|
+
return el.getAttribute(`data-key`) ?? el.getAttribute(`data-value`) ?? void 0;
|
|
2517
|
+
}
|
|
2518
|
+
/**
|
|
2519
|
+
* A lazily-built key → element index over a container. Rebuilt on first use
|
|
2520
|
+
* and after any child-list mutation under `root`; `invalidate()` forces a
|
|
2521
|
+
* rebuild on the next `get()`.
|
|
2522
|
+
*
|
|
2523
|
+
* ```ts
|
|
2524
|
+
* #keyIndex = new KeyedElementIndex(this);
|
|
2525
|
+
* // ...
|
|
2526
|
+
* const el = this.#keyIndex.get(`row-42`);
|
|
2527
|
+
* // on disconnect:
|
|
2528
|
+
* this.#keyIndex.dispose();
|
|
2529
|
+
* ```
|
|
2530
|
+
*/
|
|
2531
|
+
var KeyedElementIndex = class {
|
|
2532
|
+
#root;
|
|
2533
|
+
#selector;
|
|
2534
|
+
#map = /* @__PURE__ */ new Map();
|
|
2535
|
+
#dirty = true;
|
|
2536
|
+
#observer;
|
|
2537
|
+
/**
|
|
2538
|
+
* @param root Container whose descendants are indexed.
|
|
2539
|
+
* @param itemSelector Which descendants count as items (default: any `[data-key]`).
|
|
2540
|
+
*/
|
|
2541
|
+
constructor(root, itemSelector = `[data-key]`) {
|
|
2542
|
+
this.#root = root;
|
|
2543
|
+
this.#selector = itemSelector;
|
|
2544
|
+
this.#observer = new MutationObserver(() => {
|
|
2545
|
+
this.#dirty = true;
|
|
2546
|
+
});
|
|
2547
|
+
this.#observer.observe(root, {
|
|
2548
|
+
childList: true,
|
|
2549
|
+
subtree: true
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
/** The element with this key, or `undefined`. First match wins on duplicates. */
|
|
2553
|
+
get(key) {
|
|
2554
|
+
if (this.#dirty) this.#rebuild();
|
|
2555
|
+
return this.#map.get(key);
|
|
2556
|
+
}
|
|
2557
|
+
/** Force a rebuild on the next `get()` — e.g. after a `data-key` attribute change. */
|
|
2558
|
+
invalidate() {
|
|
2559
|
+
this.#dirty = true;
|
|
2560
|
+
}
|
|
2561
|
+
/** Stop observing and drop the cache. */
|
|
2562
|
+
dispose() {
|
|
2563
|
+
this.#observer.disconnect();
|
|
2564
|
+
this.#map.clear();
|
|
2565
|
+
}
|
|
2566
|
+
#rebuild() {
|
|
2567
|
+
this.#map.clear();
|
|
2568
|
+
for (const el of this.#root.querySelectorAll(this.#selector)) {
|
|
2569
|
+
const key = keyForElement(el);
|
|
2570
|
+
if (key !== void 0 && !this.#map.has(key)) this.#map.set(key, el);
|
|
2571
|
+
}
|
|
2572
|
+
this.#dirty = false;
|
|
2573
|
+
}
|
|
2574
|
+
};
|
|
2575
|
+
//#endregion
|
|
2576
|
+
//#region src/incr-search/element-list-search-adapter.ts
|
|
2577
|
+
function defaultLabel(el) {
|
|
2578
|
+
return el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``;
|
|
2579
|
+
}
|
|
2580
|
+
/**
|
|
2581
|
+
* A {@link ListSearchAdapter} over the light-DOM item elements of a list
|
|
2582
|
+
* component. Filter mode toggles `hidden` (only on elements it hid itself, so a
|
|
2583
|
+
* consumer's own `hidden` items are left alone). Highlights are painted on the
|
|
2584
|
+
* item elements in the light DOM — the consuming page styles
|
|
2585
|
+
* `::highlight(<highlightKey>)`. See `docs/infra-incremental.md`.
|
|
2586
|
+
*/
|
|
2587
|
+
function createElementListSearchAdapter(host, options = {}) {
|
|
2588
|
+
const itemSelector = options.itemSelector ?? `:scope > *`;
|
|
2589
|
+
const getLabel = options.getLabel ?? defaultLabel;
|
|
2590
|
+
const getKey = options.getKey ?? ((el) => keyForElement(el) ?? defaultLabel(el));
|
|
2591
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
2592
|
+
const items = () => Array.from(host.querySelectorAll(itemSelector));
|
|
2593
|
+
return {
|
|
2594
|
+
getItems: items,
|
|
2595
|
+
getLabel,
|
|
2596
|
+
getKey,
|
|
2597
|
+
focusItem: (el) => {
|
|
2598
|
+
if (el) host.focusKey(getKey(el));
|
|
2599
|
+
},
|
|
2600
|
+
activateItem: (el) => {
|
|
2601
|
+
el.dispatchEvent(new MouseEvent(`click`, {
|
|
2602
|
+
bubbles: true,
|
|
2603
|
+
composed: true
|
|
2604
|
+
}));
|
|
2605
|
+
},
|
|
2606
|
+
selectItems: (els) => {
|
|
2607
|
+
host.selectKeys(els.map(getKey));
|
|
2608
|
+
},
|
|
2609
|
+
applyFilter: (matched) => {
|
|
2610
|
+
if (matched === void 0) {
|
|
2611
|
+
for (const el of hidden) el.hidden = false;
|
|
2612
|
+
hidden.clear();
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
for (const el of items()) if (matched.has(getKey(el))) {
|
|
2616
|
+
if (hidden.delete(el)) el.hidden = false;
|
|
2617
|
+
} else {
|
|
2618
|
+
el.hidden = true;
|
|
2619
|
+
hidden.add(el);
|
|
2620
|
+
}
|
|
2621
|
+
},
|
|
2622
|
+
highlightRoot: () => host,
|
|
2623
|
+
highlightSelector: itemSelector,
|
|
2624
|
+
highlightKeyOf: (el) => getKey(el),
|
|
2625
|
+
highlightText: getLabel,
|
|
2626
|
+
highlightKey: options.highlightKey,
|
|
2627
|
+
container: () => host
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
//#endregion
|
|
2501
2631
|
//#region src/incr-search/element-search.ts
|
|
2502
2632
|
const defaultGetLabel = (el) => el.textContent?.trim() ?? ``;
|
|
2503
2633
|
function defaultGetKey(el) {
|
|
2504
2634
|
return el.getAttribute(`data-key`) ?? el.getAttribute(`data-value`) ?? el.textContent?.trim() ?? ``;
|
|
2505
2635
|
}
|
|
2636
|
+
/**
|
|
2637
|
+
* DOM filter-search over a container's item elements (sets `hidden` on
|
|
2638
|
+
* non-matches, applies highlights).
|
|
2639
|
+
*
|
|
2640
|
+
* @deprecated The list components (`ixfx-grid-list`, `ixfx-vertical-list`,
|
|
2641
|
+
* `ixfx-detail-list`, …) now have a built-in navigate / select / filter search
|
|
2642
|
+
* popover — see `docs/infra-incremental.md` and `ListSearchController`. Kept for
|
|
2643
|
+
* standalone / non-component use.
|
|
2644
|
+
*/
|
|
2506
2645
|
function createElementIncrSearch(opts) {
|
|
2507
2646
|
const { container, itemSelector, getLabel = defaultGetLabel, getKey = defaultGetKey, labelSelector, highlightKey } = opts;
|
|
2508
2647
|
let elementByKey = /* @__PURE__ */ new Map();
|
|
@@ -2576,6 +2715,512 @@ function createElementIncrSearch(opts) {
|
|
|
2576
2715
|
};
|
|
2577
2716
|
}
|
|
2578
2717
|
//#endregion
|
|
2718
|
+
//#region src/incr-search/list-search-commands.ts
|
|
2719
|
+
/** Command ids registered by {@link registerListSearchCommands}, keyed by mode. */
|
|
2720
|
+
const LIST_SEARCH_COMMAND_IDS = {
|
|
2721
|
+
navigate: `list.search.navigate`,
|
|
2722
|
+
select: `list.search.select`,
|
|
2723
|
+
filter: `list.search.filter`
|
|
2724
|
+
};
|
|
2725
|
+
const LABELS = {
|
|
2726
|
+
navigate: {
|
|
2727
|
+
label: `Find`,
|
|
2728
|
+
description: `Type to move the cursor to the best match`
|
|
2729
|
+
},
|
|
2730
|
+
select: {
|
|
2731
|
+
label: `Find & Select`,
|
|
2732
|
+
description: `Type to select every matching item`
|
|
2733
|
+
},
|
|
2734
|
+
filter: {
|
|
2735
|
+
label: `Filter`,
|
|
2736
|
+
description: `Type to hide items that don't match`
|
|
2737
|
+
}
|
|
2738
|
+
};
|
|
2739
|
+
/**
|
|
2740
|
+
* Register one command per mode the controller offers (see
|
|
2741
|
+
* `controller.registeredModes`). Each command opens the search popover in its
|
|
2742
|
+
* mode. Returns a function that unregisters them again — call it from
|
|
2743
|
+
* `disconnectedCallback`. See `docs/infra-incremental.md`.
|
|
2744
|
+
*/
|
|
2745
|
+
function registerListSearchCommands(registry, controller, options = {}) {
|
|
2746
|
+
const id = (mode) => options.idPrefix ? `${options.idPrefix}.search.${mode}` : LIST_SEARCH_COMMAND_IDS[mode];
|
|
2747
|
+
const registered = [];
|
|
2748
|
+
for (const mode of controller.registeredModes) {
|
|
2749
|
+
const commandId = id(mode);
|
|
2750
|
+
if (registry.has(commandId)) continue;
|
|
2751
|
+
registry.register({
|
|
2752
|
+
id: commandId,
|
|
2753
|
+
label: LABELS[mode].label,
|
|
2754
|
+
description: LABELS[mode].description,
|
|
2755
|
+
execute: () => controller.open(mode)
|
|
2756
|
+
});
|
|
2757
|
+
registered.push(commandId);
|
|
2758
|
+
}
|
|
2759
|
+
return () => {
|
|
2760
|
+
for (const commandId of registered) registry.unregister(commandId);
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
//#endregion
|
|
2764
|
+
//#region src/incr-search/list-search-controller.ts
|
|
2765
|
+
/** Canonical order — used for the switch-row and to normalise a requested set. */
|
|
2766
|
+
const LIST_SEARCH_MODES = [
|
|
2767
|
+
`navigate`,
|
|
2768
|
+
`select`,
|
|
2769
|
+
`filter`
|
|
2770
|
+
];
|
|
2771
|
+
const DEFAULT_HIGHLIGHT_KEY = `incr-search-highlight`;
|
|
2772
|
+
const INPUT_CLASS = `list-search-input`;
|
|
2773
|
+
/**
|
|
2774
|
+
* Shared brain for the three-mode incremental-search popover. A Lit
|
|
2775
|
+
* `ReactiveController`: the host adds `listSearchStyles` to `static styles`,
|
|
2776
|
+
* drops `${controller.renderField()}` into `render()`, and registers the
|
|
2777
|
+
* commands with {@link registerListSearchCommands}. See `docs/infra-incremental.md`.
|
|
2778
|
+
*/
|
|
2779
|
+
var ListSearchController = class {
|
|
2780
|
+
#host;
|
|
2781
|
+
#adapter;
|
|
2782
|
+
#modes;
|
|
2783
|
+
#placeholder;
|
|
2784
|
+
#search;
|
|
2785
|
+
#unsub;
|
|
2786
|
+
#observer;
|
|
2787
|
+
#itemByKey = /* @__PURE__ */ new Map();
|
|
2788
|
+
#open = false;
|
|
2789
|
+
#mode = `filter`;
|
|
2790
|
+
#query = ``;
|
|
2791
|
+
#matchIndex = 0;
|
|
2792
|
+
#matches = [];
|
|
2793
|
+
/** Whether a filter has been pushed to the adapter and still needs undoing. */
|
|
2794
|
+
#filterActive = false;
|
|
2795
|
+
constructor(host, options) {
|
|
2796
|
+
this.#host = host;
|
|
2797
|
+
host.addController(this);
|
|
2798
|
+
this.#adapter = options.adapter;
|
|
2799
|
+
const requested = new Set(options.modes ?? LIST_SEARCH_MODES);
|
|
2800
|
+
this.#modes = LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
2801
|
+
this.#mode = this.#modes[0] ?? `filter`;
|
|
2802
|
+
this.#placeholder = options.placeholder ?? `Search…`;
|
|
2803
|
+
this.#search = createIncrSearch({
|
|
2804
|
+
selector: (i) => i.label,
|
|
2805
|
+
fzf: options.fzf
|
|
2806
|
+
});
|
|
2807
|
+
this.#unsub = this.#search.onChange((state) => this.#onState(state));
|
|
2808
|
+
}
|
|
2809
|
+
/** `false` when no modes are available — the host should register no commands and render no field. */
|
|
2810
|
+
get enabled() {
|
|
2811
|
+
return this.#modes.length > 0;
|
|
2812
|
+
}
|
|
2813
|
+
/** The modes actually offered (component capability ∩ consumer request). */
|
|
2814
|
+
get registeredModes() {
|
|
2815
|
+
return this.#modes;
|
|
2816
|
+
}
|
|
2817
|
+
/**
|
|
2818
|
+
* Replace the offered mode set (normalised to {@link LIST_SEARCH_MODES}
|
|
2819
|
+
* order). If the current mode is dropped the popover closes and falls back to
|
|
2820
|
+
* the first remaining mode. Callers should re-run
|
|
2821
|
+
* {@link registerListSearchCommands} afterwards.
|
|
2822
|
+
*/
|
|
2823
|
+
setModes(modes) {
|
|
2824
|
+
const set = new Set(modes);
|
|
2825
|
+
this.#modes = LIST_SEARCH_MODES.filter((m) => set.has(m));
|
|
2826
|
+
if (!this.#modes.includes(this.#mode)) {
|
|
2827
|
+
if (this.#open) this.close();
|
|
2828
|
+
this.#mode = this.#modes[0] ?? `filter`;
|
|
2829
|
+
}
|
|
2830
|
+
this.#host.requestUpdate();
|
|
2831
|
+
}
|
|
2832
|
+
get isOpen() {
|
|
2833
|
+
return this.#open;
|
|
2834
|
+
}
|
|
2835
|
+
get mode() {
|
|
2836
|
+
return this.#mode;
|
|
2837
|
+
}
|
|
2838
|
+
get query() {
|
|
2839
|
+
return this.#query;
|
|
2840
|
+
}
|
|
2841
|
+
get matchCount() {
|
|
2842
|
+
return this.#matches.length;
|
|
2843
|
+
}
|
|
2844
|
+
get matchIndex() {
|
|
2845
|
+
return this.#matchIndex;
|
|
2846
|
+
}
|
|
2847
|
+
hostConnected() {
|
|
2848
|
+
const container = this.#adapter.container?.();
|
|
2849
|
+
if (container) {
|
|
2850
|
+
this.#observer = new MutationObserver(() => this.refresh());
|
|
2851
|
+
this.#observer.observe(container, {
|
|
2852
|
+
childList: true,
|
|
2853
|
+
subtree: false
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2856
|
+
this.refresh();
|
|
2857
|
+
}
|
|
2858
|
+
hostDisconnected() {
|
|
2859
|
+
this.#observer?.disconnect();
|
|
2860
|
+
this.#observer = void 0;
|
|
2861
|
+
if (this.#open) this.#teardownEffects();
|
|
2862
|
+
this.#open = false;
|
|
2863
|
+
this.#unsub?.();
|
|
2864
|
+
this.#unsub = void 0;
|
|
2865
|
+
this.#search.dispose();
|
|
2866
|
+
}
|
|
2867
|
+
/** Open the popover in `mode` (clamped to the available set). */
|
|
2868
|
+
open(mode) {
|
|
2869
|
+
if (!this.enabled) return;
|
|
2870
|
+
this.#mode = this.#modes.includes(mode) ? mode : this.#modes[0];
|
|
2871
|
+
this.#open = true;
|
|
2872
|
+
this.#query = ``;
|
|
2873
|
+
this.#matchIndex = 0;
|
|
2874
|
+
this.#matches = [];
|
|
2875
|
+
this.refresh();
|
|
2876
|
+
this.#search.setQuery(``);
|
|
2877
|
+
this.#host.requestUpdate();
|
|
2878
|
+
this.#host.updateComplete.then(() => this.#focusInput());
|
|
2879
|
+
}
|
|
2880
|
+
/** Close the popover, restoring any live filter. Selection / cursor stay put. */
|
|
2881
|
+
close() {
|
|
2882
|
+
if (!this.#open) return;
|
|
2883
|
+
this.#open = false;
|
|
2884
|
+
this.#teardownEffects();
|
|
2885
|
+
this.#query = ``;
|
|
2886
|
+
this.#matches = [];
|
|
2887
|
+
this.#search.clearQuery();
|
|
2888
|
+
this.#host.requestUpdate();
|
|
2889
|
+
this.#host.focus();
|
|
2890
|
+
}
|
|
2891
|
+
setQuery(query) {
|
|
2892
|
+
this.#query = query;
|
|
2893
|
+
this.#matchIndex = 0;
|
|
2894
|
+
this.#search.setQuery(query);
|
|
2895
|
+
}
|
|
2896
|
+
/** Re-interpret the current query under a new mode (undoing the old mode's effect). */
|
|
2897
|
+
switchMode(next) {
|
|
2898
|
+
if (!this.#modes.includes(next) || next === this.#mode) return;
|
|
2899
|
+
if (this.#mode === `filter` && this.#filterActive) {
|
|
2900
|
+
this.#adapter.applyFilter(void 0);
|
|
2901
|
+
this.#filterActive = false;
|
|
2902
|
+
}
|
|
2903
|
+
this.#mode = next;
|
|
2904
|
+
this.#applyMode();
|
|
2905
|
+
this.#host.requestUpdate();
|
|
2906
|
+
this.#host.updateComplete.then(() => this.#focusInput());
|
|
2907
|
+
}
|
|
2908
|
+
/** One-shot: turn the current match set into the selection, staying in the current mode. */
|
|
2909
|
+
selectMatches() {
|
|
2910
|
+
if (!this.#modes.includes(`select`)) return;
|
|
2911
|
+
this.#adapter.selectItems(this.#currentItems());
|
|
2912
|
+
this.#host.requestUpdate();
|
|
2913
|
+
}
|
|
2914
|
+
nextMatch() {
|
|
2915
|
+
this.#cycle(1);
|
|
2916
|
+
}
|
|
2917
|
+
prevMatch() {
|
|
2918
|
+
this.#cycle(-1);
|
|
2919
|
+
}
|
|
2920
|
+
/** Enter: in `navigate` activate the current match; then close. */
|
|
2921
|
+
commit() {
|
|
2922
|
+
if (this.#mode === `navigate`) {
|
|
2923
|
+
const item = this.#itemFor(this.#matches[this.#matchIndex]);
|
|
2924
|
+
if (item) {
|
|
2925
|
+
if (this.#adapter.activateItem) this.#adapter.activateItem(item);
|
|
2926
|
+
else this.#adapter.focusItem(item);
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
this.close();
|
|
2930
|
+
}
|
|
2931
|
+
/** Rebuild the searchable item set from the adapter. Called on open and on container mutation. */
|
|
2932
|
+
refresh() {
|
|
2933
|
+
this.#itemByKey.clear();
|
|
2934
|
+
const items = [];
|
|
2935
|
+
for (const it of this.#adapter.getItems()) {
|
|
2936
|
+
const key = this.#adapter.getKey(it);
|
|
2937
|
+
this.#itemByKey.set(key, it);
|
|
2938
|
+
items.push({
|
|
2939
|
+
key,
|
|
2940
|
+
label: this.#adapter.getLabel(it)
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2943
|
+
this.#search.setItems(items);
|
|
2944
|
+
}
|
|
2945
|
+
/** Popover markup. Pair with `listSearchStyles` in the host's `static styles`. */
|
|
2946
|
+
renderField() {
|
|
2947
|
+
if (!this.enabled) return A;
|
|
2948
|
+
const hasQuery = this.#query.trim().length > 0;
|
|
2949
|
+
const others = this.#modes.filter((m) => m !== this.#mode);
|
|
2950
|
+
const showSelectMatches = this.#modes.includes(`select`) && this.#mode !== `select`;
|
|
2951
|
+
return b`
|
|
2952
|
+
<div class="list-search" part="search" ?hidden=${!this.#open}>
|
|
2953
|
+
<div class="list-search-row">
|
|
2954
|
+
<span class="list-search-mode">${this.#mode}</span>
|
|
2955
|
+
<input
|
|
2956
|
+
class=${INPUT_CLASS}
|
|
2957
|
+
type="text"
|
|
2958
|
+
part="search-input"
|
|
2959
|
+
placeholder=${this.#placeholder}
|
|
2960
|
+
.value=${this.#query}
|
|
2961
|
+
@input=${this.#onInput}
|
|
2962
|
+
@keydown=${this.#onKeydown}
|
|
2963
|
+
/>
|
|
2964
|
+
${hasQuery ? b`<span class="list-search-count">${this.#matches.length === 0 ? `0` : this.#matchIndex + 1} / ${this.#matches.length}</span>` : A}
|
|
2965
|
+
${this.#mode === `navigate` && this.#matches.length > 1 ? b`
|
|
2966
|
+
<button class="list-search-icon-btn" title="Previous match (↑)" @click=${() => this.prevMatch()}>‹</button>
|
|
2967
|
+
<button class="list-search-icon-btn" title="Next match (↓)" @click=${() => this.nextMatch()}>›</button>
|
|
2968
|
+
` : A}
|
|
2969
|
+
<button class="list-search-icon-btn" title="Close (Esc)" @click=${() => this.close()}>✕</button>
|
|
2970
|
+
</div>
|
|
2971
|
+
${others.length > 0 || showSelectMatches ? b`
|
|
2972
|
+
<div class="list-search-switch">
|
|
2973
|
+
${others.map((m) => b`<button class="list-search-btn" @click=${() => this.switchMode(m)}>${m}</button>`)}
|
|
2974
|
+
${showSelectMatches ? b`<button class="list-search-btn" @click=${() => this.selectMatches()}>Select matches</button>` : A}
|
|
2975
|
+
</div>
|
|
2976
|
+
` : A}
|
|
2977
|
+
</div>
|
|
2978
|
+
`;
|
|
2979
|
+
}
|
|
2980
|
+
#focusInput() {
|
|
2981
|
+
this.#host.renderRoot?.querySelector(`.${INPUT_CLASS}`)?.focus();
|
|
2982
|
+
}
|
|
2983
|
+
#onInput = (e) => {
|
|
2984
|
+
this.setQuery(e.target.value);
|
|
2985
|
+
};
|
|
2986
|
+
#onKeydown = (e) => {
|
|
2987
|
+
switch (e.key) {
|
|
2988
|
+
case `Escape`:
|
|
2989
|
+
e.preventDefault();
|
|
2990
|
+
e.stopPropagation();
|
|
2991
|
+
this.close();
|
|
2992
|
+
break;
|
|
2993
|
+
case `Enter`:
|
|
2994
|
+
e.preventDefault();
|
|
2995
|
+
e.stopPropagation();
|
|
2996
|
+
this.commit();
|
|
2997
|
+
break;
|
|
2998
|
+
case `ArrowDown`:
|
|
2999
|
+
if (this.#mode === `navigate`) {
|
|
3000
|
+
e.preventDefault();
|
|
3001
|
+
this.nextMatch();
|
|
3002
|
+
}
|
|
3003
|
+
break;
|
|
3004
|
+
case `ArrowUp`: if (this.#mode === `navigate`) {
|
|
3005
|
+
e.preventDefault();
|
|
3006
|
+
this.prevMatch();
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
#cycle(delta) {
|
|
3011
|
+
if (this.#matches.length === 0) return;
|
|
3012
|
+
const n = this.#matches.length;
|
|
3013
|
+
this.#matchIndex = (this.#matchIndex + delta + n) % n;
|
|
3014
|
+
if (this.#mode === `navigate`) this.#adapter.focusItem(this.#itemFor(this.#matches[this.#matchIndex]));
|
|
3015
|
+
this.#host.requestUpdate();
|
|
3016
|
+
}
|
|
3017
|
+
#itemFor(match) {
|
|
3018
|
+
return match ? this.#itemByKey.get(match.item.key) : void 0;
|
|
3019
|
+
}
|
|
3020
|
+
#currentItems() {
|
|
3021
|
+
return this.#matches.map((m) => this.#itemFor(m)).filter((x) => x !== void 0);
|
|
3022
|
+
}
|
|
3023
|
+
get #highlightKey() {
|
|
3024
|
+
return this.#adapter.highlightKey ?? DEFAULT_HIGHLIGHT_KEY;
|
|
3025
|
+
}
|
|
3026
|
+
#onState(state) {
|
|
3027
|
+
if (!this.#open) return;
|
|
3028
|
+
const hasQuery = state.query.trim().length > 0;
|
|
3029
|
+
this.#matches = hasQuery ? [...state.results] : [];
|
|
3030
|
+
if (this.#matchIndex >= this.#matches.length) this.#matchIndex = 0;
|
|
3031
|
+
this.#applyMode();
|
|
3032
|
+
this.#host.requestUpdate();
|
|
3033
|
+
}
|
|
3034
|
+
#applyMode() {
|
|
3035
|
+
if (!(this.#query.trim().length > 0)) {
|
|
3036
|
+
if (this.#filterActive) {
|
|
3037
|
+
this.#adapter.applyFilter(void 0);
|
|
3038
|
+
this.#filterActive = false;
|
|
3039
|
+
}
|
|
3040
|
+
this.#applyHighlights();
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3043
|
+
switch (this.#mode) {
|
|
3044
|
+
case `navigate`:
|
|
3045
|
+
this.#adapter.focusItem(this.#itemFor(this.#matches[this.#matchIndex]));
|
|
3046
|
+
break;
|
|
3047
|
+
case `select`:
|
|
3048
|
+
this.#adapter.selectItems(this.#currentItems());
|
|
3049
|
+
break;
|
|
3050
|
+
case `filter`:
|
|
3051
|
+
this.#adapter.applyFilter(new Set(this.#matches.map((m) => m.item.key)));
|
|
3052
|
+
this.#filterActive = true;
|
|
3053
|
+
}
|
|
3054
|
+
this.#applyHighlights();
|
|
3055
|
+
}
|
|
3056
|
+
#applyHighlights() {
|
|
3057
|
+
const root = this.#adapter.highlightRoot?.();
|
|
3058
|
+
const selector = this.#adapter.highlightSelector;
|
|
3059
|
+
const keyOf = this.#adapter.highlightKeyOf;
|
|
3060
|
+
if (!root || !selector || !keyOf) return;
|
|
3061
|
+
if (this.#query.trim().length === 0) {
|
|
3062
|
+
clearHighlights({ key: this.#highlightKey });
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
const state = this.#search.state;
|
|
3066
|
+
const targets = buildHighlightTargets(root, selector, state.positionsByItem, (el) => {
|
|
3067
|
+
const key = keyOf(el);
|
|
3068
|
+
return key === void 0 ? void 0 : state.results.find((r) => r.item.key === key)?.item;
|
|
3069
|
+
}, this.#adapter.highlightText ?? ((el) => el.textContent ?? ``));
|
|
3070
|
+
try {
|
|
3071
|
+
applyHighlights(targets, { key: this.#highlightKey });
|
|
3072
|
+
} catch {
|
|
3073
|
+
clearHighlights({ key: this.#highlightKey });
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
#teardownEffects() {
|
|
3077
|
+
if (this.#filterActive) {
|
|
3078
|
+
this.#adapter.applyFilter(void 0);
|
|
3079
|
+
this.#filterActive = false;
|
|
3080
|
+
}
|
|
3081
|
+
clearHighlights({ key: this.#highlightKey });
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
//#endregion
|
|
3085
|
+
//#region src/incr-search/list-search-styles.ts
|
|
3086
|
+
/**
|
|
3087
|
+
* Styles for the incremental-search popover rendered by
|
|
3088
|
+
* `ListSearchController.renderField()`. Add to a component's `static styles`;
|
|
3089
|
+
* the class names are the contract between this fragment and `renderField()`.
|
|
3090
|
+
*
|
|
3091
|
+
* The host must give the popover a positioned ancestor (a wrapper with
|
|
3092
|
+
* `position: relative`, or `:host { position: relative }`). Tunables:
|
|
3093
|
+
* `--list-search-inset` (default `8px`) offsets it from the top-right corner.
|
|
3094
|
+
*
|
|
3095
|
+
* See `docs/infra-incremental.md`.
|
|
3096
|
+
*/
|
|
3097
|
+
const listSearchStyles = i$3`
|
|
3098
|
+
.list-search {
|
|
3099
|
+
position: absolute;
|
|
3100
|
+
top: var(--list-search-inset, 8px);
|
|
3101
|
+
right: var(--list-search-inset, 8px);
|
|
3102
|
+
z-index: 20;
|
|
3103
|
+
display: flex;
|
|
3104
|
+
flex-direction: column;
|
|
3105
|
+
gap: 4px;
|
|
3106
|
+
padding: 6px;
|
|
3107
|
+
box-sizing: border-box;
|
|
3108
|
+
background: var(--surface-4, #2a2a2a);
|
|
3109
|
+
color: var(--surface-3-text, inherit);
|
|
3110
|
+
border: 1px solid var(--border, #444);
|
|
3111
|
+
border-radius: var(--radius-m, 6px);
|
|
3112
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
|
|
3113
|
+
font-size: var(--text-s, 0.85em);
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
.list-search[hidden] {
|
|
3117
|
+
display: none;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
.list-search-row {
|
|
3121
|
+
display: flex;
|
|
3122
|
+
align-items: center;
|
|
3123
|
+
gap: 4px;
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
.list-search-mode {
|
|
3127
|
+
flex: none;
|
|
3128
|
+
padding: 1px 7px;
|
|
3129
|
+
border-radius: 999px;
|
|
3130
|
+
font-weight: 600;
|
|
3131
|
+
text-transform: capitalize;
|
|
3132
|
+
background: var(--accent, #60a5fa);
|
|
3133
|
+
color: var(--accent-text, #000);
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
.list-search-input {
|
|
3137
|
+
flex: 1;
|
|
3138
|
+
min-width: 140px;
|
|
3139
|
+
background: transparent;
|
|
3140
|
+
border: none;
|
|
3141
|
+
outline: none;
|
|
3142
|
+
color: inherit;
|
|
3143
|
+
font: inherit;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
.list-search-count {
|
|
3147
|
+
flex: none;
|
|
3148
|
+
opacity: 0.6;
|
|
3149
|
+
font-variant-numeric: tabular-nums;
|
|
3150
|
+
white-space: nowrap;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
.list-search-switch {
|
|
3154
|
+
display: flex;
|
|
3155
|
+
flex-wrap: wrap;
|
|
3156
|
+
gap: 4px;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
.list-search-btn {
|
|
3160
|
+
padding: 1px 7px;
|
|
3161
|
+
background: var(--surface-3, #333);
|
|
3162
|
+
color: inherit;
|
|
3163
|
+
border: 1px solid var(--border, #444);
|
|
3164
|
+
border-radius: var(--radius-s, 4px);
|
|
3165
|
+
cursor: pointer;
|
|
3166
|
+
font: inherit;
|
|
3167
|
+
text-transform: capitalize;
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
.list-search-btn:hover {
|
|
3171
|
+
background: var(--item-bg-tickled, #444);
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
.list-search-icon-btn {
|
|
3175
|
+
flex: none;
|
|
3176
|
+
padding: 0 2px;
|
|
3177
|
+
background: transparent;
|
|
3178
|
+
border: none;
|
|
3179
|
+
color: var(--surface-muted-text, #888);
|
|
3180
|
+
cursor: pointer;
|
|
3181
|
+
font-size: 1.1em;
|
|
3182
|
+
line-height: 1;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
.list-search-icon-btn:hover {
|
|
3186
|
+
color: inherit;
|
|
3187
|
+
}
|
|
3188
|
+
`;
|
|
3189
|
+
//#endregion
|
|
3190
|
+
//#region src/incr-search/tree-list-search-adapter.ts
|
|
3191
|
+
/**
|
|
3192
|
+
* A {@link ListSearchAdapter} over any `TreeSearchHost` (`ixfx-tree-list`,
|
|
3193
|
+
* `ixfx-miller-list`, `ixfx-crumb-navigation`). Navigate reveals the match
|
|
3194
|
+
* (expands its ancestors, moves the cursor); select drives `selectMany`; filter
|
|
3195
|
+
* sets `filterPredicate`. The item set is the whole tree — `getNodes()` returns
|
|
3196
|
+
* every node regardless of expansion or an active filter — so refining a query
|
|
3197
|
+
* can always re-surface a node. See `docs/infra-incremental.md`.
|
|
3198
|
+
*/
|
|
3199
|
+
function createTreeListSearchAdapter(host, options = {}) {
|
|
3200
|
+
return {
|
|
3201
|
+
getItems: () => host.getNodes(),
|
|
3202
|
+
getLabel: options.getLabel ?? ((item) => item.node.item.label),
|
|
3203
|
+
getKey: (item) => item.node.item.key,
|
|
3204
|
+
focusItem: (item) => {
|
|
3205
|
+
if (item) host.revealKey(item.node.item.key);
|
|
3206
|
+
},
|
|
3207
|
+
activateItem: (item) => {
|
|
3208
|
+
host.revealKey(item.node.item.key);
|
|
3209
|
+
host.select(item.node);
|
|
3210
|
+
},
|
|
3211
|
+
selectItems: (items) => {
|
|
3212
|
+
host.selectMany(items.map((i) => i.node));
|
|
3213
|
+
},
|
|
3214
|
+
applyFilter: (matched) => {
|
|
3215
|
+
host.filterPredicate = matched === void 0 ? void 0 : (node) => matched.has(node.item.key);
|
|
3216
|
+
},
|
|
3217
|
+
highlightRoot: () => host.shadowRoot,
|
|
3218
|
+
highlightSelector: options.labelSelector ?? `.tree-label`,
|
|
3219
|
+
highlightKeyOf: (el) => el.closest(`[data-key]`)?.dataset.key,
|
|
3220
|
+
highlightKey: options.highlightKey
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
//#endregion
|
|
2579
3224
|
//#region src/ac-token/ac-token.ts
|
|
2580
3225
|
const DEFAULT_TOKENIZER = (text) => text.split(/[\s,;]+/);
|
|
2581
3226
|
function makeLruCache(maxSize) {
|
|
@@ -3877,80 +4522,6 @@ __decorate([state()], RadioGroup.prototype, "_icons", void 0);
|
|
|
3877
4522
|
__decorate([query(`slot`)], RadioGroup.prototype, "_slot", void 0);
|
|
3878
4523
|
RadioGroup = __decorate([safeCustomElement(`ixfx-radio-group`)], RadioGroup);
|
|
3879
4524
|
//#endregion
|
|
3880
|
-
//#region src/interaction/keyed-list.ts
|
|
3881
|
-
/**
|
|
3882
|
-
* Helpers for key-based selection on the Element-flavoured list components
|
|
3883
|
-
* (`ixfx-vertical-list`, `ixfx-detail-list`, `ixfx-grid-list`).
|
|
3884
|
-
*
|
|
3885
|
-
* A "key" is a stable string the host puts on each item element — `data-key`
|
|
3886
|
-
* (preferred) or `data-value`. It survives the host rebuilding the item set
|
|
3887
|
-
* (re-fetch, re-sort, filter) where raw `Element` references do not.
|
|
3888
|
-
*/
|
|
3889
|
-
/**
|
|
3890
|
-
* The stable key for an item element: `data-key`, then `data-value`, else
|
|
3891
|
-
* `undefined`.
|
|
3892
|
-
*/
|
|
3893
|
-
function keyForElement(el) {
|
|
3894
|
-
return el.getAttribute(`data-key`) ?? el.getAttribute(`data-value`) ?? void 0;
|
|
3895
|
-
}
|
|
3896
|
-
/**
|
|
3897
|
-
* A lazily-built key → element index over a container. Rebuilt on first use
|
|
3898
|
-
* and after any child-list mutation under `root`; `invalidate()` forces a
|
|
3899
|
-
* rebuild on the next `get()`.
|
|
3900
|
-
*
|
|
3901
|
-
* ```ts
|
|
3902
|
-
* #keyIndex = new KeyedElementIndex(this);
|
|
3903
|
-
* // ...
|
|
3904
|
-
* const el = this.#keyIndex.get(`row-42`);
|
|
3905
|
-
* // on disconnect:
|
|
3906
|
-
* this.#keyIndex.dispose();
|
|
3907
|
-
* ```
|
|
3908
|
-
*/
|
|
3909
|
-
var KeyedElementIndex = class {
|
|
3910
|
-
#root;
|
|
3911
|
-
#selector;
|
|
3912
|
-
#map = /* @__PURE__ */ new Map();
|
|
3913
|
-
#dirty = true;
|
|
3914
|
-
#observer;
|
|
3915
|
-
/**
|
|
3916
|
-
* @param root Container whose descendants are indexed.
|
|
3917
|
-
* @param itemSelector Which descendants count as items (default: any `[data-key]`).
|
|
3918
|
-
*/
|
|
3919
|
-
constructor(root, itemSelector = `[data-key]`) {
|
|
3920
|
-
this.#root = root;
|
|
3921
|
-
this.#selector = itemSelector;
|
|
3922
|
-
this.#observer = new MutationObserver(() => {
|
|
3923
|
-
this.#dirty = true;
|
|
3924
|
-
});
|
|
3925
|
-
this.#observer.observe(root, {
|
|
3926
|
-
childList: true,
|
|
3927
|
-
subtree: true
|
|
3928
|
-
});
|
|
3929
|
-
}
|
|
3930
|
-
/** The element with this key, or `undefined`. First match wins on duplicates. */
|
|
3931
|
-
get(key) {
|
|
3932
|
-
if (this.#dirty) this.#rebuild();
|
|
3933
|
-
return this.#map.get(key);
|
|
3934
|
-
}
|
|
3935
|
-
/** Force a rebuild on the next `get()` — e.g. after a `data-key` attribute change. */
|
|
3936
|
-
invalidate() {
|
|
3937
|
-
this.#dirty = true;
|
|
3938
|
-
}
|
|
3939
|
-
/** Stop observing and drop the cache. */
|
|
3940
|
-
dispose() {
|
|
3941
|
-
this.#observer.disconnect();
|
|
3942
|
-
this.#map.clear();
|
|
3943
|
-
}
|
|
3944
|
-
#rebuild() {
|
|
3945
|
-
this.#map.clear();
|
|
3946
|
-
for (const el of this.#root.querySelectorAll(this.#selector)) {
|
|
3947
|
-
const key = keyForElement(el);
|
|
3948
|
-
if (key !== void 0 && !this.#map.has(key)) this.#map.set(key, el);
|
|
3949
|
-
}
|
|
3950
|
-
this.#dirty = false;
|
|
3951
|
-
}
|
|
3952
|
-
};
|
|
3953
|
-
//#endregion
|
|
3954
4525
|
//#region src/interaction/list-item-animation.ts
|
|
3955
4526
|
/**
|
|
3956
4527
|
* Shared insertion / removal animation for the list & tree components.
|
|
@@ -8557,6 +9128,16 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
|
|
|
8557
9128
|
if (!keyPath) return false;
|
|
8558
9129
|
return this.navigateTo(keyPath);
|
|
8559
9130
|
}
|
|
9131
|
+
/** `TreeComponent` parity — a crumb path always follows the selection, so this just navigates to `key`. */
|
|
9132
|
+
revealKey(key) {
|
|
9133
|
+
return this.navigateToNode((n) => n.item.key === key);
|
|
9134
|
+
}
|
|
9135
|
+
/** `TreeComponent` parity — a crumb path is a single selection, so only the last node is kept. */
|
|
9136
|
+
selectMany(nodes) {
|
|
9137
|
+
const last = [...nodes].at(-1);
|
|
9138
|
+
if (last) this.select(last);
|
|
9139
|
+
else this.clearSelection();
|
|
9140
|
+
}
|
|
8560
9141
|
#findNodePath(nodes, predicate, prefix = []) {
|
|
8561
9142
|
for (const node of nodes) {
|
|
8562
9143
|
const path = [...prefix, node.item.key];
|
|
@@ -11067,6 +11648,8 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11067
11648
|
this._sortedRows = [];
|
|
11068
11649
|
this._flat = [];
|
|
11069
11650
|
this._flatKeys = [];
|
|
11651
|
+
this.#searchKeyByRow = /* @__PURE__ */ new Map();
|
|
11652
|
+
this.search = new ListSearchController(this, { adapter: this.#createSearchAdapter() });
|
|
11070
11653
|
this.#prevFlatKeys = [];
|
|
11071
11654
|
this.#enteringRowKeys = /* @__PURE__ */ new Set();
|
|
11072
11655
|
this.#rowReflowTimer = 0;
|
|
@@ -11272,6 +11855,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11272
11855
|
tickledStyles,
|
|
11273
11856
|
tickledItemStyles,
|
|
11274
11857
|
listItemFadeKeyframes,
|
|
11858
|
+
listSearchStyles,
|
|
11275
11859
|
i$3`
|
|
11276
11860
|
:host {
|
|
11277
11861
|
--data-grid-row-height: 28px;
|
|
@@ -11280,6 +11864,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11280
11864
|
--data-grid-header-bg: var(--surface-2);
|
|
11281
11865
|
--data-grid-border: var(--border);
|
|
11282
11866
|
--data-grid-header-text: var(--surface-3-text);
|
|
11867
|
+
position: relative;
|
|
11283
11868
|
display: block;
|
|
11284
11869
|
overflow: hidden;
|
|
11285
11870
|
border: var(--data-grid-border);
|
|
@@ -11373,6 +11958,55 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11373
11958
|
`
|
|
11374
11959
|
];
|
|
11375
11960
|
}
|
|
11961
|
+
#searchRegistry;
|
|
11962
|
+
#unregisterSearchCommands;
|
|
11963
|
+
#searchKeyByRow;
|
|
11964
|
+
/** Command registry carrying this grid's `list.search.*` commands. */
|
|
11965
|
+
get registry() {
|
|
11966
|
+
return this.#searchRegistry;
|
|
11967
|
+
}
|
|
11968
|
+
#rowSearchText(row) {
|
|
11969
|
+
if (this.rowSearchText) return this.rowSearchText(row);
|
|
11970
|
+
const rec = row;
|
|
11971
|
+
return this._visibleColumns.map((c) => String(rec[c.key] ?? ``)).join(` `);
|
|
11972
|
+
}
|
|
11973
|
+
#createSearchAdapter() {
|
|
11974
|
+
return {
|
|
11975
|
+
getItems: () => {
|
|
11976
|
+
this.#searchKeyByRow.clear();
|
|
11977
|
+
this._sortedRows.forEach((r, i) => this.#searchKeyByRow.set(r, this.getRowKey(r, i)));
|
|
11978
|
+
return this._sortedRows;
|
|
11979
|
+
},
|
|
11980
|
+
getLabel: (row) => this.#rowSearchText(row),
|
|
11981
|
+
getKey: (row) => this.#searchKeyByRow.get(row) ?? String(this._sortedRows.indexOf(row)),
|
|
11982
|
+
focusItem: (row) => {
|
|
11983
|
+
if (row === void 0) return;
|
|
11984
|
+
const key = this.#searchKeyByRow.get(row);
|
|
11985
|
+
if (key !== void 0) this.tickleRow(key);
|
|
11986
|
+
},
|
|
11987
|
+
selectItems: (rows) => {
|
|
11988
|
+
this.selectKeys(rows.map((r) => this.#searchKeyByRow.get(r)).filter((k) => k !== void 0));
|
|
11989
|
+
},
|
|
11990
|
+
applyFilter: (matched) => {
|
|
11991
|
+
this.filterPredicate = matched === void 0 ? void 0 : (row) => {
|
|
11992
|
+
const k = this.#searchKeyByRow.get(row);
|
|
11993
|
+
return k === void 0 || matched.has(k);
|
|
11994
|
+
};
|
|
11995
|
+
}
|
|
11996
|
+
};
|
|
11997
|
+
}
|
|
11998
|
+
#effectiveSearchModes() {
|
|
11999
|
+
const raw = this.searchModes?.trim();
|
|
12000
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
12001
|
+
const requested = new Set(raw.split(/\s+/));
|
|
12002
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
12003
|
+
}
|
|
12004
|
+
#syncSearchCommands() {
|
|
12005
|
+
if (!this.#searchRegistry) return;
|
|
12006
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
12007
|
+
this.#unregisterSearchCommands?.();
|
|
12008
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
12009
|
+
}
|
|
11376
12010
|
/** Row-key snapshot from the previous update, for the insertion / removal animation. */
|
|
11377
12011
|
#prevFlatKeys;
|
|
11378
12012
|
#enteringRowKeys;
|
|
@@ -11385,17 +12019,29 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11385
12019
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
11386
12020
|
this.addEventListener(`keydown`, this._onKeyDown);
|
|
11387
12021
|
this.addEventListener(`focusout`, this._onFocusOut);
|
|
12022
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
12023
|
+
this.#syncSearchCommands();
|
|
11388
12024
|
}
|
|
11389
12025
|
disconnectedCallback() {
|
|
11390
12026
|
super.disconnectedCallback();
|
|
11391
12027
|
this.removeEventListener(`keydown`, this._onKeyDown);
|
|
11392
12028
|
this.removeEventListener(`focusout`, this._onFocusOut);
|
|
11393
12029
|
cancelAnimationFrame(this._raf);
|
|
12030
|
+
this.#unregisterSearchCommands?.();
|
|
12031
|
+
this.#unregisterSearchCommands = void 0;
|
|
12032
|
+
this.#searchRegistry = void 0;
|
|
11394
12033
|
if (this.#rowReflowTimer) {
|
|
11395
12034
|
clearTimeout(this.#rowReflowTimer);
|
|
11396
12035
|
this.#rowReflowTimer = 0;
|
|
11397
12036
|
}
|
|
11398
12037
|
}
|
|
12038
|
+
/**
|
|
12039
|
+
* Dispatch a command to this grid — routes through the command registry so
|
|
12040
|
+
* `list.search.*` (and any other registered commands) work.
|
|
12041
|
+
*/
|
|
12042
|
+
dispatch(commandId, args) {
|
|
12043
|
+
this.#searchRegistry?.invoke(commandId, args);
|
|
12044
|
+
}
|
|
11399
12045
|
firstUpdated() {
|
|
11400
12046
|
new ResizeObserver(() => {
|
|
11401
12047
|
if (!this._viewportEl) return;
|
|
@@ -11421,6 +12067,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11421
12067
|
willUpdate(changed) {
|
|
11422
12068
|
super.willUpdate(changed);
|
|
11423
12069
|
this.setAttribute(`data-input-mode`, this._tickled.inputMode);
|
|
12070
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
11424
12071
|
if (!this.#appendInProgress) {
|
|
11425
12072
|
if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`)) this._sortedRows = this._applySort([...this.rows]);
|
|
11426
12073
|
this._rebuildFlat();
|
|
@@ -11475,18 +12122,23 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11475
12122
|
return String(this.groupBy(row));
|
|
11476
12123
|
}
|
|
11477
12124
|
_rebuildFlat() {
|
|
12125
|
+
const filtered = this.filterPredicate !== void 0;
|
|
12126
|
+
const source = filtered ? this._sortedRows.filter((r) => this.filterPredicate(r)) : this._sortedRows;
|
|
11478
12127
|
if (!this.groupBy) {
|
|
11479
|
-
this._flat =
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
12128
|
+
this._flat = source.map((r, i) => {
|
|
12129
|
+
const idx = filtered ? this._sortedRows.indexOf(r) : i;
|
|
12130
|
+
return {
|
|
12131
|
+
type: `row`,
|
|
12132
|
+
key: this.getRowKey(r, idx),
|
|
12133
|
+
row: r,
|
|
12134
|
+
idx
|
|
12135
|
+
};
|
|
12136
|
+
});
|
|
11485
12137
|
this._flatKeys = this._flat.map((f) => f.key);
|
|
11486
12138
|
return;
|
|
11487
12139
|
}
|
|
11488
12140
|
const groups = /* @__PURE__ */ new Map();
|
|
11489
|
-
for (const r of
|
|
12141
|
+
for (const r of source) {
|
|
11490
12142
|
const k = this._getGroupKey(r) ?? ``;
|
|
11491
12143
|
const arr = groups.get(k);
|
|
11492
12144
|
if (arr) arr.push(r);
|
|
@@ -12356,6 +13008,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
12356
13008
|
})}
|
|
12357
13009
|
</div>
|
|
12358
13010
|
</div>
|
|
13011
|
+
${this.search.renderField()}
|
|
12359
13012
|
</div>
|
|
12360
13013
|
`;
|
|
12361
13014
|
}
|
|
@@ -12399,6 +13052,13 @@ __decorate([property({
|
|
|
12399
13052
|
attribute: `column-menu`
|
|
12400
13053
|
})], DataGridElement.prototype, "columnMenu", void 0);
|
|
12401
13054
|
__decorate([property({ attribute: false })], DataGridElement.prototype, "globalFormatter", void 0);
|
|
13055
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "filterPredicate", void 0);
|
|
13056
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "rowSearchText", void 0);
|
|
13057
|
+
__decorate([property({
|
|
13058
|
+
type: String,
|
|
13059
|
+
attribute: `search-modes`,
|
|
13060
|
+
reflect: true
|
|
13061
|
+
})], DataGridElement.prototype, "searchModes", void 0);
|
|
12402
13062
|
__decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
|
|
12403
13063
|
__decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
|
|
12404
13064
|
__decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
|
|
@@ -12684,8 +13344,8 @@ const ITEM_SELECTOR$2 = `:scope > li`;
|
|
|
12684
13344
|
const SEARCH_HIGHLIGHT_KEY = `detail-list-search`;
|
|
12685
13345
|
const LABEL_SPAN_CSS = `overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0`;
|
|
12686
13346
|
let DetailListElement = class DetailListElement extends i {
|
|
12687
|
-
constructor(...
|
|
12688
|
-
super(...
|
|
13347
|
+
constructor(..._args) {
|
|
13348
|
+
super(..._args);
|
|
12689
13349
|
this.selectionMode = `single`;
|
|
12690
13350
|
this.interactionMode = `standard`;
|
|
12691
13351
|
this.sort = `none`;
|
|
@@ -12695,8 +13355,6 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12695
13355
|
this.iconProperty = ``;
|
|
12696
13356
|
this.keyProperty = ``;
|
|
12697
13357
|
this.itemWrap = `stop`;
|
|
12698
|
-
this._searchVisible = false;
|
|
12699
|
-
this._searchQuery = ``;
|
|
12700
13358
|
this.#itemData = /* @__PURE__ */ new WeakMap();
|
|
12701
13359
|
this.#grid = [];
|
|
12702
13360
|
this.#positions = /* @__PURE__ */ new Map();
|
|
@@ -12733,6 +13391,11 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12733
13391
|
onStateChange: () => this._syncItemStates(),
|
|
12734
13392
|
itemWrap: this.itemWrap
|
|
12735
13393
|
});
|
|
13394
|
+
this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
|
|
13395
|
+
itemSelector: ITEM_SELECTOR$2,
|
|
13396
|
+
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
13397
|
+
highlightKey: SEARCH_HIGHLIGHT_KEY
|
|
13398
|
+
}) });
|
|
12736
13399
|
this._onPointerOver = (e) => {
|
|
12737
13400
|
const item = this._findListItem(e.composedPath());
|
|
12738
13401
|
if (item) this.tickled.handlePointerEnter(item);
|
|
@@ -12741,11 +13404,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12741
13404
|
this.tickled.handlePointerLeave(e);
|
|
12742
13405
|
};
|
|
12743
13406
|
this._onKeyDown = (e) => {
|
|
12744
|
-
if (
|
|
12745
|
-
e.preventDefault();
|
|
12746
|
-
this._openSearch();
|
|
12747
|
-
return;
|
|
12748
|
-
}
|
|
13407
|
+
if (this.search.isOpen) return;
|
|
12749
13408
|
if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
|
|
12750
13409
|
if (this.selection.canSelectAll()) {
|
|
12751
13410
|
e.preventDefault();
|
|
@@ -12753,11 +13412,6 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12753
13412
|
}
|
|
12754
13413
|
return;
|
|
12755
13414
|
}
|
|
12756
|
-
if (this._searchVisible && e.key === `Escape`) {
|
|
12757
|
-
e.preventDefault();
|
|
12758
|
-
this._closeSearch();
|
|
12759
|
-
return;
|
|
12760
|
-
}
|
|
12761
13415
|
switch (e.key) {
|
|
12762
13416
|
case `ArrowDown`:
|
|
12763
13417
|
e.preventDefault();
|
|
@@ -12826,44 +13480,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12826
13480
|
e.preventDefault();
|
|
12827
13481
|
this._listEl.scrollLeft += e.deltaY;
|
|
12828
13482
|
};
|
|
12829
|
-
this._onSearchInput = (e) => {
|
|
12830
|
-
const input = e.target;
|
|
12831
|
-
this._searchQuery = input.value;
|
|
12832
|
-
this.#search?.setQuery(this._searchQuery).then(() => this.#recomputeGeometry());
|
|
12833
|
-
};
|
|
12834
|
-
this._onSearchKeydown = (e) => {
|
|
12835
|
-
if (e.key === `Escape`) {
|
|
12836
|
-
e.stopPropagation();
|
|
12837
|
-
this._closeSearch();
|
|
12838
|
-
} else if (e.key === `ArrowDown`) {
|
|
12839
|
-
e.preventDefault();
|
|
12840
|
-
this.focus();
|
|
12841
|
-
this.#navigateDown();
|
|
12842
|
-
} else if (e.key === `ArrowUp`) {
|
|
12843
|
-
e.preventDefault();
|
|
12844
|
-
this.focus();
|
|
12845
|
-
this.#navigateUp();
|
|
12846
|
-
} else if (e.key === `ArrowRight`) e.preventDefault();
|
|
12847
|
-
else if (e.key === `ArrowLeft`) e.preventDefault();
|
|
12848
|
-
else if (e.key === `Enter`) {
|
|
12849
|
-
e.preventDefault();
|
|
12850
|
-
e.stopPropagation();
|
|
12851
|
-
const id = this.tickled.tickledId ?? this.#findFirstCheckable();
|
|
12852
|
-
if (id) {
|
|
12853
|
-
this._dispatch(`list-item-click`, { item: id });
|
|
12854
|
-
this._activateItem(id);
|
|
12855
|
-
this._closeSearch();
|
|
12856
|
-
}
|
|
12857
|
-
} else if (e.key === ` `) {
|
|
12858
|
-
const id = this.tickled.tickledId;
|
|
12859
|
-
if (id) {
|
|
12860
|
-
e.preventDefault();
|
|
12861
|
-
this.selection.handleSpace(id);
|
|
12862
|
-
}
|
|
12863
|
-
}
|
|
12864
|
-
};
|
|
12865
13483
|
}
|
|
12866
|
-
#search;
|
|
12867
13484
|
#itemData;
|
|
12868
13485
|
#grid;
|
|
12869
13486
|
#positions;
|
|
@@ -12875,6 +13492,24 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12875
13492
|
#builtFromProvider;
|
|
12876
13493
|
#providerBuilt;
|
|
12877
13494
|
#data;
|
|
13495
|
+
#searchRegistry;
|
|
13496
|
+
#unregisterSearchCommands;
|
|
13497
|
+
/** Command registry carrying this list's `list.search.*` commands. */
|
|
13498
|
+
get registry() {
|
|
13499
|
+
return this.#searchRegistry;
|
|
13500
|
+
}
|
|
13501
|
+
#effectiveSearchModes() {
|
|
13502
|
+
const raw = this.searchModes?.trim();
|
|
13503
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
13504
|
+
const requested = new Set(raw.split(/\s+/));
|
|
13505
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
13506
|
+
}
|
|
13507
|
+
#syncSearchCommands() {
|
|
13508
|
+
if (!this.#searchRegistry) return;
|
|
13509
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
13510
|
+
this.#unregisterSearchCommands?.();
|
|
13511
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
13512
|
+
}
|
|
12878
13513
|
connectedCallback() {
|
|
12879
13514
|
super.connectedCallback();
|
|
12880
13515
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
@@ -12897,6 +13532,8 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12897
13532
|
attributeFilter: [`hidden`]
|
|
12898
13533
|
});
|
|
12899
13534
|
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) this.#animatedItems.add(el);
|
|
13535
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
13536
|
+
this.#syncSearchCommands();
|
|
12900
13537
|
}
|
|
12901
13538
|
/**
|
|
12902
13539
|
* Collapse-in items appended to the light DOM (however they got there). A bulk
|
|
@@ -12930,23 +13567,18 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12930
13567
|
this.#resizeObserver = void 0;
|
|
12931
13568
|
this.#mutationObserver?.disconnect();
|
|
12932
13569
|
this.#mutationObserver = void 0;
|
|
12933
|
-
this.#
|
|
12934
|
-
this.#
|
|
13570
|
+
this.#unregisterSearchCommands?.();
|
|
13571
|
+
this.#unregisterSearchCommands = void 0;
|
|
13572
|
+
this.#searchRegistry = void 0;
|
|
12935
13573
|
}
|
|
12936
13574
|
firstUpdated() {
|
|
12937
13575
|
this.#processChildren();
|
|
12938
13576
|
if (this.filterPredicate) this.#applyFilterPredicate();
|
|
12939
|
-
this.#search = createElementIncrSearch({
|
|
12940
|
-
container: this,
|
|
12941
|
-
itemSelector: ITEM_SELECTOR$2,
|
|
12942
|
-
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
12943
|
-
getKey: (el) => el.getAttribute(`data-key`) ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
12944
|
-
highlightKey: SEARCH_HIGHLIGHT_KEY
|
|
12945
|
-
});
|
|
12946
13577
|
this.#recomputeGeometry();
|
|
12947
13578
|
}
|
|
12948
13579
|
updated(changed) {
|
|
12949
13580
|
super.updated(changed);
|
|
13581
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
12950
13582
|
if (changed.has(`provider`)) {
|
|
12951
13583
|
this.#builtFromProvider = [];
|
|
12952
13584
|
this.#providerBuilt = 0;
|
|
@@ -13040,8 +13672,14 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13040
13672
|
getItemData(item) {
|
|
13041
13673
|
return this.#itemData.get(item);
|
|
13042
13674
|
}
|
|
13043
|
-
/**
|
|
13044
|
-
|
|
13675
|
+
/**
|
|
13676
|
+
* Dispatch a command to this list — used by `ixfx-grouped-item-lister` to
|
|
13677
|
+
* forward commands. Routes through the command registry (so `list.search.*`
|
|
13678
|
+
* and any other registered commands work).
|
|
13679
|
+
*/
|
|
13680
|
+
dispatch(commandId, args) {
|
|
13681
|
+
this.#searchRegistry?.invoke(commandId, args);
|
|
13682
|
+
}
|
|
13045
13683
|
select(item) {
|
|
13046
13684
|
this.selection.replace(item);
|
|
13047
13685
|
}
|
|
@@ -13525,16 +14163,6 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13525
14163
|
composed: true
|
|
13526
14164
|
}));
|
|
13527
14165
|
}
|
|
13528
|
-
_openSearch() {
|
|
13529
|
-
this._searchVisible = true;
|
|
13530
|
-
this.updateComplete.then(() => this._searchInputEl?.focus());
|
|
13531
|
-
}
|
|
13532
|
-
_closeSearch() {
|
|
13533
|
-
this._searchVisible = false;
|
|
13534
|
-
this._searchQuery = ``;
|
|
13535
|
-
this.#search?.clearQuery().then(() => this.#recomputeGeometry());
|
|
13536
|
-
this.focus();
|
|
13537
|
-
}
|
|
13538
14166
|
_activateItem(item) {
|
|
13539
14167
|
if (this.interactionMode !== `manual`) this.selection.replace(item);
|
|
13540
14168
|
this._dispatch(`list-activate`, { item });
|
|
@@ -13543,6 +14171,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13543
14171
|
this.styles = [
|
|
13544
14172
|
themeFallbacks,
|
|
13545
14173
|
scrollFadeStyles(`x`),
|
|
14174
|
+
listSearchStyles,
|
|
13546
14175
|
i$3`
|
|
13547
14176
|
:host {
|
|
13548
14177
|
display: block;
|
|
@@ -13664,48 +14293,6 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13664
14293
|
.loading[hidden] {
|
|
13665
14294
|
display: none;
|
|
13666
14295
|
}
|
|
13667
|
-
|
|
13668
|
-
.search-overlay {
|
|
13669
|
-
position: absolute;
|
|
13670
|
-
top: 8px;
|
|
13671
|
-
right: 8px;
|
|
13672
|
-
z-index: 10;
|
|
13673
|
-
display: flex;
|
|
13674
|
-
align-items: center;
|
|
13675
|
-
gap: 4px;
|
|
13676
|
-
background: var(--surface-4, #2a2a2a);
|
|
13677
|
-
border: 1px solid var(--border, #444);
|
|
13678
|
-
border-radius: var(--radius-m, 4px);
|
|
13679
|
-
padding: 4px 6px;
|
|
13680
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
13681
|
-
}
|
|
13682
|
-
|
|
13683
|
-
.search-overlay[hidden] {
|
|
13684
|
-
display: none;
|
|
13685
|
-
}
|
|
13686
|
-
|
|
13687
|
-
.search-input {
|
|
13688
|
-
background: transparent;
|
|
13689
|
-
border: none;
|
|
13690
|
-
outline: none;
|
|
13691
|
-
color: inherit;
|
|
13692
|
-
font-size: var(--text-m, 0.875em);
|
|
13693
|
-
min-width: 140px;
|
|
13694
|
-
}
|
|
13695
|
-
|
|
13696
|
-
.search-close {
|
|
13697
|
-
background: transparent;
|
|
13698
|
-
border: none;
|
|
13699
|
-
color: var(--surface-muted-text, #888);
|
|
13700
|
-
cursor: pointer;
|
|
13701
|
-
font-size: 1.1em;
|
|
13702
|
-
line-height: 1;
|
|
13703
|
-
padding: 0 2px;
|
|
13704
|
-
}
|
|
13705
|
-
|
|
13706
|
-
.search-close:hover {
|
|
13707
|
-
color: inherit;
|
|
13708
|
-
}
|
|
13709
14296
|
`
|
|
13710
14297
|
];
|
|
13711
14298
|
}
|
|
@@ -13720,21 +14307,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13720
14307
|
<div class="loading" part="loading" ?hidden=${!loading}>
|
|
13721
14308
|
${loading ? progress !== void 0 ? `${Math.round(progress * 100)}%` : `loading…` : A}
|
|
13722
14309
|
</div>
|
|
13723
|
-
|
|
13724
|
-
class="search-overlay"
|
|
13725
|
-
part="search-overlay"
|
|
13726
|
-
?hidden=${!this._searchVisible}
|
|
13727
|
-
>
|
|
13728
|
-
<input
|
|
13729
|
-
type="text"
|
|
13730
|
-
class="search-input"
|
|
13731
|
-
placeholder="Search…"
|
|
13732
|
-
.value=${this._searchQuery}
|
|
13733
|
-
@input=${this._onSearchInput}
|
|
13734
|
-
@keydown=${this._onSearchKeydown}
|
|
13735
|
-
/>
|
|
13736
|
-
<button class="search-close" @click=${this._closeSearch} title="Close search (Esc)">×</button>
|
|
13737
|
-
</div>
|
|
14310
|
+
${this.search.renderField()}
|
|
13738
14311
|
</div>
|
|
13739
14312
|
`;
|
|
13740
14313
|
}
|
|
@@ -13774,9 +14347,11 @@ __decorate([property({
|
|
|
13774
14347
|
attribute: `item-wrap`
|
|
13775
14348
|
})], DetailListElement.prototype, "itemWrap", void 0);
|
|
13776
14349
|
__decorate([property({ attribute: false })], DetailListElement.prototype, "checkboxRenderer", void 0);
|
|
13777
|
-
__decorate([
|
|
13778
|
-
|
|
13779
|
-
|
|
14350
|
+
__decorate([property({
|
|
14351
|
+
type: String,
|
|
14352
|
+
attribute: `search-modes`,
|
|
14353
|
+
reflect: true
|
|
14354
|
+
})], DetailListElement.prototype, "searchModes", void 0);
|
|
13780
14355
|
__decorate([query(`ul`)], DetailListElement.prototype, "_listEl", void 0);
|
|
13781
14356
|
DetailListElement = __decorate([safeCustomElement(`ixfx-detail-list`)], DetailListElement);
|
|
13782
14357
|
//#endregion
|
|
@@ -19619,6 +20194,11 @@ let GridListElement = class GridListElement extends i {
|
|
|
19619
20194
|
onStateChange: () => this._syncItemStates(),
|
|
19620
20195
|
itemWrap: this.#itemWrap
|
|
19621
20196
|
});
|
|
20197
|
+
this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
|
|
20198
|
+
itemSelector: ITEM_SELECTOR$1,
|
|
20199
|
+
getLabel: (el) => el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
20200
|
+
highlightKey: `grid-list-search`
|
|
20201
|
+
}) });
|
|
19622
20202
|
this.#onScroll = (e) => {
|
|
19623
20203
|
const el = e.target;
|
|
19624
20204
|
this.#cancelScrollRaf();
|
|
@@ -19645,7 +20225,10 @@ let GridListElement = class GridListElement extends i {
|
|
|
19645
20225
|
const from = this.tickled.tickledId ? this.#items.indexOf(this.tickled.tickledId) : -1;
|
|
19646
20226
|
const nextIndex = Math.max(0, Math.min(count - 1, from + delta));
|
|
19647
20227
|
const next = this.#items[nextIndex];
|
|
19648
|
-
if (next)
|
|
20228
|
+
if (next) {
|
|
20229
|
+
this.tickled.handleKeyboardNavigation(next);
|
|
20230
|
+
this.#scrollItemIntoView(next);
|
|
20231
|
+
}
|
|
19649
20232
|
};
|
|
19650
20233
|
const perLine = this.orientation === `grid` ? win.cols : 1;
|
|
19651
20234
|
switch (e.key) {
|
|
@@ -19663,9 +20246,11 @@ let GridListElement = class GridListElement extends i {
|
|
|
19663
20246
|
break;
|
|
19664
20247
|
case `Home`:
|
|
19665
20248
|
this.tickled.handleKeyboardNavigation(`first`);
|
|
20249
|
+
if (this.tickled.tickledId) this.#scrollItemIntoView(this.tickled.tickledId);
|
|
19666
20250
|
break;
|
|
19667
20251
|
case `End`:
|
|
19668
20252
|
this.tickled.handleKeyboardNavigation(`last`);
|
|
20253
|
+
if (this.tickled.tickledId) this.#scrollItemIntoView(this.tickled.tickledId);
|
|
19669
20254
|
break;
|
|
19670
20255
|
case `PageUp`:
|
|
19671
20256
|
case `PageDown`: {
|
|
@@ -19708,11 +20293,13 @@ let GridListElement = class GridListElement extends i {
|
|
|
19708
20293
|
scrollFadeStyles(`x`),
|
|
19709
20294
|
scrollFadeStyles(`y`),
|
|
19710
20295
|
listItemFadeKeyframes,
|
|
20296
|
+
listSearchStyles,
|
|
19711
20297
|
i$3`
|
|
19712
20298
|
:host {
|
|
19713
20299
|
--grid-list-item-bg: var(--item-bg);
|
|
19714
20300
|
--grid-list-radius: var(--radius-m);
|
|
19715
20301
|
--grid-list-gap: var(--space-xs);
|
|
20302
|
+
position: relative;
|
|
19716
20303
|
display: block;
|
|
19717
20304
|
height: 100%;
|
|
19718
20305
|
min-height: 2em;
|
|
@@ -19825,16 +20412,19 @@ let GridListElement = class GridListElement extends i {
|
|
|
19825
20412
|
overflow: hidden;
|
|
19826
20413
|
transition: background var(--transition), border-color var(--transition);
|
|
19827
20414
|
}
|
|
19828
|
-
:host(:not([caption-style="separate"])) .cell:hover
|
|
20415
|
+
:host(:not([caption-style="separate"])) .cell:hover,
|
|
20416
|
+
:host(:not([caption-style="separate"])) .cell.tickled {
|
|
19829
20417
|
background: var(--item-bg-tickled);
|
|
19830
|
-
border
|
|
20418
|
+
border: var(--item-border-tickled);
|
|
19831
20419
|
}
|
|
19832
20420
|
:host(:not([caption-style="separate"])) .cell.selected {
|
|
19833
20421
|
outline: 2px solid var(--accent);
|
|
19834
20422
|
outline-offset: -2px;
|
|
19835
20423
|
}
|
|
19836
|
-
:host(:not([caption-style="separate"])) .cell.tickled {
|
|
19837
|
-
|
|
20424
|
+
:host(:not([caption-style="separate"])) .cell.selected.tickled {
|
|
20425
|
+
background: var(--item-bg-selected-tickled);
|
|
20426
|
+
outline: 2px dashed var(--surface-3);
|
|
20427
|
+
outline-offset: -2px;
|
|
19838
20428
|
}
|
|
19839
20429
|
|
|
19840
20430
|
/* under: caption is a strip inside the box, below the content */
|
|
@@ -19893,9 +20483,10 @@ let GridListElement = class GridListElement extends i {
|
|
|
19893
20483
|
background: var(--grid-list-item-bg);
|
|
19894
20484
|
border: 1px solid transparent;
|
|
19895
20485
|
}
|
|
19896
|
-
:host([caption-style="separate"]) .cell:hover .thumb
|
|
20486
|
+
:host([caption-style="separate"]) .cell:hover .thumb,
|
|
20487
|
+
:host([caption-style="separate"]) .cell.tickled .thumb {
|
|
19897
20488
|
background: var(--item-bg-tickled);
|
|
19898
|
-
border
|
|
20489
|
+
border: var(--item-border-tickled);
|
|
19899
20490
|
}
|
|
19900
20491
|
:host([caption-style="separate"]) .caption {
|
|
19901
20492
|
display: flex;
|
|
@@ -19907,20 +20498,27 @@ let GridListElement = class GridListElement extends i {
|
|
|
19907
20498
|
}
|
|
19908
20499
|
:host([caption-style="separate"][caption-align="left"]) .caption { align-self: flex-start; }
|
|
19909
20500
|
:host([caption-style="separate"][caption-align="right"]) .caption { align-self: flex-end; }
|
|
19910
|
-
:host([caption-style="separate"]) .cell.selected .thumb
|
|
19911
|
-
:host([caption-style="separate"]) .cell.tickled .thumb {
|
|
20501
|
+
:host([caption-style="separate"]) .cell.selected .thumb {
|
|
19912
20502
|
outline: 2px solid var(--accent);
|
|
19913
20503
|
outline-offset: 2px;
|
|
19914
20504
|
}
|
|
19915
|
-
:host([caption-style="separate"]) .cell.tickled .thumb {
|
|
19916
|
-
|
|
20505
|
+
:host([caption-style="separate"]) .cell.selected.tickled .thumb {
|
|
20506
|
+
background: var(--item-bg-selected-tickled);
|
|
20507
|
+
outline: 2px dashed var(--surface-3);
|
|
20508
|
+
outline-offset: 2px;
|
|
19917
20509
|
}
|
|
19918
20510
|
:host([caption-style="separate"]) .cell.selected .caption {
|
|
19919
20511
|
background: var(--accent);
|
|
19920
20512
|
color: var(--accent-text, #000);
|
|
19921
20513
|
}
|
|
19922
20514
|
:host([caption-style="separate"]) .cell.tickled .caption {
|
|
19923
|
-
|
|
20515
|
+
background: var(--item-bg-tickled);
|
|
20516
|
+
outline: var(--item-border-tickled);
|
|
20517
|
+
}
|
|
20518
|
+
:host([caption-style="separate"]) .cell.selected.tickled .caption {
|
|
20519
|
+
background: var(--item-bg-selected-tickled);
|
|
20520
|
+
outline: 2px dashed var(--surface-3);
|
|
20521
|
+
outline-offset: -2px;
|
|
19924
20522
|
}
|
|
19925
20523
|
|
|
19926
20524
|
/* ---- Insertion / removal animation ----
|
|
@@ -19973,6 +20571,25 @@ let GridListElement = class GridListElement extends i {
|
|
|
19973
20571
|
*/
|
|
19974
20572
|
#slotNames;
|
|
19975
20573
|
#slotSeq;
|
|
20574
|
+
#searchRegistry;
|
|
20575
|
+
#unregisterSearchCommands;
|
|
20576
|
+
/** Command registry carrying this grid's `list.search.*` commands. */
|
|
20577
|
+
get registry() {
|
|
20578
|
+
return this.#searchRegistry;
|
|
20579
|
+
}
|
|
20580
|
+
/** Effective search modes: this component's capability (all three) ∩ the `search-modes` attribute. */
|
|
20581
|
+
#effectiveSearchModes() {
|
|
20582
|
+
const raw = this.searchModes?.trim();
|
|
20583
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
20584
|
+
const requested = new Set(raw.split(/\s+/));
|
|
20585
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
20586
|
+
}
|
|
20587
|
+
#syncSearchCommands() {
|
|
20588
|
+
if (!this.#searchRegistry) return;
|
|
20589
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
20590
|
+
this.#unregisterSearchCommands?.();
|
|
20591
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
20592
|
+
}
|
|
19976
20593
|
connectedCallback() {
|
|
19977
20594
|
super.connectedCallback();
|
|
19978
20595
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
@@ -19984,7 +20601,12 @@ let GridListElement = class GridListElement extends i {
|
|
|
19984
20601
|
]) if (!this.hasAttribute(attr)) this.setAttribute(attr, value);
|
|
19985
20602
|
this.addEventListener(`keydown`, this._onKeyDown);
|
|
19986
20603
|
this.#mutationObserver ??= new MutationObserver((records) => this.#onChildrenChanged(records));
|
|
19987
|
-
this.#mutationObserver.observe(this, {
|
|
20604
|
+
this.#mutationObserver.observe(this, {
|
|
20605
|
+
childList: true,
|
|
20606
|
+
attributeFilter: [`hidden`]
|
|
20607
|
+
});
|
|
20608
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
20609
|
+
this.#syncSearchCommands();
|
|
19988
20610
|
}
|
|
19989
20611
|
disconnectedCallback() {
|
|
19990
20612
|
super.disconnectedCallback();
|
|
@@ -19993,6 +20615,9 @@ let GridListElement = class GridListElement extends i {
|
|
|
19993
20615
|
this.#resizeObserver = void 0;
|
|
19994
20616
|
this.#mutationObserver?.disconnect();
|
|
19995
20617
|
this.#cancelScrollRaf();
|
|
20618
|
+
this.#unregisterSearchCommands?.();
|
|
20619
|
+
this.#unregisterSearchCommands = void 0;
|
|
20620
|
+
this.#searchRegistry = void 0;
|
|
19996
20621
|
if (this.#reflowTimer) {
|
|
19997
20622
|
clearTimeout(this.#reflowTimer);
|
|
19998
20623
|
this.#reflowTimer = 0;
|
|
@@ -20060,6 +20685,8 @@ let GridListElement = class GridListElement extends i {
|
|
|
20060
20685
|
this.requestUpdate();
|
|
20061
20686
|
}
|
|
20062
20687
|
willUpdate(changed) {
|
|
20688
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
20689
|
+
if (changed.has(`filterPredicate`)) this.#syncItems();
|
|
20063
20690
|
if (changed.has(`itemSize`) || changed.has(`gap`)) {
|
|
20064
20691
|
this.itemSize = Math.max(MIN_ITEM_SIZE, Math.round(this.itemSize));
|
|
20065
20692
|
this.gap = Math.max(0, Math.round(this.gap));
|
|
@@ -20144,7 +20771,7 @@ let GridListElement = class GridListElement extends i {
|
|
|
20144
20771
|
}
|
|
20145
20772
|
}
|
|
20146
20773
|
#syncItems() {
|
|
20147
|
-
this.#items = Array.from(this.querySelectorAll(ITEM_SELECTOR$1));
|
|
20774
|
+
this.#items = Array.from(this.querySelectorAll(ITEM_SELECTOR$1)).filter((el) => !el.hidden && (this.filterPredicate === void 0 || this.filterPredicate(el)));
|
|
20148
20775
|
for (const el of this.#items) {
|
|
20149
20776
|
const name = this.#slotNameFor(el);
|
|
20150
20777
|
if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
|
|
@@ -20300,6 +20927,7 @@ let GridListElement = class GridListElement extends i {
|
|
|
20300
20927
|
${repeat(slice, (el) => this.#slotNameFor(el), (el, i) => this.#renderCell(el, win.startIndex + i, win))}
|
|
20301
20928
|
</div>
|
|
20302
20929
|
</div>
|
|
20930
|
+
${this.search.renderField()}
|
|
20303
20931
|
`;
|
|
20304
20932
|
}
|
|
20305
20933
|
#renderCell(el, index, win) {
|
|
@@ -20396,6 +21024,12 @@ __decorate([property({
|
|
|
20396
21024
|
type: String,
|
|
20397
21025
|
attribute: `item-wrap`
|
|
20398
21026
|
})], GridListElement.prototype, "itemWrap", void 0);
|
|
21027
|
+
__decorate([property({ attribute: false })], GridListElement.prototype, "filterPredicate", void 0);
|
|
21028
|
+
__decorate([property({
|
|
21029
|
+
type: String,
|
|
21030
|
+
attribute: `search-modes`,
|
|
21031
|
+
reflect: true
|
|
21032
|
+
})], GridListElement.prototype, "searchModes", void 0);
|
|
20399
21033
|
__decorate([state()], GridListElement.prototype, "_scrollTop", void 0);
|
|
20400
21034
|
__decorate([state()], GridListElement.prototype, "_scrollLeft", void 0);
|
|
20401
21035
|
__decorate([state()], GridListElement.prototype, "_viewportHeight", void 0);
|
|
@@ -20778,6 +21412,10 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
20778
21412
|
}
|
|
20779
21413
|
});
|
|
20780
21414
|
this._focusedDepth = 0;
|
|
21415
|
+
this.search = new ListSearchController(this, { adapter: createTreeListSearchAdapter(this, {
|
|
21416
|
+
labelSelector: `.tree-label`,
|
|
21417
|
+
highlightKey: `incr-search-highlight`
|
|
21418
|
+
}) });
|
|
20781
21419
|
this.#config = { indent: 20 };
|
|
20782
21420
|
this.#renderedKeys = [];
|
|
20783
21421
|
this.#lastNodeByKey = /* @__PURE__ */ new Map();
|
|
@@ -20786,6 +21424,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
20786
21424
|
this.#leavingAnimating = /* @__PURE__ */ new Set();
|
|
20787
21425
|
this.#treeAnimReady = false;
|
|
20788
21426
|
this.#onKeyDown = (event) => {
|
|
21427
|
+
if (this.search.isOpen) return;
|
|
20789
21428
|
const nodes = this.#getAllNodes();
|
|
20790
21429
|
if (nodes.length === 0) return;
|
|
20791
21430
|
if ((event.ctrlKey || event.metaKey) && event.key === `a`) {
|
|
@@ -20906,6 +21545,19 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
20906
21545
|
get selectedNodes() {
|
|
20907
21546
|
return this._selectedNodes;
|
|
20908
21547
|
}
|
|
21548
|
+
#unregisterSearchCommands;
|
|
21549
|
+
#effectiveSearchModes() {
|
|
21550
|
+
const raw = this.searchModes?.trim();
|
|
21551
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
21552
|
+
const requested = new Set(raw.split(/\s+/));
|
|
21553
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
21554
|
+
}
|
|
21555
|
+
#syncSearchCommands() {
|
|
21556
|
+
if (!this.#ownedRegistry) return;
|
|
21557
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
21558
|
+
this.#unregisterSearchCommands?.();
|
|
21559
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#ownedRegistry, this.search);
|
|
21560
|
+
}
|
|
20909
21561
|
#pendingNode;
|
|
20910
21562
|
#pendingController;
|
|
20911
21563
|
#modelUnsubscribe;
|
|
@@ -20948,6 +21600,13 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
20948
21600
|
_isLeaving(node) {
|
|
20949
21601
|
return this.#leaving.has(node.item.key);
|
|
20950
21602
|
}
|
|
21603
|
+
/**
|
|
21604
|
+
* True when `filterPredicate` is set and neither `node` nor any descendant
|
|
21605
|
+
* matches — the row should carry the `filtered` class (`display: none`).
|
|
21606
|
+
*/
|
|
21607
|
+
_isFilteredOut(node, depth) {
|
|
21608
|
+
return this.filterPredicate !== void 0 && !this.#matchesFilter(node, depth);
|
|
21609
|
+
}
|
|
20951
21610
|
/** Splice the fading ghost rows back into the visible list near where they were. */
|
|
20952
21611
|
#weaveLeaving(nodes) {
|
|
20953
21612
|
if (this.#leaving.size === 0) return nodes;
|
|
@@ -21044,11 +21703,14 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21044
21703
|
execute: () => this.deselectAll()
|
|
21045
21704
|
});
|
|
21046
21705
|
this.registry = registry;
|
|
21706
|
+
this.#syncSearchCommands();
|
|
21047
21707
|
}
|
|
21048
21708
|
disconnectedCallback() {
|
|
21049
21709
|
super.disconnectedCallback();
|
|
21050
21710
|
this.#modelUnsubscribe?.();
|
|
21051
21711
|
this.#modelUnsubscribe = void 0;
|
|
21712
|
+
this.#unregisterSearchCommands?.();
|
|
21713
|
+
this.#unregisterSearchCommands = void 0;
|
|
21052
21714
|
if (this.#ownedRegistry) {
|
|
21053
21715
|
this.#ownedRegistry.unregister(`select-all`);
|
|
21054
21716
|
this.#ownedRegistry.unregister(`deselect-all`);
|
|
@@ -21146,6 +21808,28 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21146
21808
|
if (!keyPath) return false;
|
|
21147
21809
|
return this.navigateTo(keyPath);
|
|
21148
21810
|
}
|
|
21811
|
+
/** Expand ancestors + move the cursor to `key`, without touching the selection (search "navigate" mode). */
|
|
21812
|
+
revealKey(key) {
|
|
21813
|
+
if (!this.root?.children) return false;
|
|
21814
|
+
const path = this.#findNodePath(this.root.children, (n) => n.item.key === key);
|
|
21815
|
+
if (!path) return false;
|
|
21816
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
21817
|
+
this.#applyExclusivity(path[i]);
|
|
21818
|
+
this._expandedKeys.add(path[i]);
|
|
21819
|
+
}
|
|
21820
|
+
this.tickled.setTickled(key);
|
|
21821
|
+
this.requestUpdate();
|
|
21822
|
+
this.updateComplete.then(() => this.#scrollKeyIntoView(key));
|
|
21823
|
+
return true;
|
|
21824
|
+
}
|
|
21825
|
+
/** Replace the selection with `nodes` (subject to `selectionFilter`) — search "select" mode. */
|
|
21826
|
+
selectMany(nodes) {
|
|
21827
|
+
const prev = new Set(this._selectedNodes);
|
|
21828
|
+
this._selectedNodes = new Set([...nodes].filter((n) => this.#canSelect(n)));
|
|
21829
|
+
this.selectedNode = [...this._selectedNodes].at(-1);
|
|
21830
|
+
this.#dispatchSelect(prev);
|
|
21831
|
+
this.requestUpdate();
|
|
21832
|
+
}
|
|
21149
21833
|
getNodes() {
|
|
21150
21834
|
if (!this.root?.children) return [];
|
|
21151
21835
|
const result = [];
|
|
@@ -21549,7 +22233,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21549
22233
|
node: n,
|
|
21550
22234
|
depth: d
|
|
21551
22235
|
});
|
|
21552
|
-
if (
|
|
22236
|
+
if (n.children && this._isExpanded(n, d)) for (const child of n.children) visit(child, d + 1);
|
|
21553
22237
|
};
|
|
21554
22238
|
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
21555
22239
|
return result;
|
|
@@ -21606,6 +22290,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21606
22290
|
>
|
|
21607
22291
|
${this.root ? this.#renderNodes(nodes) : A}
|
|
21608
22292
|
</div>
|
|
22293
|
+
${this.search.renderField()}
|
|
21609
22294
|
`;
|
|
21610
22295
|
}
|
|
21611
22296
|
#onDblClick;
|
|
@@ -21617,7 +22302,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21617
22302
|
`;
|
|
21618
22303
|
return b`
|
|
21619
22304
|
<ul class="tree-list" role="tree">
|
|
21620
|
-
${this.#weaveLeaving(nodes).
|
|
22305
|
+
${repeat(this.#weaveLeaving(nodes), ({ node }) => node.item.key, ({ node, depth }) => this._renderNode(node, depth))}
|
|
21621
22306
|
</ul>
|
|
21622
22307
|
`;
|
|
21623
22308
|
}
|
|
@@ -21627,7 +22312,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21627
22312
|
const isBranch = node.item.isLeaf !== true;
|
|
21628
22313
|
const isExpanded = this._isExpanded(node, depth);
|
|
21629
22314
|
const isPending = this.#pendingNode === node;
|
|
21630
|
-
const isFiltered = this.
|
|
22315
|
+
const isFiltered = this._isFilteredOut(node, depth);
|
|
21631
22316
|
const isLeaving = this._isLeaving(node);
|
|
21632
22317
|
return b`
|
|
21633
22318
|
<li
|
|
@@ -21662,6 +22347,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21662
22347
|
themeFallbacks,
|
|
21663
22348
|
tickledStyles,
|
|
21664
22349
|
tickledItemStyles,
|
|
22350
|
+
listSearchStyles,
|
|
21665
22351
|
i$3`
|
|
21666
22352
|
:host {
|
|
21667
22353
|
--tree-bg: var(--surface-3);
|
|
@@ -21672,16 +22358,23 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21672
22358
|
--tree-caret-size: 10px;
|
|
21673
22359
|
--tree-radius: var(--radius-s);
|
|
21674
22360
|
|
|
22361
|
+
position: relative;
|
|
21675
22362
|
display: block;
|
|
21676
|
-
overflow:
|
|
22363
|
+
overflow: hidden;
|
|
21677
22364
|
box-sizing: border-box;
|
|
21678
22365
|
background: var(--tree-bg);
|
|
21679
22366
|
color: var(--tree-text);
|
|
21680
22367
|
}
|
|
21681
22368
|
|
|
22369
|
+
/* min-height (not height) so the tree still grows to its content when the
|
|
22370
|
+
host has no explicit height; when it does, this fills it and scrolls.
|
|
22371
|
+
The scroll lives here, not on :host, so the search popover (a sibling of
|
|
22372
|
+
#container, positioned against :host) stays put while the list scrolls. */
|
|
21682
22373
|
#container {
|
|
21683
22374
|
outline: none;
|
|
21684
22375
|
min-height: 100%;
|
|
22376
|
+
max-height: 100%;
|
|
22377
|
+
overflow: auto;
|
|
21685
22378
|
}
|
|
21686
22379
|
|
|
21687
22380
|
.tree-list {
|
|
@@ -21795,6 +22488,11 @@ __decorate([property({
|
|
|
21795
22488
|
attribute: `disable-keyboard-navigation`
|
|
21796
22489
|
})], TreeBaseElement.prototype, "disableKeyboardNavigation", void 0);
|
|
21797
22490
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "registry", void 0);
|
|
22491
|
+
__decorate([property({
|
|
22492
|
+
type: String,
|
|
22493
|
+
attribute: `search-modes`,
|
|
22494
|
+
reflect: true
|
|
22495
|
+
})], TreeBaseElement.prototype, "searchModes", void 0);
|
|
21798
22496
|
__decorate([state()], TreeBaseElement.prototype, "_expandedKeys", void 0);
|
|
21799
22497
|
__decorate([state()], TreeBaseElement.prototype, "_focusedDepth", void 0);
|
|
21800
22498
|
TreeBaseElement = __decorate([safeCustomElement(`ixfx-tree-base`)], TreeBaseElement);
|
|
@@ -21875,9 +22573,10 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
|
21875
22573
|
const isPending = this._pendingNode === node;
|
|
21876
22574
|
const hasIconAtDepth = this.#depthsWithIcons.has(depth);
|
|
21877
22575
|
const isLeaving = this._isLeaving(node);
|
|
22576
|
+
const isFiltered = this._isFilteredOut(node, depth);
|
|
21878
22577
|
return b`
|
|
21879
22578
|
<li
|
|
21880
|
-
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``}"
|
|
22579
|
+
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``} ${isFiltered ? `filtered` : ``}"
|
|
21881
22580
|
data-key=${node.item.key}
|
|
21882
22581
|
role="treeitem"
|
|
21883
22582
|
aria-selected=${selected}
|
|
@@ -22146,13 +22845,11 @@ function createTreeListAdapter(toTreeItem, options) {
|
|
|
22146
22845
|
//#region src/vertical-list/vertical-list.ts
|
|
22147
22846
|
const ITEM_SELECTOR = `li, [data-list-item]`;
|
|
22148
22847
|
let VerticalListElement = class VerticalListElement extends i {
|
|
22149
|
-
constructor(...
|
|
22150
|
-
super(...
|
|
22848
|
+
constructor(..._args) {
|
|
22849
|
+
super(..._args);
|
|
22151
22850
|
this.selectionMode = `single`;
|
|
22152
22851
|
this.interactionMode = `standard`;
|
|
22153
22852
|
this.itemWrap = `stop`;
|
|
22154
|
-
this._searchVisible = false;
|
|
22155
|
-
this._searchQuery = ``;
|
|
22156
22853
|
this.#customWrappers = /* @__PURE__ */ new Map();
|
|
22157
22854
|
this.#wrappedElements = /* @__PURE__ */ new Set();
|
|
22158
22855
|
this.#animatedItems = /* @__PURE__ */ new WeakSet();
|
|
@@ -22184,6 +22881,11 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22184
22881
|
onStateChange: () => this._syncItemStates(),
|
|
22185
22882
|
itemWrap: this.itemWrap
|
|
22186
22883
|
});
|
|
22884
|
+
this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
|
|
22885
|
+
itemSelector: ITEM_SELECTOR,
|
|
22886
|
+
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
22887
|
+
highlightKey: `vertical-list-search`
|
|
22888
|
+
}) });
|
|
22187
22889
|
this._onPointerOver = (e) => {
|
|
22188
22890
|
const item = this._findListItem(e.composedPath());
|
|
22189
22891
|
if (item) this.tickled.handlePointerEnter(item);
|
|
@@ -22192,11 +22894,7 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22192
22894
|
this.tickled.handlePointerLeave(e);
|
|
22193
22895
|
};
|
|
22194
22896
|
this._onKeyDown = (e) => {
|
|
22195
|
-
if (
|
|
22196
|
-
e.preventDefault();
|
|
22197
|
-
this._openSearch();
|
|
22198
|
-
return;
|
|
22199
|
-
}
|
|
22897
|
+
if (this.search.isOpen) return;
|
|
22200
22898
|
if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
|
|
22201
22899
|
if (this.selection.canSelectAll()) {
|
|
22202
22900
|
e.preventDefault();
|
|
@@ -22204,7 +22902,6 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22204
22902
|
}
|
|
22205
22903
|
return;
|
|
22206
22904
|
}
|
|
22207
|
-
if (this._searchVisible) return;
|
|
22208
22905
|
switch (e.key) {
|
|
22209
22906
|
case `ArrowDown`:
|
|
22210
22907
|
e.preventDefault();
|
|
@@ -22252,40 +22949,31 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22252
22949
|
const item = this._findListItem(e.composedPath());
|
|
22253
22950
|
if (item) this._activateItem(item);
|
|
22254
22951
|
};
|
|
22255
|
-
this._onSearchInput = (e) => {
|
|
22256
|
-
const input = e.target;
|
|
22257
|
-
this._searchQuery = input.value;
|
|
22258
|
-
this.#search?.setQuery(this._searchQuery);
|
|
22259
|
-
};
|
|
22260
|
-
this._onSearchKeydown = (e) => {
|
|
22261
|
-
if (e.key === `Escape`) {
|
|
22262
|
-
e.stopPropagation();
|
|
22263
|
-
this._closeSearch();
|
|
22264
|
-
} else if (e.key === `ArrowDown`) {
|
|
22265
|
-
e.preventDefault();
|
|
22266
|
-
this.tickled.handleKeyboardNavigation(`next`);
|
|
22267
|
-
} else if (e.key === `ArrowUp`) {
|
|
22268
|
-
e.preventDefault();
|
|
22269
|
-
this.tickled.handleKeyboardNavigation(`previous`);
|
|
22270
|
-
} else if (e.key === `Enter`) {
|
|
22271
|
-
e.preventDefault();
|
|
22272
|
-
e.stopPropagation();
|
|
22273
|
-
const id = this.tickled.tickledId ?? this._getVisibleItems()[0];
|
|
22274
|
-
if (id) {
|
|
22275
|
-
this._dispatch(`list-item-click`, { item: id });
|
|
22276
|
-
this._activateItem(id);
|
|
22277
|
-
this._closeSearch();
|
|
22278
|
-
}
|
|
22279
|
-
}
|
|
22280
|
-
};
|
|
22281
22952
|
}
|
|
22282
|
-
#search;
|
|
22283
22953
|
#customWrappers;
|
|
22284
22954
|
#wrappedElements;
|
|
22285
22955
|
/** Watches for items added to the light DOM so they can be animated in. */
|
|
22286
22956
|
#itemObserver;
|
|
22287
22957
|
/** Items that have already played (or been skipped for) their enter animation. */
|
|
22288
22958
|
#animatedItems;
|
|
22959
|
+
#searchRegistry;
|
|
22960
|
+
#unregisterSearchCommands;
|
|
22961
|
+
/** Command registry carrying this list's `list.search.*` commands. */
|
|
22962
|
+
get registry() {
|
|
22963
|
+
return this.#searchRegistry;
|
|
22964
|
+
}
|
|
22965
|
+
#effectiveSearchModes() {
|
|
22966
|
+
const raw = this.searchModes?.trim();
|
|
22967
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
22968
|
+
const requested = new Set(raw.split(/\s+/));
|
|
22969
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
22970
|
+
}
|
|
22971
|
+
#syncSearchCommands() {
|
|
22972
|
+
if (!this.#searchRegistry) return;
|
|
22973
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
22974
|
+
this.#unregisterSearchCommands?.();
|
|
22975
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
22976
|
+
}
|
|
22289
22977
|
connectedCallback() {
|
|
22290
22978
|
super.connectedCallback();
|
|
22291
22979
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
@@ -22297,6 +22985,8 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22297
22985
|
for (const el of this.querySelectorAll(ITEM_SELECTOR)) this.#animatedItems.add(el);
|
|
22298
22986
|
this.#itemObserver ??= new MutationObserver((records) => this.#onItemsMutated(records));
|
|
22299
22987
|
this.#itemObserver.observe(this, { childList: true });
|
|
22988
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
22989
|
+
this.#syncSearchCommands();
|
|
22300
22990
|
}
|
|
22301
22991
|
disconnectedCallback() {
|
|
22302
22992
|
super.disconnectedCallback();
|
|
@@ -22306,8 +22996,9 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22306
22996
|
this.removeEventListener(`click`, this._onClick);
|
|
22307
22997
|
this.removeEventListener(`dblclick`, this._onDblClick);
|
|
22308
22998
|
this.#itemObserver?.disconnect();
|
|
22309
|
-
this.#
|
|
22310
|
-
this.#
|
|
22999
|
+
this.#unregisterSearchCommands?.();
|
|
23000
|
+
this.#unregisterSearchCommands = void 0;
|
|
23001
|
+
this.#searchRegistry = void 0;
|
|
22311
23002
|
}
|
|
22312
23003
|
/**
|
|
22313
23004
|
* Animate in items appended to the light DOM (however they got there —
|
|
@@ -22330,17 +23021,9 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22330
23021
|
if (fresh.length >= total) return;
|
|
22331
23022
|
for (const el of fresh) animateListItemIn(el);
|
|
22332
23023
|
}
|
|
22333
|
-
firstUpdated() {
|
|
22334
|
-
this.#search = createElementIncrSearch({
|
|
22335
|
-
container: this,
|
|
22336
|
-
itemSelector: ITEM_SELECTOR,
|
|
22337
|
-
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
22338
|
-
getKey: (el) => el.getAttribute(`data-key`) ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
22339
|
-
highlightKey: `vertical-list-search`
|
|
22340
|
-
});
|
|
22341
|
-
}
|
|
22342
23024
|
updated(changed) {
|
|
22343
23025
|
super.updated(changed);
|
|
23026
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
22344
23027
|
if (changed.has(`interactionMode`)) {
|
|
22345
23028
|
this.selection.clearAnchor();
|
|
22346
23029
|
const previousSelected = this.selection.selected;
|
|
@@ -22408,11 +23091,13 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22408
23091
|
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
22409
23092
|
}
|
|
22410
23093
|
/**
|
|
22411
|
-
* Dispatch a command to this list
|
|
22412
|
-
*
|
|
22413
|
-
*
|
|
23094
|
+
* Dispatch a command to this list — used by `ixfx-grouped-item-lister` to
|
|
23095
|
+
* forward commands to all sub-components. Routes through the command registry
|
|
23096
|
+
* (so `list.search.*` and any other registered commands work).
|
|
22414
23097
|
*/
|
|
22415
|
-
dispatch(
|
|
23098
|
+
dispatch(commandId, args) {
|
|
23099
|
+
this.#searchRegistry?.invoke(commandId, args);
|
|
23100
|
+
}
|
|
22416
23101
|
select(item) {
|
|
22417
23102
|
this.selection.replace(item);
|
|
22418
23103
|
}
|
|
@@ -22622,22 +23307,15 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22622
23307
|
composed: true
|
|
22623
23308
|
}));
|
|
22624
23309
|
}
|
|
22625
|
-
_openSearch() {
|
|
22626
|
-
this._searchVisible = true;
|
|
22627
|
-
this.updateComplete.then(() => this._searchInputEl?.focus());
|
|
22628
|
-
}
|
|
22629
|
-
_closeSearch() {
|
|
22630
|
-
this._searchVisible = false;
|
|
22631
|
-
this._searchQuery = ``;
|
|
22632
|
-
this.#search?.clearQuery();
|
|
22633
|
-
this.focus();
|
|
22634
|
-
}
|
|
22635
23310
|
_activateItem(item) {
|
|
22636
23311
|
if (this.interactionMode !== `manual`) this.selection.replace(item);
|
|
22637
23312
|
this._dispatch(`list-activate`, { item });
|
|
22638
23313
|
}
|
|
22639
23314
|
static {
|
|
22640
|
-
this.styles = [
|
|
23315
|
+
this.styles = [
|
|
23316
|
+
themeFallbacks,
|
|
23317
|
+
listSearchStyles,
|
|
23318
|
+
i$3`
|
|
22641
23319
|
:host {
|
|
22642
23320
|
display: block;
|
|
22643
23321
|
position: relative;
|
|
@@ -22688,6 +23366,10 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22688
23366
|
display: flex;
|
|
22689
23367
|
align-items: center;
|
|
22690
23368
|
padding: var(--ixfx-list-item-padding);
|
|
23369
|
+
/* Inset each row from the scroll container's edges (margin, not padding:
|
|
23370
|
+
padding is inside the border box, so the tickle/selection outlines
|
|
23371
|
+
would still sit flush against the edge and be clipped). */
|
|
23372
|
+
margin: 0 var(--list-gutter, 4px);
|
|
22691
23373
|
min-height: var(--list-item-height, 28px);
|
|
22692
23374
|
cursor: pointer;
|
|
22693
23375
|
user-select: none;
|
|
@@ -22700,6 +23382,7 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22700
23382
|
/* Custom shadow-DOM components handle their own internal layout/padding */
|
|
22701
23383
|
::slotted([data-list-item]) {
|
|
22702
23384
|
display: block;
|
|
23385
|
+
margin: 0 var(--list-gutter, 4px);
|
|
22703
23386
|
cursor: pointer;
|
|
22704
23387
|
user-select: none;
|
|
22705
23388
|
transition: background var(--transition, 0.1s ease-out),
|
|
@@ -22755,49 +23438,8 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22755
23438
|
color: inherit;
|
|
22756
23439
|
outline: var(--item-border-tickled);
|
|
22757
23440
|
}
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
position: absolute;
|
|
22761
|
-
top: 8px;
|
|
22762
|
-
right: 8px;
|
|
22763
|
-
z-index: 10;
|
|
22764
|
-
display: flex;
|
|
22765
|
-
align-items: center;
|
|
22766
|
-
gap: 4px;
|
|
22767
|
-
background: var(--surface-4, #2a2a2a);
|
|
22768
|
-
border: 1px solid var(--border, #444);
|
|
22769
|
-
border-radius: var(--radius-m, 4px);
|
|
22770
|
-
padding: 4px 6px;
|
|
22771
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
22772
|
-
}
|
|
22773
|
-
|
|
22774
|
-
.search-overlay[hidden] {
|
|
22775
|
-
display: none;
|
|
22776
|
-
}
|
|
22777
|
-
|
|
22778
|
-
.search-input {
|
|
22779
|
-
background: transparent;
|
|
22780
|
-
border: none;
|
|
22781
|
-
outline: none;
|
|
22782
|
-
color: inherit;
|
|
22783
|
-
font-size: var(--text-m, 0.875em);
|
|
22784
|
-
min-width: 140px;
|
|
22785
|
-
}
|
|
22786
|
-
|
|
22787
|
-
.search-close {
|
|
22788
|
-
background: transparent;
|
|
22789
|
-
border: none;
|
|
22790
|
-
color: var(--surface-muted-text, #888);
|
|
22791
|
-
cursor: pointer;
|
|
22792
|
-
font-size: 1.1em;
|
|
22793
|
-
line-height: 1;
|
|
22794
|
-
padding: 0 2px;
|
|
22795
|
-
}
|
|
22796
|
-
|
|
22797
|
-
.search-close:hover {
|
|
22798
|
-
color: inherit;
|
|
22799
|
-
}
|
|
22800
|
-
`];
|
|
23441
|
+
`
|
|
23442
|
+
];
|
|
22801
23443
|
}
|
|
22802
23444
|
render() {
|
|
22803
23445
|
return b`
|
|
@@ -22805,21 +23447,7 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22805
23447
|
<ul part="list">
|
|
22806
23448
|
<slot></slot>
|
|
22807
23449
|
</ul>
|
|
22808
|
-
|
|
22809
|
-
class="search-overlay"
|
|
22810
|
-
part="search-overlay"
|
|
22811
|
-
?hidden=${!this._searchVisible}
|
|
22812
|
-
>
|
|
22813
|
-
<input
|
|
22814
|
-
type="text"
|
|
22815
|
-
class="search-input"
|
|
22816
|
-
placeholder="Search…"
|
|
22817
|
-
.value=${this._searchQuery}
|
|
22818
|
-
@input=${this._onSearchInput}
|
|
22819
|
-
@keydown=${this._onSearchKeydown}
|
|
22820
|
-
/>
|
|
22821
|
-
<button class="search-close" @click=${this._closeSearch} title="Close search (Esc)">×</button>
|
|
22822
|
-
</div>
|
|
23450
|
+
${this.search.renderField()}
|
|
22823
23451
|
</div>
|
|
22824
23452
|
`;
|
|
22825
23453
|
}
|
|
@@ -22838,9 +23466,11 @@ __decorate([property({
|
|
|
22838
23466
|
attribute: `item-wrap`
|
|
22839
23467
|
})], VerticalListElement.prototype, "itemWrap", void 0);
|
|
22840
23468
|
__decorate([property({ attribute: false })], VerticalListElement.prototype, "checkboxRenderer", void 0);
|
|
22841
|
-
__decorate([
|
|
22842
|
-
|
|
22843
|
-
|
|
23469
|
+
__decorate([property({
|
|
23470
|
+
type: String,
|
|
23471
|
+
attribute: `search-modes`,
|
|
23472
|
+
reflect: true
|
|
23473
|
+
})], VerticalListElement.prototype, "searchModes", void 0);
|
|
22844
23474
|
VerticalListElement = __decorate([safeCustomElement(`ixfx-vertical-list`)], VerticalListElement);
|
|
22845
23475
|
//#endregion
|
|
22846
23476
|
//#region src/grouped-item-lister/vertical-list-adapter.ts
|
|
@@ -27338,6 +27968,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27338
27968
|
constructor(..._args) {
|
|
27339
27969
|
super(..._args);
|
|
27340
27970
|
this.headers = false;
|
|
27971
|
+
this.stretchLastColumn = false;
|
|
27341
27972
|
this.interactionMode = `standard`;
|
|
27342
27973
|
this.selectionFilter = `leaf`;
|
|
27343
27974
|
this.exclusivity = `none`;
|
|
@@ -27349,6 +27980,10 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27349
27980
|
this.#resizingDepth = -1;
|
|
27350
27981
|
this.#resizeStartX = 0;
|
|
27351
27982
|
this.#resizeStartWidth = 0;
|
|
27983
|
+
this.search = new ListSearchController(this, { adapter: createTreeListSearchAdapter(this, {
|
|
27984
|
+
labelSelector: `.label`,
|
|
27985
|
+
highlightKey: `incr-search-highlight`
|
|
27986
|
+
}) });
|
|
27352
27987
|
this.tickled = new TickledController(this, { getItems: () => {
|
|
27353
27988
|
const items = [];
|
|
27354
27989
|
for (let depth = 0; depth < this._columns.length; depth++) {
|
|
@@ -27358,6 +27993,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27358
27993
|
return items;
|
|
27359
27994
|
} });
|
|
27360
27995
|
this.#onKeyDown = (event) => {
|
|
27996
|
+
if (this.search.isOpen) return;
|
|
27361
27997
|
const currentDepth = this.#getTickledDepthOrDefault();
|
|
27362
27998
|
const col = this._columns[currentDepth];
|
|
27363
27999
|
if (!col || col.length === 0) {
|
|
@@ -27564,9 +28200,31 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27564
28200
|
#resizeStartX;
|
|
27565
28201
|
#resizeStartWidth;
|
|
27566
28202
|
#modelUnsubscribe;
|
|
28203
|
+
#searchRegistry;
|
|
28204
|
+
#unregisterSearchCommands;
|
|
28205
|
+
/** Command registry carrying this component's `list.search.*` commands. */
|
|
28206
|
+
get registry() {
|
|
28207
|
+
return this.#searchRegistry;
|
|
28208
|
+
}
|
|
28209
|
+
#effectiveSearchModes() {
|
|
28210
|
+
const raw = this.searchModes?.trim();
|
|
28211
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
28212
|
+
const requested = new Set(raw.split(/\s+/));
|
|
28213
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
28214
|
+
}
|
|
28215
|
+
#syncSearchCommands() {
|
|
28216
|
+
if (!this.#searchRegistry) return;
|
|
28217
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
28218
|
+
this.#unregisterSearchCommands?.();
|
|
28219
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
28220
|
+
}
|
|
27567
28221
|
#buildTickledId(node, depth) {
|
|
27568
28222
|
return `${depth}:${node.item.key}`;
|
|
27569
28223
|
}
|
|
28224
|
+
/** The node column 0 renders the children of: `viewRoot` when rebased, else `root`. */
|
|
28225
|
+
get #displayRoot() {
|
|
28226
|
+
return this.viewRoot ?? this.root;
|
|
28227
|
+
}
|
|
27570
28228
|
#getTickledDepth() {
|
|
27571
28229
|
const id = this.tickled.tickledId;
|
|
27572
28230
|
if (!id) return void 0;
|
|
@@ -27575,16 +28233,20 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27575
28233
|
return Number(id.substring(0, colonIndex));
|
|
27576
28234
|
}
|
|
27577
28235
|
updated(changedProps) {
|
|
28236
|
+
if (changedProps.has(`searchModes`)) this.#syncSearchCommands();
|
|
27578
28237
|
if (changedProps.has(`root`) && this.root) {
|
|
27579
28238
|
this.#abortPending();
|
|
27580
|
-
this.
|
|
28239
|
+
if (this.viewRoot !== void 0) this.viewRoot = void 0;
|
|
27581
28240
|
this._selectedByDepth.clear();
|
|
27582
28241
|
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
27583
28242
|
this._selectedNodes.clear();
|
|
27584
28243
|
this.#lastSelectedNode = void 0;
|
|
27585
28244
|
this.#anchor = void 0;
|
|
27586
28245
|
this.tickled.clearTickled();
|
|
28246
|
+
const display = this.#displayRoot;
|
|
28247
|
+
this._columns = display?.children ? [display.children] : [];
|
|
27587
28248
|
}
|
|
28249
|
+
if (changedProps.has(`viewRoot`)) this.#applyViewRoot();
|
|
27588
28250
|
if (changedProps.has(`model`)) {
|
|
27589
28251
|
this.#modelUnsubscribe?.();
|
|
27590
28252
|
this.#modelUnsubscribe = void 0;
|
|
@@ -27626,10 +28288,18 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27626
28288
|
});
|
|
27627
28289
|
}
|
|
27628
28290
|
}
|
|
28291
|
+
connectedCallback() {
|
|
28292
|
+
super.connectedCallback();
|
|
28293
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
28294
|
+
this.#syncSearchCommands();
|
|
28295
|
+
}
|
|
27629
28296
|
disconnectedCallback() {
|
|
27630
28297
|
super.disconnectedCallback();
|
|
27631
28298
|
this.#modelUnsubscribe?.();
|
|
27632
28299
|
this.#modelUnsubscribe = void 0;
|
|
28300
|
+
this.#unregisterSearchCommands?.();
|
|
28301
|
+
this.#unregisterSearchCommands = void 0;
|
|
28302
|
+
this.#searchRegistry = void 0;
|
|
27633
28303
|
}
|
|
27634
28304
|
getConfig() {
|
|
27635
28305
|
const columnWidths = {};
|
|
@@ -27725,15 +28395,17 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27725
28395
|
return results;
|
|
27726
28396
|
}
|
|
27727
28397
|
/**
|
|
27728
|
-
* Programmatically navigate to a node by key path
|
|
28398
|
+
* Programmatically navigate to a node by key path, relative to the current
|
|
28399
|
+
* view root (rebased or full tree).
|
|
27729
28400
|
*/
|
|
27730
28401
|
navigateTo(keys) {
|
|
27731
|
-
|
|
28402
|
+
const display = this.#displayRoot;
|
|
28403
|
+
if (!display?.children) return false;
|
|
27732
28404
|
this.#abortPending();
|
|
27733
28405
|
this._selectedByDepth.clear();
|
|
27734
28406
|
this.tickled.clearTickled();
|
|
27735
|
-
const newColumns = [
|
|
27736
|
-
let currentColumn =
|
|
28407
|
+
const newColumns = [display.children];
|
|
28408
|
+
let currentColumn = display.children;
|
|
27737
28409
|
let success = true;
|
|
27738
28410
|
let lastNode;
|
|
27739
28411
|
let lastDepth = 0;
|
|
@@ -27768,13 +28440,51 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27768
28440
|
* Programmatically navigate to the first node (breadth-first) matching a predicate.
|
|
27769
28441
|
*/
|
|
27770
28442
|
navigateToNode(predicate) {
|
|
27771
|
-
|
|
27772
|
-
|
|
28443
|
+
const display = this.#displayRoot;
|
|
28444
|
+
if (!display?.children) return false;
|
|
28445
|
+
const keyPath = this.#findNodePath(display.children, predicate);
|
|
27773
28446
|
if (!keyPath) return false;
|
|
27774
28447
|
return this.navigateTo(keyPath);
|
|
27775
28448
|
}
|
|
28449
|
+
/** Open the columns down to `key` and move the cursor to it, without changing the selection (search "navigate" mode). */
|
|
28450
|
+
revealKey(key) {
|
|
28451
|
+
const display = this.#displayRoot;
|
|
28452
|
+
if (!display?.children) return false;
|
|
28453
|
+
const path = this.#findNodePath(display.children, (n) => n.item.key === key);
|
|
28454
|
+
if (!path) return false;
|
|
28455
|
+
const columns = [display.children];
|
|
28456
|
+
let current = display.children;
|
|
28457
|
+
let lastNode;
|
|
28458
|
+
let lastDepth = 0;
|
|
28459
|
+
for (let depth = 0; depth < path.length; depth++) {
|
|
28460
|
+
const node = current.find((n) => n.item.key === path[depth]);
|
|
28461
|
+
if (!node) return false;
|
|
28462
|
+
lastNode = node;
|
|
28463
|
+
lastDepth = depth;
|
|
28464
|
+
if (node.children && node.children.length > 0) {
|
|
28465
|
+
columns.push(node.children);
|
|
28466
|
+
current = node.children;
|
|
28467
|
+
} else break;
|
|
28468
|
+
}
|
|
28469
|
+
this._columns = columns;
|
|
28470
|
+
if (lastNode) this.tickled.setTickled(this.#buildTickledId(lastNode, lastDepth));
|
|
28471
|
+
this.requestUpdate();
|
|
28472
|
+
this.#scrollNavigatedItemIntoView();
|
|
28473
|
+
return true;
|
|
28474
|
+
}
|
|
28475
|
+
/** Replace the selection with `nodes` (search "select" mode). */
|
|
28476
|
+
selectMany(nodes) {
|
|
28477
|
+
const arr = this.selectionFilter === `none` ? [] : [...nodes];
|
|
28478
|
+
const prev = new Set(this._selectedNodes);
|
|
28479
|
+
this._selectedNodes = new Set(arr);
|
|
28480
|
+
this._selectedKeys = new Set(arr.map((n) => n.item.key));
|
|
28481
|
+
this.#lastSelectedNode = arr.at(-1);
|
|
28482
|
+
this.#dispatchSelectEvent(prev);
|
|
28483
|
+
this.requestUpdate();
|
|
28484
|
+
}
|
|
27776
28485
|
getNodes() {
|
|
27777
|
-
|
|
28486
|
+
const display = this.#displayRoot;
|
|
28487
|
+
if (!display?.children) return [];
|
|
27778
28488
|
const result = [];
|
|
27779
28489
|
const visit = (node, depth) => {
|
|
27780
28490
|
result.push({
|
|
@@ -27783,7 +28493,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27783
28493
|
});
|
|
27784
28494
|
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
27785
28495
|
};
|
|
27786
|
-
for (const child of
|
|
28496
|
+
for (const child of display.children) visit(child, 0);
|
|
27787
28497
|
return result;
|
|
27788
28498
|
}
|
|
27789
28499
|
#findNodePath(nodes, predicate, prefix = []) {
|
|
@@ -27796,6 +28506,124 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27796
28506
|
}
|
|
27797
28507
|
}
|
|
27798
28508
|
}
|
|
28509
|
+
/**
|
|
28510
|
+
* Re-render the columns for the current `#displayRoot`, clearing selection.
|
|
28511
|
+
* Runs on every `viewRoot` change.
|
|
28512
|
+
*/
|
|
28513
|
+
#applyViewRoot() {
|
|
28514
|
+
this.#abortPending();
|
|
28515
|
+
const prev = new Set(this._selectedNodes);
|
|
28516
|
+
this._selectedByDepth.clear();
|
|
28517
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
28518
|
+
this._selectedNodes.clear();
|
|
28519
|
+
this.#lastSelectedNode = void 0;
|
|
28520
|
+
this.#anchor = void 0;
|
|
28521
|
+
this.tickled.clearTickled();
|
|
28522
|
+
const display = this.#displayRoot;
|
|
28523
|
+
if (display?.children !== void 0) {
|
|
28524
|
+
this._columns = display.children ? [display.children] : [];
|
|
28525
|
+
this._loadingDepth = void 0;
|
|
28526
|
+
} else if (display) {
|
|
28527
|
+
this._columns = [];
|
|
28528
|
+
this.#loadUnloadedChildren(display, -1, false);
|
|
28529
|
+
} else {
|
|
28530
|
+
this._columns = [];
|
|
28531
|
+
this._loadingDepth = void 0;
|
|
28532
|
+
}
|
|
28533
|
+
if (prev.size > 0) this.#dispatchSelectEvent(prev);
|
|
28534
|
+
}
|
|
28535
|
+
/**
|
|
28536
|
+
* Rebase the view to show `target`'s children in column 0 (Finder column-view
|
|
28537
|
+
* behavior on double-click), keeping the full tree in `root`.
|
|
28538
|
+
*
|
|
28539
|
+
* - Pass a `MillerNode` (used as-is) or a key (looked up in the full tree).
|
|
28540
|
+
* - Pass `undefined` to restore the full-tree view.
|
|
28541
|
+
* - Rebasing to the true root is equivalent to restoring the full view.
|
|
28542
|
+
* - Leaves are refused (returns `false`).
|
|
28543
|
+
*
|
|
28544
|
+
* Selection is cleared; all depths, headers, and key paths are relative to
|
|
28545
|
+
* the new view root. Fires a `rebase` event when the view actually changes.
|
|
28546
|
+
* Returns `false` when nothing changed (unknown key, leaf, or same view).
|
|
28547
|
+
*/
|
|
28548
|
+
rebaseTo(target) {
|
|
28549
|
+
if (target === void 0) return this.#clearViewRoot();
|
|
28550
|
+
const node = typeof target === `string` ? this.#findNodeByKey(target) : target;
|
|
28551
|
+
if (!node || node.item.isLeaf === true) return false;
|
|
28552
|
+
if (node === this.viewRoot || node.item.key === this.viewRoot?.item.key) return false;
|
|
28553
|
+
if (node === this.root || !this.viewRoot && node.item.key === this.root?.item.key) return this.#clearViewRoot();
|
|
28554
|
+
this.viewRoot = node;
|
|
28555
|
+
this.#dispatchRebaseEvent();
|
|
28556
|
+
return true;
|
|
28557
|
+
}
|
|
28558
|
+
/**
|
|
28559
|
+
* Rebase one level up from the current view root (no-op at the full tree).
|
|
28560
|
+
* Fires a `rebase` event when the view actually changes.
|
|
28561
|
+
*/
|
|
28562
|
+
rebaseUp() {
|
|
28563
|
+
if (!this.viewRoot) return false;
|
|
28564
|
+
const parent = this.#findParentOf(this.viewRoot);
|
|
28565
|
+
if (!parent) return false;
|
|
28566
|
+
if (parent === this.root) return this.#clearViewRoot();
|
|
28567
|
+
this.viewRoot = parent;
|
|
28568
|
+
this.#dispatchRebaseEvent();
|
|
28569
|
+
return true;
|
|
28570
|
+
}
|
|
28571
|
+
#clearViewRoot() {
|
|
28572
|
+
if (this.viewRoot === void 0) return false;
|
|
28573
|
+
this.viewRoot = void 0;
|
|
28574
|
+
this.#dispatchRebaseEvent();
|
|
28575
|
+
return true;
|
|
28576
|
+
}
|
|
28577
|
+
#dispatchRebaseEvent() {
|
|
28578
|
+
const viewRoot = this.viewRoot;
|
|
28579
|
+
const detail = {
|
|
28580
|
+
viewRoot,
|
|
28581
|
+
path: viewRoot ? this.#pathToRoot(viewRoot) : []
|
|
28582
|
+
};
|
|
28583
|
+
this.dispatchEvent(new CustomEvent(`rebase`, {
|
|
28584
|
+
detail,
|
|
28585
|
+
bubbles: true,
|
|
28586
|
+
composed: true
|
|
28587
|
+
}));
|
|
28588
|
+
}
|
|
28589
|
+
#findNodeByKey(key) {
|
|
28590
|
+
if (!this.root) return void 0;
|
|
28591
|
+
if (this.root.item.key === key) return this.root;
|
|
28592
|
+
return this.#findNodeIn(this.root.children ?? [], key);
|
|
28593
|
+
}
|
|
28594
|
+
#findNodeIn(nodes, key) {
|
|
28595
|
+
for (const node of nodes) {
|
|
28596
|
+
if (node.item.key === key) return node;
|
|
28597
|
+
if (node.children) {
|
|
28598
|
+
const found = this.#findNodeIn(node.children, key);
|
|
28599
|
+
if (found) return found;
|
|
28600
|
+
}
|
|
28601
|
+
}
|
|
28602
|
+
}
|
|
28603
|
+
/** Find the parent of `target` (by reference, falling back to key) in the full tree. */
|
|
28604
|
+
#findParentOf(target) {
|
|
28605
|
+
if (!this.root || target === this.root) return void 0;
|
|
28606
|
+
return this.#findParentIn(this.root, target);
|
|
28607
|
+
}
|
|
28608
|
+
#findParentIn(node, target) {
|
|
28609
|
+
if (!node.children) return void 0;
|
|
28610
|
+
for (const child of node.children) {
|
|
28611
|
+
if (child === target || child.item.key === target.item.key) return node;
|
|
28612
|
+
const found = this.#findParentIn(child, target);
|
|
28613
|
+
if (found) return found;
|
|
28614
|
+
}
|
|
28615
|
+
}
|
|
28616
|
+
/** Ancestry chain from the true root down to and including `target`. */
|
|
28617
|
+
#pathToRoot(target) {
|
|
28618
|
+
const chain = [target];
|
|
28619
|
+
let current = this.#findParentOf(target);
|
|
28620
|
+
while (current) {
|
|
28621
|
+
chain.unshift(current);
|
|
28622
|
+
if (current === this.root) break;
|
|
28623
|
+
current = this.#findParentOf(current);
|
|
28624
|
+
}
|
|
28625
|
+
return chain;
|
|
28626
|
+
}
|
|
27799
28627
|
renderColumnContent(nodes, depth) {
|
|
27800
28628
|
console.warn(`ixfx-miller-base: renderColumnContent not overridden. depth=${depth}, nodes=${nodes.length}`);
|
|
27801
28629
|
return A;
|
|
@@ -27922,7 +28750,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27922
28750
|
*/
|
|
27923
28751
|
#buildPath(depth) {
|
|
27924
28752
|
const path = [];
|
|
27925
|
-
if (this
|
|
28753
|
+
if (this.#displayRoot) path.push(this.#displayRoot);
|
|
27926
28754
|
for (let d = 0; d < depth; d++) {
|
|
27927
28755
|
const sel = this._selectedByDepth.get(d);
|
|
27928
28756
|
if (sel) path.push(sel);
|
|
@@ -28025,57 +28853,8 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28025
28853
|
this.requestUpdate();
|
|
28026
28854
|
return;
|
|
28027
28855
|
}
|
|
28028
|
-
if (node.children === void 0)
|
|
28029
|
-
|
|
28030
|
-
this._loadingDepth = depth + 1;
|
|
28031
|
-
this.#pendingNode = node;
|
|
28032
|
-
const controller = new AbortController();
|
|
28033
|
-
this.#pendingController = controller;
|
|
28034
|
-
this.dispatchEvent(new CustomEvent(`expand`, {
|
|
28035
|
-
detail: {
|
|
28036
|
-
node,
|
|
28037
|
-
depth
|
|
28038
|
-
},
|
|
28039
|
-
bubbles: true,
|
|
28040
|
-
composed: true
|
|
28041
|
-
}));
|
|
28042
|
-
const applyChildren = (children) => {
|
|
28043
|
-
if (controller.signal.aborted) return;
|
|
28044
|
-
node.children = children;
|
|
28045
|
-
this.#pendingNode = void 0;
|
|
28046
|
-
this.#pendingController = void 0;
|
|
28047
|
-
this._loadingDepth = void 0;
|
|
28048
|
-
const cols = this._columns.slice(0, depth + 1);
|
|
28049
|
-
if (children.length > 0) cols.push(children);
|
|
28050
|
-
this._columns = cols;
|
|
28051
|
-
};
|
|
28052
|
-
const handleError = (err) => {
|
|
28053
|
-
if (err.name !== `AbortError`) console.error(err);
|
|
28054
|
-
node.children = [];
|
|
28055
|
-
this.#pendingNode = void 0;
|
|
28056
|
-
this.#pendingController = void 0;
|
|
28057
|
-
this._loadingDepth = void 0;
|
|
28058
|
-
this.requestUpdate();
|
|
28059
|
-
};
|
|
28060
|
-
if (this.model || this.loadChildren) {
|
|
28061
|
-
if (this.#expandTimer !== void 0) clearTimeout(this.#expandTimer);
|
|
28062
|
-
this.#expandTimer = setTimeout(() => {
|
|
28063
|
-
this.#expandTimer = void 0;
|
|
28064
|
-
if (this.#pendingNode !== node) return;
|
|
28065
|
-
(this.model ? this.model.getChildren(node, depth, controller.signal) : collectDataResult(this.loadChildren({
|
|
28066
|
-
node,
|
|
28067
|
-
depth
|
|
28068
|
-
}, controller.signal), controller.signal)).then((children) => applyChildren([...children])).catch(handleError);
|
|
28069
|
-
}, EXPAND_DEBOUNCE_MS);
|
|
28070
|
-
} else {
|
|
28071
|
-
this._loadingDepth = void 0;
|
|
28072
|
-
this.#pendingNode = void 0;
|
|
28073
|
-
this.#pendingController = void 0;
|
|
28074
|
-
const newColumns = this._columns.slice(0, depth + 1);
|
|
28075
|
-
newColumns.push([]);
|
|
28076
|
-
this._columns = newColumns;
|
|
28077
|
-
}
|
|
28078
|
-
} else {
|
|
28856
|
+
if (node.children === void 0) this.#loadUnloadedChildren(node, depth, true);
|
|
28857
|
+
else {
|
|
28079
28858
|
const newColumns = this._columns.slice(0, depth + 1);
|
|
28080
28859
|
newColumns.push(node.children);
|
|
28081
28860
|
this._columns = newColumns;
|
|
@@ -28089,6 +28868,68 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28089
28868
|
}));
|
|
28090
28869
|
}
|
|
28091
28870
|
}
|
|
28871
|
+
/**
|
|
28872
|
+
* Fetch children for a node whose `children === undefined` and show them as
|
|
28873
|
+
* the column after `parentDepth` (with the loading placeholder meanwhile).
|
|
28874
|
+
* Shared by branch expansion (`parentDepth` = the branch's column) and view
|
|
28875
|
+
* rebasing (`parentDepth` = -1, so the children become column 0).
|
|
28876
|
+
*
|
|
28877
|
+
* The `depth` reported to `loadChildren`/`model.getChildren` is the node's
|
|
28878
|
+
* own depth: its column for expansions, -1 for a view root (it sits above
|
|
28879
|
+
* column 0, so loaders computing child depth as `depth + 1` stay correct).
|
|
28880
|
+
*/
|
|
28881
|
+
#loadUnloadedChildren(node, parentDepth, fireExpandEvent) {
|
|
28882
|
+
const nodeDepth = parentDepth + 1;
|
|
28883
|
+
this._columns = this._columns.slice(0, nodeDepth);
|
|
28884
|
+
this._loadingDepth = nodeDepth;
|
|
28885
|
+
this.#pendingNode = node;
|
|
28886
|
+
const controller = new AbortController();
|
|
28887
|
+
this.#pendingController = controller;
|
|
28888
|
+
if (fireExpandEvent) this.dispatchEvent(new CustomEvent(`expand`, {
|
|
28889
|
+
detail: {
|
|
28890
|
+
node,
|
|
28891
|
+
depth: parentDepth
|
|
28892
|
+
},
|
|
28893
|
+
bubbles: true,
|
|
28894
|
+
composed: true
|
|
28895
|
+
}));
|
|
28896
|
+
const applyChildren = (children) => {
|
|
28897
|
+
if (controller.signal.aborted) return;
|
|
28898
|
+
node.children = children;
|
|
28899
|
+
this.#pendingNode = void 0;
|
|
28900
|
+
this.#pendingController = void 0;
|
|
28901
|
+
this._loadingDepth = void 0;
|
|
28902
|
+
const cols = this._columns.slice(0, nodeDepth);
|
|
28903
|
+
if (children.length > 0) cols.push(children);
|
|
28904
|
+
this._columns = cols;
|
|
28905
|
+
};
|
|
28906
|
+
const handleError = (err) => {
|
|
28907
|
+
if (err.name !== `AbortError`) console.error(err);
|
|
28908
|
+
node.children = [];
|
|
28909
|
+
this.#pendingNode = void 0;
|
|
28910
|
+
this.#pendingController = void 0;
|
|
28911
|
+
this._loadingDepth = void 0;
|
|
28912
|
+
this.requestUpdate();
|
|
28913
|
+
};
|
|
28914
|
+
if (this.model || this.loadChildren) {
|
|
28915
|
+
if (this.#expandTimer !== void 0) clearTimeout(this.#expandTimer);
|
|
28916
|
+
this.#expandTimer = setTimeout(() => {
|
|
28917
|
+
this.#expandTimer = void 0;
|
|
28918
|
+
if (this.#pendingNode !== node) return;
|
|
28919
|
+
(this.model ? this.model.getChildren(node, nodeDepth - 1, controller.signal) : collectDataResult(this.loadChildren({
|
|
28920
|
+
node,
|
|
28921
|
+
depth: nodeDepth - 1
|
|
28922
|
+
}, controller.signal), controller.signal)).then((children) => applyChildren([...children])).catch(handleError);
|
|
28923
|
+
}, EXPAND_DEBOUNCE_MS);
|
|
28924
|
+
} else {
|
|
28925
|
+
this._loadingDepth = void 0;
|
|
28926
|
+
this.#pendingNode = void 0;
|
|
28927
|
+
this.#pendingController = void 0;
|
|
28928
|
+
const newColumns = this._columns.slice(0, nodeDepth);
|
|
28929
|
+
newColumns.push([]);
|
|
28930
|
+
this._columns = newColumns;
|
|
28931
|
+
}
|
|
28932
|
+
}
|
|
28092
28933
|
#onCheckboxClick(node) {
|
|
28093
28934
|
const currentlySelected = this._selectedKeys.has(node.item.key);
|
|
28094
28935
|
if (!!node.children?.length ? this.#allDescendantsSelected(node) : currentlySelected) this.#applyCheckedToggle(node, false);
|
|
@@ -28158,7 +28999,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28158
28999
|
#onResizeMove;
|
|
28159
29000
|
#onResizeEnd;
|
|
28160
29001
|
#getColumnHeader(depth) {
|
|
28161
|
-
if (depth === 0) return this
|
|
29002
|
+
if (depth === 0) return this.#displayRoot?.item.label ?? ``;
|
|
28162
29003
|
const explicit = this._selectedByDepth.get(depth - 1);
|
|
28163
29004
|
if (explicit) return explicit.item.label;
|
|
28164
29005
|
const prevCol = this._columns[depth - 1];
|
|
@@ -28169,9 +29010,11 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28169
29010
|
}
|
|
28170
29011
|
return ``;
|
|
28171
29012
|
}
|
|
28172
|
-
#columnStyle(depth) {
|
|
29013
|
+
#columnStyle(depth, isLast) {
|
|
28173
29014
|
const w = this.#columnWidths.get(depth);
|
|
28174
|
-
|
|
29015
|
+
if (w !== void 0) return `width:${w}px;flex-grow:0;flex-shrink:0;flex-basis:auto;max-width:none`;
|
|
29016
|
+
if (this.stretchLastColumn && isLast) return `width:max-content;flex-grow:1;flex-shrink:0;flex-basis:auto;min-width:0`;
|
|
29017
|
+
return `width:max-content;flex-grow:0;flex-shrink:0;flex-basis:auto`;
|
|
28175
29018
|
}
|
|
28176
29019
|
#isFocused(node, depth) {
|
|
28177
29020
|
return this.tickled.isTickled(this.#buildTickledId(node, depth));
|
|
@@ -28184,8 +29027,9 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28184
29027
|
}
|
|
28185
29028
|
#renderLoadingColumn() {
|
|
28186
29029
|
const depth = this._loadingDepth;
|
|
29030
|
+
const stretched = this.stretchLastColumn && this.#columnWidths.get(depth) === void 0;
|
|
28187
29031
|
return b`
|
|
28188
|
-
<div class="column loading-column" style=${this.#columnStyle(depth)}>
|
|
29032
|
+
<div class="column loading-column ${stretched ? `stretch-last` : ``}" style=${this.#columnStyle(depth, true)}>
|
|
28189
29033
|
${this.headers ? b`
|
|
28190
29034
|
<div class="column-header">${this.#getColumnHeader(depth)}</div>
|
|
28191
29035
|
` : A}
|
|
@@ -28200,8 +29044,10 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28200
29044
|
<div id="container" @keydown=${this.#onKeyDown} tabindex="0">
|
|
28201
29045
|
${this._columns.map((nodes, depth) => {
|
|
28202
29046
|
const visibleNodes = this.filterPredicate ? nodes.filter((n) => this.filterPredicate(n, depth)) : nodes;
|
|
29047
|
+
const isLast = depth === this._columns.length - 1 && this._loadingDepth === void 0;
|
|
29048
|
+
const stretched = this.stretchLastColumn && isLast && this.#columnWidths.get(depth) === void 0;
|
|
28203
29049
|
return b`
|
|
28204
|
-
<div class="column" style=${this.#columnStyle(depth)}>
|
|
29050
|
+
<div class="column ${stretched ? `stretch-last` : ``}" style=${this.#columnStyle(depth, isLast)}>
|
|
28205
29051
|
${this.headers ? b`
|
|
28206
29052
|
<div class="column-header"
|
|
28207
29053
|
@click=${() => this.#onHeaderClick(depth)}
|
|
@@ -28220,6 +29066,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28220
29066
|
})}
|
|
28221
29067
|
${this._loadingDepth !== void 0 ? this.#renderLoadingColumn() : A}
|
|
28222
29068
|
</div>
|
|
29069
|
+
${this.search.renderField()}
|
|
28223
29070
|
`;
|
|
28224
29071
|
}
|
|
28225
29072
|
_getFocusedIndex(depth) {
|
|
@@ -28255,6 +29102,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28255
29102
|
this.styles = [
|
|
28256
29103
|
themeFallbacks,
|
|
28257
29104
|
tickledStyles,
|
|
29105
|
+
listSearchStyles,
|
|
28258
29106
|
i$3`
|
|
28259
29107
|
:host {
|
|
28260
29108
|
--column-bg: var(--surface-3);
|
|
@@ -28289,6 +29137,20 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28289
29137
|
background: var(--column-bg);
|
|
28290
29138
|
border-right: 1px solid var(--column-separator);
|
|
28291
29139
|
flex-shrink: 0;
|
|
29140
|
+
/* Content-sized columns never grow wider than 80% of the available
|
|
29141
|
+
width — pathological labels truncate with ellipsis instead of
|
|
29142
|
+
pushing every other column out of view */
|
|
29143
|
+
max-width: 80%;
|
|
29144
|
+
/* Smoothly animate the last column stretching to fill remaining
|
|
29145
|
+
space and shrinking back to content width when it stops being last */
|
|
29146
|
+
transition: flex-grow var(--transition, 0.2s ease-out);
|
|
29147
|
+
}
|
|
29148
|
+
|
|
29149
|
+
.column.stretch-last {
|
|
29150
|
+
min-width: 0;
|
|
29151
|
+
/* Stretching fills remaining space by definition, so it is exempt
|
|
29152
|
+
from the 80% auto-size cap */
|
|
29153
|
+
max-width: none;
|
|
28292
29154
|
}
|
|
28293
29155
|
|
|
28294
29156
|
.column-header {
|
|
@@ -28372,6 +29234,12 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28372
29234
|
__decorate([property({ type: Object })], MillerBaseElement.prototype, "root", void 0);
|
|
28373
29235
|
__decorate([property({ attribute: false })], MillerBaseElement.prototype, "model", void 0);
|
|
28374
29236
|
__decorate([property({ type: Boolean })], MillerBaseElement.prototype, "headers", void 0);
|
|
29237
|
+
__decorate([property({
|
|
29238
|
+
type: Boolean,
|
|
29239
|
+
attribute: `stretch-last-column`,
|
|
29240
|
+
reflect: true
|
|
29241
|
+
})], MillerBaseElement.prototype, "stretchLastColumn", void 0);
|
|
29242
|
+
__decorate([property({ attribute: false })], MillerBaseElement.prototype, "viewRoot", void 0);
|
|
28375
29243
|
__decorate([property({ type: String })], MillerBaseElement.prototype, "interactionMode", void 0);
|
|
28376
29244
|
__decorate([property({
|
|
28377
29245
|
reflect: true,
|
|
@@ -28383,6 +29251,11 @@ __decorate([property({
|
|
|
28383
29251
|
})], MillerBaseElement.prototype, "exclusivity", void 0);
|
|
28384
29252
|
__decorate([property({ type: Object })], MillerBaseElement.prototype, "loadChildren", void 0);
|
|
28385
29253
|
__decorate([property({ attribute: false })], MillerBaseElement.prototype, "filterPredicate", void 0);
|
|
29254
|
+
__decorate([property({
|
|
29255
|
+
type: String,
|
|
29256
|
+
attribute: `search-modes`,
|
|
29257
|
+
reflect: true
|
|
29258
|
+
})], MillerBaseElement.prototype, "searchModes", void 0);
|
|
28386
29259
|
__decorate([state()], MillerBaseElement.prototype, "_columns", void 0);
|
|
28387
29260
|
__decorate([state()], MillerBaseElement.prototype, "_loadingDepth", void 0);
|
|
28388
29261
|
__decorate([state()], MillerBaseElement.prototype, "_selectedNodes", void 0);
|
|
@@ -28620,14 +29493,28 @@ var MillerTreeController = class {
|
|
|
28620
29493
|
* Any ancestor `MillerNode`s with unloaded children are populated
|
|
28621
29494
|
* synchronously before calling `element.navigateTo()`.
|
|
28622
29495
|
*
|
|
29496
|
+
* Key paths are relative to the element's current view root: when the view
|
|
29497
|
+
* is rebased, only matches at or below the view root can be reached.
|
|
29498
|
+
*
|
|
28623
29499
|
* Returns `false` if no matching node is found.
|
|
28624
29500
|
*/
|
|
28625
29501
|
navigateToValue(predicate) {
|
|
28626
29502
|
const treePath = this.#findTreePath(this.#root, predicate);
|
|
28627
29503
|
if (!treePath) return false;
|
|
28628
|
-
|
|
29504
|
+
const base = this.#element.viewRoot ?? this.#element.root;
|
|
29505
|
+
if (!base) return false;
|
|
29506
|
+
let startIndex = 1;
|
|
29507
|
+
if (this.#element.viewRoot) {
|
|
29508
|
+
const mappedBase = this.#nodeToTree.get(base);
|
|
29509
|
+
if (!mappedBase) return false;
|
|
29510
|
+
const baseIndex = treePath.indexOf(mappedBase);
|
|
29511
|
+
if (baseIndex < 0) return false;
|
|
29512
|
+
startIndex = baseIndex + 1;
|
|
29513
|
+
if (base.children === void 0) base.children = this.#childrenOf(mappedBase);
|
|
29514
|
+
}
|
|
29515
|
+
let currentColumn = base.children ?? [];
|
|
28629
29516
|
const keys = [];
|
|
28630
|
-
for (let i =
|
|
29517
|
+
for (let i = startIndex; i < treePath.length; i++) {
|
|
28631
29518
|
const treeNode = treePath[i];
|
|
28632
29519
|
const key = this.#options.valueToItem(treeNode.getValue(), treeNode).key;
|
|
28633
29520
|
keys.push(key);
|
|
@@ -37455,29 +38342,24 @@ var TreeController = class {
|
|
|
37455
38342
|
navigateToValue(predicate) {
|
|
37456
38343
|
const treePath = this.#findTreePath(this.#root, predicate);
|
|
37457
38344
|
if (!treePath) return false;
|
|
38345
|
+
let currentColumn = this.#element.root?.children ?? [];
|
|
37458
38346
|
const keys = [];
|
|
37459
38347
|
for (let i = 1; i < treePath.length; i++) {
|
|
37460
38348
|
const treeNode = treePath[i];
|
|
37461
38349
|
const key = this.#options.valueToItem(treeNode.getValue(), treeNode).key;
|
|
37462
38350
|
keys.push(key);
|
|
37463
38351
|
if (i < treePath.length - 1) {
|
|
37464
|
-
const
|
|
37465
|
-
const treeNodeMap = this.#nodeToTree;
|
|
37466
|
-
const node = currentChildren.find((n) => n.item.key === key);
|
|
38352
|
+
const node = currentColumn.find((n) => n.item.key === key);
|
|
37467
38353
|
if (!node) return false;
|
|
37468
38354
|
if (node.children === void 0) {
|
|
37469
|
-
const mapped =
|
|
38355
|
+
const mapped = this.#nodeToTree.get(node);
|
|
37470
38356
|
if (mapped) node.children = this.#childrenOf(mapped);
|
|
37471
38357
|
}
|
|
38358
|
+
currentColumn = node.children ?? [];
|
|
37472
38359
|
}
|
|
37473
38360
|
}
|
|
37474
38361
|
return this.#element.navigateTo(keys);
|
|
37475
38362
|
}
|
|
37476
|
-
#findInChildren(children, keys) {
|
|
37477
|
-
if (keys.length === 0) return children;
|
|
37478
|
-
const key = keys[0];
|
|
37479
|
-
return children.find((n) => n.item.key === key)?.children ?? [];
|
|
37480
|
-
}
|
|
37481
38363
|
#childrenOf(treeNode) {
|
|
37482
38364
|
return Array.from(treeNode.children(), (child) => this.#toTreeNode(child));
|
|
37483
38365
|
}
|
|
@@ -38642,6 +39524,6 @@ function init() {
|
|
|
38642
39524
|
console.log(`Init`);
|
|
38643
39525
|
}
|
|
38644
39526
|
//#endregion
|
|
38645
|
-
export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createElementIncrSearch, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
|
|
39527
|
+
export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
|
|
38646
39528
|
|
|
38647
39529
|
//# sourceMappingURL=index.js.map
|