@ixfx/components 0.7.4 → 0.8.0
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 +787 -190
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +2770 -592
- 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-D-z7bx5-.d.ts} +26 -1
- package/dist/data-grid-D-z7bx5-.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-_ZJHYEBr.d.ts} +20 -12
- package/dist/detail-list-_ZJHYEBr.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-nRUQOcEL.js +1537 -0
- package/dist/grid-list-nRUQOcEL.js.map +1 -0
- package/dist/{grid-list-B_orPsot.d.ts → grid-list-u33vmKzb.d.ts} +128 -10
- package/dist/grid-list-u33vmKzb.d.ts.map +1 -0
- package/dist/grid-list.d.ts +1 -1
- package/dist/grid-list.js +1 -932
- package/dist/incr-search-C05WjbZh.js +999 -0
- package/dist/incr-search-C05WjbZh.js.map +1 -0
- package/dist/incr-search.d.ts +2 -2
- package/dist/incr-search.js +2 -3
- package/dist/index-h6gxtfHH.d.ts +397 -0
- package/dist/index-h6gxtfHH.d.ts.map +1 -0
- package/dist/index.d.ts +62 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +343 -17
- package/dist/index.js.map +1 -1
- package/dist/led.d.ts +33 -1
- package/dist/led.d.ts.map +1 -1
- package/dist/led.js +157 -13
- package/dist/led.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/panel.d.ts +3 -1
- package/dist/panel.d.ts.map +1 -1
- package/dist/panel.js +31 -1
- package/dist/panel.js.map +1 -1
- package/dist/plots.d.ts +2 -2
- package/dist/plots.js +1 -1
- package/dist/{titlebar-Bj5pfdw_.d.ts → titlebar-YQ-jVQoS.d.ts} +2 -2
- package/dist/{titlebar-Bj5pfdw_.d.ts.map → titlebar-YQ-jVQoS.d.ts.map} +1 -1
- package/dist/titlebar.d.ts +1 -1
- package/dist/titlebar.js +83 -1
- package/dist/titlebar.js.map +1 -1
- package/dist/{tree-B74zdI60.js → tree-Chptss7I.js} +77 -15
- package/dist/tree-Chptss7I.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-CFuFDygi.d.ts} +17 -14
- package/dist/vertical-list-CFuFDygi.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/dist/{xy-axis-5KhVTNoX.d.ts → xy-axis-BfP9hKLj.d.ts} +25 -2
- package/dist/{xy-axis-5KhVTNoX.d.ts.map → xy-axis-BfP9hKLj.d.ts.map} +1 -1
- package/dist/{xy-axis-VrX8dmvS.js → xy-axis-DzM1brBZ.js} +141 -8
- package/dist/xy-axis-DzM1brBZ.js.map +1 -0
- package/docs-user/data-grid.md +19 -0
- package/docs-user/detail-list.md +1 -1
- package/docs-user/grid-list.md +120 -11
- package/docs-user/incr-search.md +12 -2
- package/docs-user/index.json +3 -3
- package/docs-user/led.md +37 -7
- package/docs-user/llms.txt +1 -1
- package/docs-user/panel.md +68 -13
- package/docs-user/titlebar.md +3 -4
- package/docs-user/tree.md +22 -1
- package/docs-user/vertical-list.md +7 -1
- package/llms.txt +1 -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/grid-list.js.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/dist/xy-axis-VrX8dmvS.js.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,186 @@ var IncrSearchTreeController = class {
|
|
|
2498
2499
|
}
|
|
2499
2500
|
};
|
|
2500
2501
|
//#endregion
|
|
2502
|
+
//#region src/incr-search/data-list-search-adapter.ts
|
|
2503
|
+
/**
|
|
2504
|
+
* A {@link ListSearchAdapter} over a data-backed list. Unlike the element
|
|
2505
|
+
* adapter it never materialises an element per item — it reads keys and text
|
|
2506
|
+
* straight from the source by index. Highlights are painted on whatever
|
|
2507
|
+
* captions are currently rendered in the shadow window; offscreen matches paint
|
|
2508
|
+
* when scrolled into view. See `docs/infra-incremental.md`.
|
|
2509
|
+
*/
|
|
2510
|
+
function createDataListSearchAdapter(host, options) {
|
|
2511
|
+
return {
|
|
2512
|
+
getItems: () => Array.from({ length: options.rawCount() }, (_, i) => ({
|
|
2513
|
+
key: options.rawKeyAt(i),
|
|
2514
|
+
index: i
|
|
2515
|
+
})),
|
|
2516
|
+
getLabel: (item) => options.rawSearchTextAt(item.index),
|
|
2517
|
+
getKey: (item) => item.key,
|
|
2518
|
+
focusItem: (item) => {
|
|
2519
|
+
if (item) host.focusKey(item.key);
|
|
2520
|
+
},
|
|
2521
|
+
activateItem: (item) => {
|
|
2522
|
+
if (options.onActivate) options.onActivate(item.key);
|
|
2523
|
+
else host.focusKey(item.key);
|
|
2524
|
+
},
|
|
2525
|
+
selectItems: (items) => {
|
|
2526
|
+
host.selectKeys(items.map((i) => i.key));
|
|
2527
|
+
},
|
|
2528
|
+
applyFilter: (matched) => {
|
|
2529
|
+
options.onFilterChange?.(matched);
|
|
2530
|
+
},
|
|
2531
|
+
highlightRoot: options.getHighlightRoot,
|
|
2532
|
+
highlightSelector: `.cell .caption`,
|
|
2533
|
+
highlightKeyOf: (el) => el.closest(`.cell`)?.getAttribute(`data-key`) ?? void 0,
|
|
2534
|
+
highlightText: (el) => el.textContent ?? ``,
|
|
2535
|
+
highlightKey: `grid-list-search`
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
//#endregion
|
|
2539
|
+
//#region src/interaction/keyed-list.ts
|
|
2540
|
+
/**
|
|
2541
|
+
* Helpers for key-based selection on the Element-flavoured list components
|
|
2542
|
+
* (`ixfx-vertical-list`, `ixfx-detail-list`, `ixfx-grid-list`).
|
|
2543
|
+
*
|
|
2544
|
+
* A "key" is a stable string the host puts on each item element — `data-key`
|
|
2545
|
+
* (preferred) or `data-value`. It survives the host rebuilding the item set
|
|
2546
|
+
* (re-fetch, re-sort, filter) where raw `Element` references do not.
|
|
2547
|
+
*/
|
|
2548
|
+
/**
|
|
2549
|
+
* The stable key for an item element: `data-key`, then `data-value`, else
|
|
2550
|
+
* `undefined`.
|
|
2551
|
+
*/
|
|
2552
|
+
function keyForElement(el) {
|
|
2553
|
+
return el.getAttribute(`data-key`) ?? el.getAttribute(`data-value`) ?? void 0;
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* A lazily-built key → element index over a container. Rebuilt on first use
|
|
2557
|
+
* and after any child-list mutation under `root`; `invalidate()` forces a
|
|
2558
|
+
* rebuild on the next `get()`.
|
|
2559
|
+
*
|
|
2560
|
+
* ```ts
|
|
2561
|
+
* #keyIndex = new KeyedElementIndex(this);
|
|
2562
|
+
* // ...
|
|
2563
|
+
* const el = this.#keyIndex.get(`row-42`);
|
|
2564
|
+
* // on disconnect:
|
|
2565
|
+
* this.#keyIndex.dispose();
|
|
2566
|
+
* ```
|
|
2567
|
+
*/
|
|
2568
|
+
var KeyedElementIndex = class {
|
|
2569
|
+
#root;
|
|
2570
|
+
#selector;
|
|
2571
|
+
#map = /* @__PURE__ */ new Map();
|
|
2572
|
+
#dirty = true;
|
|
2573
|
+
#observer;
|
|
2574
|
+
/**
|
|
2575
|
+
* @param root Container whose descendants are indexed.
|
|
2576
|
+
* @param itemSelector Which descendants count as items (default: any `[data-key]`).
|
|
2577
|
+
*/
|
|
2578
|
+
constructor(root, itemSelector = `[data-key]`) {
|
|
2579
|
+
this.#root = root;
|
|
2580
|
+
this.#selector = itemSelector;
|
|
2581
|
+
this.#observer = new MutationObserver(() => {
|
|
2582
|
+
this.#dirty = true;
|
|
2583
|
+
});
|
|
2584
|
+
this.#observer.observe(root, {
|
|
2585
|
+
childList: true,
|
|
2586
|
+
subtree: true
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
/** The element with this key, or `undefined`. First match wins on duplicates. */
|
|
2590
|
+
get(key) {
|
|
2591
|
+
if (this.#dirty) this.#rebuild();
|
|
2592
|
+
return this.#map.get(key);
|
|
2593
|
+
}
|
|
2594
|
+
/** Force a rebuild on the next `get()` — e.g. after a `data-key` attribute change. */
|
|
2595
|
+
invalidate() {
|
|
2596
|
+
this.#dirty = true;
|
|
2597
|
+
}
|
|
2598
|
+
/** Stop observing and drop the cache. */
|
|
2599
|
+
dispose() {
|
|
2600
|
+
this.#observer.disconnect();
|
|
2601
|
+
this.#map.clear();
|
|
2602
|
+
}
|
|
2603
|
+
#rebuild() {
|
|
2604
|
+
this.#map.clear();
|
|
2605
|
+
for (const el of this.#root.querySelectorAll(this.#selector)) {
|
|
2606
|
+
const key = keyForElement(el);
|
|
2607
|
+
if (key !== void 0 && !this.#map.has(key)) this.#map.set(key, el);
|
|
2608
|
+
}
|
|
2609
|
+
this.#dirty = false;
|
|
2610
|
+
}
|
|
2611
|
+
};
|
|
2612
|
+
//#endregion
|
|
2613
|
+
//#region src/incr-search/element-list-search-adapter.ts
|
|
2614
|
+
function defaultLabel(el) {
|
|
2615
|
+
return el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``;
|
|
2616
|
+
}
|
|
2617
|
+
/**
|
|
2618
|
+
* A {@link ListSearchAdapter} over the light-DOM item elements of a list
|
|
2619
|
+
* component. Filter mode toggles `hidden` (only on elements it hid itself, so a
|
|
2620
|
+
* consumer's own `hidden` items are left alone). Highlights are painted on the
|
|
2621
|
+
* item elements in the light DOM — the consuming page styles
|
|
2622
|
+
* `::highlight(<highlightKey>)`. See `docs/infra-incremental.md`.
|
|
2623
|
+
*/
|
|
2624
|
+
function createElementListSearchAdapter(host, options = {}) {
|
|
2625
|
+
const itemSelector = options.itemSelector ?? `:scope > *`;
|
|
2626
|
+
const getLabel = options.getLabel ?? defaultLabel;
|
|
2627
|
+
const getKey = options.getKey ?? ((el) => keyForElement(el) ?? defaultLabel(el));
|
|
2628
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
2629
|
+
const items = () => Array.from(host.querySelectorAll(itemSelector));
|
|
2630
|
+
return {
|
|
2631
|
+
getItems: items,
|
|
2632
|
+
getLabel,
|
|
2633
|
+
getKey,
|
|
2634
|
+
focusItem: (el) => {
|
|
2635
|
+
if (el) host.focusKey(getKey(el));
|
|
2636
|
+
},
|
|
2637
|
+
activateItem: (el) => {
|
|
2638
|
+
el.dispatchEvent(new MouseEvent(`click`, {
|
|
2639
|
+
bubbles: true,
|
|
2640
|
+
composed: true
|
|
2641
|
+
}));
|
|
2642
|
+
},
|
|
2643
|
+
selectItems: (els) => {
|
|
2644
|
+
host.selectKeys(els.map(getKey));
|
|
2645
|
+
},
|
|
2646
|
+
applyFilter: (matched) => {
|
|
2647
|
+
if (matched === void 0) {
|
|
2648
|
+
for (const el of hidden) el.hidden = false;
|
|
2649
|
+
hidden.clear();
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
for (const el of items()) if (matched.has(getKey(el))) {
|
|
2653
|
+
if (hidden.delete(el)) el.hidden = false;
|
|
2654
|
+
} else {
|
|
2655
|
+
el.hidden = true;
|
|
2656
|
+
hidden.add(el);
|
|
2657
|
+
}
|
|
2658
|
+
},
|
|
2659
|
+
highlightRoot: () => host,
|
|
2660
|
+
highlightSelector: itemSelector,
|
|
2661
|
+
highlightKeyOf: (el) => getKey(el),
|
|
2662
|
+
highlightText: getLabel,
|
|
2663
|
+
highlightKey: options.highlightKey,
|
|
2664
|
+
container: () => host
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
//#endregion
|
|
2501
2668
|
//#region src/incr-search/element-search.ts
|
|
2502
2669
|
const defaultGetLabel = (el) => el.textContent?.trim() ?? ``;
|
|
2503
2670
|
function defaultGetKey(el) {
|
|
2504
2671
|
return el.getAttribute(`data-key`) ?? el.getAttribute(`data-value`) ?? el.textContent?.trim() ?? ``;
|
|
2505
2672
|
}
|
|
2673
|
+
/**
|
|
2674
|
+
* DOM filter-search over a container's item elements (sets `hidden` on
|
|
2675
|
+
* non-matches, applies highlights).
|
|
2676
|
+
*
|
|
2677
|
+
* @deprecated The list components (`ixfx-grid-list`, `ixfx-vertical-list`,
|
|
2678
|
+
* `ixfx-detail-list`, …) now have a built-in navigate / select / filter search
|
|
2679
|
+
* popover — see `docs/infra-incremental.md` and `ListSearchController`. Kept for
|
|
2680
|
+
* standalone / non-component use.
|
|
2681
|
+
*/
|
|
2506
2682
|
function createElementIncrSearch(opts) {
|
|
2507
2683
|
const { container, itemSelector, getLabel = defaultGetLabel, getKey = defaultGetKey, labelSelector, highlightKey } = opts;
|
|
2508
2684
|
let elementByKey = /* @__PURE__ */ new Map();
|
|
@@ -2576,6 +2752,512 @@ function createElementIncrSearch(opts) {
|
|
|
2576
2752
|
};
|
|
2577
2753
|
}
|
|
2578
2754
|
//#endregion
|
|
2755
|
+
//#region src/incr-search/list-search-commands.ts
|
|
2756
|
+
/** Command ids registered by {@link registerListSearchCommands}, keyed by mode. */
|
|
2757
|
+
const LIST_SEARCH_COMMAND_IDS = {
|
|
2758
|
+
navigate: `list.search.navigate`,
|
|
2759
|
+
select: `list.search.select`,
|
|
2760
|
+
filter: `list.search.filter`
|
|
2761
|
+
};
|
|
2762
|
+
const LABELS = {
|
|
2763
|
+
navigate: {
|
|
2764
|
+
label: `Find`,
|
|
2765
|
+
description: `Type to move the cursor to the best match`
|
|
2766
|
+
},
|
|
2767
|
+
select: {
|
|
2768
|
+
label: `Find & Select`,
|
|
2769
|
+
description: `Type to select every matching item`
|
|
2770
|
+
},
|
|
2771
|
+
filter: {
|
|
2772
|
+
label: `Filter`,
|
|
2773
|
+
description: `Type to hide items that don't match`
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
/**
|
|
2777
|
+
* Register one command per mode the controller offers (see
|
|
2778
|
+
* `controller.registeredModes`). Each command opens the search popover in its
|
|
2779
|
+
* mode. Returns a function that unregisters them again — call it from
|
|
2780
|
+
* `disconnectedCallback`. See `docs/infra-incremental.md`.
|
|
2781
|
+
*/
|
|
2782
|
+
function registerListSearchCommands(registry, controller, options = {}) {
|
|
2783
|
+
const id = (mode) => options.idPrefix ? `${options.idPrefix}.search.${mode}` : LIST_SEARCH_COMMAND_IDS[mode];
|
|
2784
|
+
const registered = [];
|
|
2785
|
+
for (const mode of controller.registeredModes) {
|
|
2786
|
+
const commandId = id(mode);
|
|
2787
|
+
if (registry.has(commandId)) continue;
|
|
2788
|
+
registry.register({
|
|
2789
|
+
id: commandId,
|
|
2790
|
+
label: LABELS[mode].label,
|
|
2791
|
+
description: LABELS[mode].description,
|
|
2792
|
+
execute: () => controller.open(mode)
|
|
2793
|
+
});
|
|
2794
|
+
registered.push(commandId);
|
|
2795
|
+
}
|
|
2796
|
+
return () => {
|
|
2797
|
+
for (const commandId of registered) registry.unregister(commandId);
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
//#endregion
|
|
2801
|
+
//#region src/incr-search/list-search-controller.ts
|
|
2802
|
+
/** Canonical order — used for the switch-row and to normalise a requested set. */
|
|
2803
|
+
const LIST_SEARCH_MODES = [
|
|
2804
|
+
`navigate`,
|
|
2805
|
+
`select`,
|
|
2806
|
+
`filter`
|
|
2807
|
+
];
|
|
2808
|
+
const DEFAULT_HIGHLIGHT_KEY = `incr-search-highlight`;
|
|
2809
|
+
const INPUT_CLASS = `list-search-input`;
|
|
2810
|
+
/**
|
|
2811
|
+
* Shared brain for the three-mode incremental-search popover. A Lit
|
|
2812
|
+
* `ReactiveController`: the host adds `listSearchStyles` to `static styles`,
|
|
2813
|
+
* drops `${controller.renderField()}` into `render()`, and registers the
|
|
2814
|
+
* commands with {@link registerListSearchCommands}. See `docs/infra-incremental.md`.
|
|
2815
|
+
*/
|
|
2816
|
+
var ListSearchController = class {
|
|
2817
|
+
#host;
|
|
2818
|
+
#adapter;
|
|
2819
|
+
#modes;
|
|
2820
|
+
#placeholder;
|
|
2821
|
+
#search;
|
|
2822
|
+
#unsub;
|
|
2823
|
+
#observer;
|
|
2824
|
+
#itemByKey = /* @__PURE__ */ new Map();
|
|
2825
|
+
#open = false;
|
|
2826
|
+
#mode = `filter`;
|
|
2827
|
+
#query = ``;
|
|
2828
|
+
#matchIndex = 0;
|
|
2829
|
+
#matches = [];
|
|
2830
|
+
/** Whether a filter has been pushed to the adapter and still needs undoing. */
|
|
2831
|
+
#filterActive = false;
|
|
2832
|
+
constructor(host, options) {
|
|
2833
|
+
this.#host = host;
|
|
2834
|
+
this.#adapter = options.adapter;
|
|
2835
|
+
const requested = new Set(options.modes ?? LIST_SEARCH_MODES);
|
|
2836
|
+
this.#modes = LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
2837
|
+
this.#mode = this.#modes[0] ?? `filter`;
|
|
2838
|
+
this.#placeholder = options.placeholder ?? `Search…`;
|
|
2839
|
+
this.#search = createIncrSearch({
|
|
2840
|
+
selector: (i) => i.label,
|
|
2841
|
+
fzf: options.fzf
|
|
2842
|
+
});
|
|
2843
|
+
this.#unsub = this.#search.onChange((state) => this.#onState(state));
|
|
2844
|
+
host.addController(this);
|
|
2845
|
+
}
|
|
2846
|
+
/** `false` when no modes are available — the host should register no commands and render no field. */
|
|
2847
|
+
get enabled() {
|
|
2848
|
+
return this.#modes.length > 0;
|
|
2849
|
+
}
|
|
2850
|
+
/** The modes actually offered (component capability ∩ consumer request). */
|
|
2851
|
+
get registeredModes() {
|
|
2852
|
+
return this.#modes;
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Replace the offered mode set (normalised to {@link LIST_SEARCH_MODES}
|
|
2856
|
+
* order). If the current mode is dropped the popover closes and falls back to
|
|
2857
|
+
* the first remaining mode. Callers should re-run
|
|
2858
|
+
* {@link registerListSearchCommands} afterwards.
|
|
2859
|
+
*/
|
|
2860
|
+
setModes(modes) {
|
|
2861
|
+
const set = new Set(modes);
|
|
2862
|
+
this.#modes = LIST_SEARCH_MODES.filter((m) => set.has(m));
|
|
2863
|
+
if (!this.#modes.includes(this.#mode)) {
|
|
2864
|
+
if (this.#open) this.close();
|
|
2865
|
+
this.#mode = this.#modes[0] ?? `filter`;
|
|
2866
|
+
}
|
|
2867
|
+
this.#host.requestUpdate();
|
|
2868
|
+
}
|
|
2869
|
+
get isOpen() {
|
|
2870
|
+
return this.#open;
|
|
2871
|
+
}
|
|
2872
|
+
get mode() {
|
|
2873
|
+
return this.#mode;
|
|
2874
|
+
}
|
|
2875
|
+
get query() {
|
|
2876
|
+
return this.#query;
|
|
2877
|
+
}
|
|
2878
|
+
get matchCount() {
|
|
2879
|
+
return this.#matches.length;
|
|
2880
|
+
}
|
|
2881
|
+
get matchIndex() {
|
|
2882
|
+
return this.#matchIndex;
|
|
2883
|
+
}
|
|
2884
|
+
hostConnected() {
|
|
2885
|
+
const container = this.#adapter.container?.();
|
|
2886
|
+
if (container) {
|
|
2887
|
+
this.#observer = new MutationObserver(() => this.refresh());
|
|
2888
|
+
this.#observer.observe(container, {
|
|
2889
|
+
childList: true,
|
|
2890
|
+
subtree: false
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
this.refresh();
|
|
2894
|
+
}
|
|
2895
|
+
hostDisconnected() {
|
|
2896
|
+
this.#observer?.disconnect();
|
|
2897
|
+
this.#observer = void 0;
|
|
2898
|
+
if (this.#open) this.#teardownEffects();
|
|
2899
|
+
this.#open = false;
|
|
2900
|
+
this.#unsub?.();
|
|
2901
|
+
this.#unsub = void 0;
|
|
2902
|
+
this.#search.dispose();
|
|
2903
|
+
}
|
|
2904
|
+
/** Open the popover in `mode` (clamped to the available set). */
|
|
2905
|
+
open(mode) {
|
|
2906
|
+
if (!this.enabled) return;
|
|
2907
|
+
this.#mode = this.#modes.includes(mode) ? mode : this.#modes[0];
|
|
2908
|
+
this.#open = true;
|
|
2909
|
+
this.#query = ``;
|
|
2910
|
+
this.#matchIndex = 0;
|
|
2911
|
+
this.#matches = [];
|
|
2912
|
+
this.refresh();
|
|
2913
|
+
this.#search.setQuery(``);
|
|
2914
|
+
this.#host.requestUpdate();
|
|
2915
|
+
this.#host.updateComplete.then(() => this.#focusInput());
|
|
2916
|
+
}
|
|
2917
|
+
/** Close the popover, restoring any live filter. Selection / cursor stay put. */
|
|
2918
|
+
close() {
|
|
2919
|
+
if (!this.#open) return;
|
|
2920
|
+
this.#open = false;
|
|
2921
|
+
this.#teardownEffects();
|
|
2922
|
+
this.#query = ``;
|
|
2923
|
+
this.#matches = [];
|
|
2924
|
+
this.#search.clearQuery();
|
|
2925
|
+
this.#host.requestUpdate();
|
|
2926
|
+
this.#host.focus();
|
|
2927
|
+
}
|
|
2928
|
+
setQuery(query) {
|
|
2929
|
+
this.#query = query;
|
|
2930
|
+
this.#matchIndex = 0;
|
|
2931
|
+
this.#search.setQuery(query);
|
|
2932
|
+
}
|
|
2933
|
+
/** Re-interpret the current query under a new mode (undoing the old mode's effect). */
|
|
2934
|
+
switchMode(next) {
|
|
2935
|
+
if (!this.#modes.includes(next) || next === this.#mode) return;
|
|
2936
|
+
if (this.#mode === `filter` && this.#filterActive) {
|
|
2937
|
+
this.#adapter.applyFilter(void 0);
|
|
2938
|
+
this.#filterActive = false;
|
|
2939
|
+
}
|
|
2940
|
+
this.#mode = next;
|
|
2941
|
+
this.#applyMode();
|
|
2942
|
+
this.#host.requestUpdate();
|
|
2943
|
+
this.#host.updateComplete.then(() => this.#focusInput());
|
|
2944
|
+
}
|
|
2945
|
+
/** One-shot: turn the current match set into the selection, staying in the current mode. */
|
|
2946
|
+
selectMatches() {
|
|
2947
|
+
if (!this.#modes.includes(`select`)) return;
|
|
2948
|
+
this.#adapter.selectItems(this.#currentItems());
|
|
2949
|
+
this.#host.requestUpdate();
|
|
2950
|
+
}
|
|
2951
|
+
nextMatch() {
|
|
2952
|
+
this.#cycle(1);
|
|
2953
|
+
}
|
|
2954
|
+
prevMatch() {
|
|
2955
|
+
this.#cycle(-1);
|
|
2956
|
+
}
|
|
2957
|
+
/** Enter: in `navigate` activate the current match; then close. */
|
|
2958
|
+
commit() {
|
|
2959
|
+
if (this.#mode === `navigate`) {
|
|
2960
|
+
const item = this.#itemFor(this.#matches[this.#matchIndex]);
|
|
2961
|
+
if (item) {
|
|
2962
|
+
if (this.#adapter.activateItem) this.#adapter.activateItem(item);
|
|
2963
|
+
else this.#adapter.focusItem(item);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
this.close();
|
|
2967
|
+
}
|
|
2968
|
+
/** Rebuild the searchable item set from the adapter. Called on open and on container mutation. */
|
|
2969
|
+
refresh() {
|
|
2970
|
+
this.#itemByKey.clear();
|
|
2971
|
+
const items = [];
|
|
2972
|
+
for (const it of this.#adapter.getItems()) {
|
|
2973
|
+
const key = this.#adapter.getKey(it);
|
|
2974
|
+
this.#itemByKey.set(key, it);
|
|
2975
|
+
items.push({
|
|
2976
|
+
key,
|
|
2977
|
+
label: this.#adapter.getLabel(it)
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
this.#search.setItems(items);
|
|
2981
|
+
}
|
|
2982
|
+
/** Popover markup. Pair with `listSearchStyles` in the host's `static styles`. */
|
|
2983
|
+
renderField() {
|
|
2984
|
+
if (!this.enabled) return A;
|
|
2985
|
+
const hasQuery = this.#query.trim().length > 0;
|
|
2986
|
+
const others = this.#modes.filter((m) => m !== this.#mode);
|
|
2987
|
+
const showSelectMatches = this.#modes.includes(`select`) && this.#mode !== `select`;
|
|
2988
|
+
return b`
|
|
2989
|
+
<div class="list-search" part="search" ?hidden=${!this.#open}>
|
|
2990
|
+
<div class="list-search-row">
|
|
2991
|
+
<span class="list-search-mode">${this.#mode}</span>
|
|
2992
|
+
<input
|
|
2993
|
+
class=${INPUT_CLASS}
|
|
2994
|
+
type="text"
|
|
2995
|
+
part="search-input"
|
|
2996
|
+
placeholder=${this.#placeholder}
|
|
2997
|
+
.value=${this.#query}
|
|
2998
|
+
@input=${this.#onInput}
|
|
2999
|
+
@keydown=${this.#onKeydown}
|
|
3000
|
+
/>
|
|
3001
|
+
${hasQuery ? b`<span class="list-search-count">${this.#matches.length === 0 ? `0` : this.#matchIndex + 1} / ${this.#matches.length}</span>` : A}
|
|
3002
|
+
${this.#mode === `navigate` && this.#matches.length > 1 ? b`
|
|
3003
|
+
<button class="list-search-icon-btn" title="Previous match (↑)" @click=${() => this.prevMatch()}>‹</button>
|
|
3004
|
+
<button class="list-search-icon-btn" title="Next match (↓)" @click=${() => this.nextMatch()}>›</button>
|
|
3005
|
+
` : A}
|
|
3006
|
+
<button class="list-search-icon-btn" title="Close (Esc)" @click=${() => this.close()}>✕</button>
|
|
3007
|
+
</div>
|
|
3008
|
+
${others.length > 0 || showSelectMatches ? b`
|
|
3009
|
+
<div class="list-search-switch">
|
|
3010
|
+
${others.map((m) => b`<button class="list-search-btn" @click=${() => this.switchMode(m)}>${m}</button>`)}
|
|
3011
|
+
${showSelectMatches ? b`<button class="list-search-btn" @click=${() => this.selectMatches()}>Select matches</button>` : A}
|
|
3012
|
+
</div>
|
|
3013
|
+
` : A}
|
|
3014
|
+
</div>
|
|
3015
|
+
`;
|
|
3016
|
+
}
|
|
3017
|
+
#focusInput() {
|
|
3018
|
+
this.#host.renderRoot?.querySelector(`.${INPUT_CLASS}`)?.focus();
|
|
3019
|
+
}
|
|
3020
|
+
#onInput = (e) => {
|
|
3021
|
+
this.setQuery(e.target.value);
|
|
3022
|
+
};
|
|
3023
|
+
#onKeydown = (e) => {
|
|
3024
|
+
switch (e.key) {
|
|
3025
|
+
case `Escape`:
|
|
3026
|
+
e.preventDefault();
|
|
3027
|
+
e.stopPropagation();
|
|
3028
|
+
this.close();
|
|
3029
|
+
break;
|
|
3030
|
+
case `Enter`:
|
|
3031
|
+
e.preventDefault();
|
|
3032
|
+
e.stopPropagation();
|
|
3033
|
+
this.commit();
|
|
3034
|
+
break;
|
|
3035
|
+
case `ArrowDown`:
|
|
3036
|
+
if (this.#mode === `navigate`) {
|
|
3037
|
+
e.preventDefault();
|
|
3038
|
+
this.nextMatch();
|
|
3039
|
+
}
|
|
3040
|
+
break;
|
|
3041
|
+
case `ArrowUp`: if (this.#mode === `navigate`) {
|
|
3042
|
+
e.preventDefault();
|
|
3043
|
+
this.prevMatch();
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
#cycle(delta) {
|
|
3048
|
+
if (this.#matches.length === 0) return;
|
|
3049
|
+
const n = this.#matches.length;
|
|
3050
|
+
this.#matchIndex = (this.#matchIndex + delta + n) % n;
|
|
3051
|
+
if (this.#mode === `navigate`) this.#adapter.focusItem(this.#itemFor(this.#matches[this.#matchIndex]));
|
|
3052
|
+
this.#host.requestUpdate();
|
|
3053
|
+
}
|
|
3054
|
+
#itemFor(match) {
|
|
3055
|
+
return match ? this.#itemByKey.get(match.item.key) : void 0;
|
|
3056
|
+
}
|
|
3057
|
+
#currentItems() {
|
|
3058
|
+
return this.#matches.map((m) => this.#itemFor(m)).filter((x) => x !== void 0);
|
|
3059
|
+
}
|
|
3060
|
+
get #highlightKey() {
|
|
3061
|
+
return this.#adapter.highlightKey ?? DEFAULT_HIGHLIGHT_KEY;
|
|
3062
|
+
}
|
|
3063
|
+
#onState(state) {
|
|
3064
|
+
if (!this.#open) return;
|
|
3065
|
+
const hasQuery = state.query.trim().length > 0;
|
|
3066
|
+
this.#matches = hasQuery ? [...state.results] : [];
|
|
3067
|
+
if (this.#matchIndex >= this.#matches.length) this.#matchIndex = 0;
|
|
3068
|
+
this.#applyMode();
|
|
3069
|
+
this.#host.requestUpdate();
|
|
3070
|
+
}
|
|
3071
|
+
#applyMode() {
|
|
3072
|
+
if (!(this.#query.trim().length > 0)) {
|
|
3073
|
+
if (this.#filterActive) {
|
|
3074
|
+
this.#adapter.applyFilter(void 0);
|
|
3075
|
+
this.#filterActive = false;
|
|
3076
|
+
}
|
|
3077
|
+
this.#applyHighlights();
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
switch (this.#mode) {
|
|
3081
|
+
case `navigate`:
|
|
3082
|
+
this.#adapter.focusItem(this.#itemFor(this.#matches[this.#matchIndex]));
|
|
3083
|
+
break;
|
|
3084
|
+
case `select`:
|
|
3085
|
+
this.#adapter.selectItems(this.#currentItems());
|
|
3086
|
+
break;
|
|
3087
|
+
case `filter`:
|
|
3088
|
+
this.#adapter.applyFilter(new Set(this.#matches.map((m) => m.item.key)));
|
|
3089
|
+
this.#filterActive = true;
|
|
3090
|
+
}
|
|
3091
|
+
this.#applyHighlights();
|
|
3092
|
+
}
|
|
3093
|
+
#applyHighlights() {
|
|
3094
|
+
const root = this.#adapter.highlightRoot?.();
|
|
3095
|
+
const selector = this.#adapter.highlightSelector;
|
|
3096
|
+
const keyOf = this.#adapter.highlightKeyOf;
|
|
3097
|
+
if (!root || !selector || !keyOf) return;
|
|
3098
|
+
if (this.#query.trim().length === 0) {
|
|
3099
|
+
clearHighlights({ key: this.#highlightKey });
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
const state = this.#search.state;
|
|
3103
|
+
const targets = buildHighlightTargets(root, selector, state.positionsByItem, (el) => {
|
|
3104
|
+
const key = keyOf(el);
|
|
3105
|
+
return key === void 0 ? void 0 : state.results.find((r) => r.item.key === key)?.item;
|
|
3106
|
+
}, this.#adapter.highlightText ?? ((el) => el.textContent ?? ``));
|
|
3107
|
+
try {
|
|
3108
|
+
applyHighlights(targets, { key: this.#highlightKey });
|
|
3109
|
+
} catch {
|
|
3110
|
+
clearHighlights({ key: this.#highlightKey });
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
#teardownEffects() {
|
|
3114
|
+
if (this.#filterActive) {
|
|
3115
|
+
this.#adapter.applyFilter(void 0);
|
|
3116
|
+
this.#filterActive = false;
|
|
3117
|
+
}
|
|
3118
|
+
clearHighlights({ key: this.#highlightKey });
|
|
3119
|
+
}
|
|
3120
|
+
};
|
|
3121
|
+
//#endregion
|
|
3122
|
+
//#region src/incr-search/list-search-styles.ts
|
|
3123
|
+
/**
|
|
3124
|
+
* Styles for the incremental-search popover rendered by
|
|
3125
|
+
* `ListSearchController.renderField()`. Add to a component's `static styles`;
|
|
3126
|
+
* the class names are the contract between this fragment and `renderField()`.
|
|
3127
|
+
*
|
|
3128
|
+
* The host must give the popover a positioned ancestor (a wrapper with
|
|
3129
|
+
* `position: relative`, or `:host { position: relative }`). Tunables:
|
|
3130
|
+
* `--list-search-inset` (default `8px`) offsets it from the top-right corner.
|
|
3131
|
+
*
|
|
3132
|
+
* See `docs/infra-incremental.md`.
|
|
3133
|
+
*/
|
|
3134
|
+
const listSearchStyles = i$3`
|
|
3135
|
+
.list-search {
|
|
3136
|
+
position: absolute;
|
|
3137
|
+
top: var(--list-search-inset, 8px);
|
|
3138
|
+
right: var(--list-search-inset, 8px);
|
|
3139
|
+
z-index: 20;
|
|
3140
|
+
display: flex;
|
|
3141
|
+
flex-direction: column;
|
|
3142
|
+
gap: 4px;
|
|
3143
|
+
padding: 6px;
|
|
3144
|
+
box-sizing: border-box;
|
|
3145
|
+
background: var(--surface-4, #2a2a2a);
|
|
3146
|
+
color: var(--surface-3-text, inherit);
|
|
3147
|
+
border: 1px solid var(--border, #444);
|
|
3148
|
+
border-radius: var(--radius-m, 6px);
|
|
3149
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
|
|
3150
|
+
font-size: var(--text-s, 0.85em);
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
.list-search[hidden] {
|
|
3154
|
+
display: none;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
.list-search-row {
|
|
3158
|
+
display: flex;
|
|
3159
|
+
align-items: center;
|
|
3160
|
+
gap: 4px;
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
.list-search-mode {
|
|
3164
|
+
flex: none;
|
|
3165
|
+
padding: 1px 7px;
|
|
3166
|
+
border-radius: 999px;
|
|
3167
|
+
font-weight: 600;
|
|
3168
|
+
text-transform: capitalize;
|
|
3169
|
+
background: var(--accent, #60a5fa);
|
|
3170
|
+
color: var(--accent-text, #000);
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
.list-search-input {
|
|
3174
|
+
flex: 1;
|
|
3175
|
+
min-width: 140px;
|
|
3176
|
+
background: transparent;
|
|
3177
|
+
border: none;
|
|
3178
|
+
outline: none;
|
|
3179
|
+
color: inherit;
|
|
3180
|
+
font: inherit;
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
.list-search-count {
|
|
3184
|
+
flex: none;
|
|
3185
|
+
opacity: 0.6;
|
|
3186
|
+
font-variant-numeric: tabular-nums;
|
|
3187
|
+
white-space: nowrap;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
.list-search-switch {
|
|
3191
|
+
display: flex;
|
|
3192
|
+
flex-wrap: wrap;
|
|
3193
|
+
gap: 4px;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
.list-search-btn {
|
|
3197
|
+
padding: 1px 7px;
|
|
3198
|
+
background: var(--surface-3, #333);
|
|
3199
|
+
color: inherit;
|
|
3200
|
+
border: 1px solid var(--border, #444);
|
|
3201
|
+
border-radius: var(--radius-s, 4px);
|
|
3202
|
+
cursor: pointer;
|
|
3203
|
+
font: inherit;
|
|
3204
|
+
text-transform: capitalize;
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
.list-search-btn:hover {
|
|
3208
|
+
background: var(--item-bg-tickled, #444);
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
.list-search-icon-btn {
|
|
3212
|
+
flex: none;
|
|
3213
|
+
padding: 0 2px;
|
|
3214
|
+
background: transparent;
|
|
3215
|
+
border: none;
|
|
3216
|
+
color: var(--surface-muted-text, #888);
|
|
3217
|
+
cursor: pointer;
|
|
3218
|
+
font-size: 1.1em;
|
|
3219
|
+
line-height: 1;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
.list-search-icon-btn:hover {
|
|
3223
|
+
color: inherit;
|
|
3224
|
+
}
|
|
3225
|
+
`;
|
|
3226
|
+
//#endregion
|
|
3227
|
+
//#region src/incr-search/tree-list-search-adapter.ts
|
|
3228
|
+
/**
|
|
3229
|
+
* A {@link ListSearchAdapter} over any `TreeSearchHost` (`ixfx-tree-list`,
|
|
3230
|
+
* `ixfx-miller-list`, `ixfx-crumb-navigation`). Navigate reveals the match
|
|
3231
|
+
* (expands its ancestors, moves the cursor); select drives `selectMany`; filter
|
|
3232
|
+
* sets `filterPredicate`. The item set is the whole tree — `getNodes()` returns
|
|
3233
|
+
* every node regardless of expansion or an active filter — so refining a query
|
|
3234
|
+
* can always re-surface a node. See `docs/infra-incremental.md`.
|
|
3235
|
+
*/
|
|
3236
|
+
function createTreeListSearchAdapter(host, options = {}) {
|
|
3237
|
+
return {
|
|
3238
|
+
getItems: () => host.getNodes(),
|
|
3239
|
+
getLabel: options.getLabel ?? ((item) => item.node.item.label),
|
|
3240
|
+
getKey: (item) => item.node.item.key,
|
|
3241
|
+
focusItem: (item) => {
|
|
3242
|
+
if (item) host.revealKey(item.node.item.key);
|
|
3243
|
+
},
|
|
3244
|
+
activateItem: (item) => {
|
|
3245
|
+
host.revealKey(item.node.item.key);
|
|
3246
|
+
host.select(item.node);
|
|
3247
|
+
},
|
|
3248
|
+
selectItems: (items) => {
|
|
3249
|
+
host.selectMany(items.map((i) => i.node));
|
|
3250
|
+
},
|
|
3251
|
+
applyFilter: (matched) => {
|
|
3252
|
+
host.filterPredicate = matched === void 0 ? void 0 : (node) => matched.has(node.item.key);
|
|
3253
|
+
},
|
|
3254
|
+
highlightRoot: () => host.shadowRoot,
|
|
3255
|
+
highlightSelector: options.labelSelector ?? `.tree-label`,
|
|
3256
|
+
highlightKeyOf: (el) => el.closest(`[data-key]`)?.dataset.key,
|
|
3257
|
+
highlightKey: options.highlightKey
|
|
3258
|
+
};
|
|
3259
|
+
}
|
|
3260
|
+
//#endregion
|
|
2579
3261
|
//#region src/ac-token/ac-token.ts
|
|
2580
3262
|
const DEFAULT_TOKENIZER = (text) => text.split(/[\s,;]+/);
|
|
2581
3263
|
function makeLruCache(maxSize) {
|
|
@@ -3877,80 +4559,6 @@ __decorate([state()], RadioGroup.prototype, "_icons", void 0);
|
|
|
3877
4559
|
__decorate([query(`slot`)], RadioGroup.prototype, "_slot", void 0);
|
|
3878
4560
|
RadioGroup = __decorate([safeCustomElement(`ixfx-radio-group`)], RadioGroup);
|
|
3879
4561
|
//#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
4562
|
//#region src/interaction/list-item-animation.ts
|
|
3955
4563
|
/**
|
|
3956
4564
|
* Shared insertion / removal animation for the list & tree components.
|
|
@@ -8557,6 +9165,16 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
|
|
|
8557
9165
|
if (!keyPath) return false;
|
|
8558
9166
|
return this.navigateTo(keyPath);
|
|
8559
9167
|
}
|
|
9168
|
+
/** `TreeComponent` parity — a crumb path always follows the selection, so this just navigates to `key`. */
|
|
9169
|
+
revealKey(key) {
|
|
9170
|
+
return this.navigateToNode((n) => n.item.key === key);
|
|
9171
|
+
}
|
|
9172
|
+
/** `TreeComponent` parity — a crumb path is a single selection, so only the last node is kept. */
|
|
9173
|
+
selectMany(nodes) {
|
|
9174
|
+
const last = [...nodes].at(-1);
|
|
9175
|
+
if (last) this.select(last);
|
|
9176
|
+
else this.clearSelection();
|
|
9177
|
+
}
|
|
8560
9178
|
#findNodePath(nodes, predicate, prefix = []) {
|
|
8561
9179
|
for (const node of nodes) {
|
|
8562
9180
|
const path = [...prefix, node.item.key];
|
|
@@ -11067,6 +11685,8 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11067
11685
|
this._sortedRows = [];
|
|
11068
11686
|
this._flat = [];
|
|
11069
11687
|
this._flatKeys = [];
|
|
11688
|
+
this.#searchKeyByRow = /* @__PURE__ */ new Map();
|
|
11689
|
+
this.search = new ListSearchController(this, { adapter: this.#createSearchAdapter() });
|
|
11070
11690
|
this.#prevFlatKeys = [];
|
|
11071
11691
|
this.#enteringRowKeys = /* @__PURE__ */ new Set();
|
|
11072
11692
|
this.#rowReflowTimer = 0;
|
|
@@ -11272,6 +11892,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11272
11892
|
tickledStyles,
|
|
11273
11893
|
tickledItemStyles,
|
|
11274
11894
|
listItemFadeKeyframes,
|
|
11895
|
+
listSearchStyles,
|
|
11275
11896
|
i$3`
|
|
11276
11897
|
:host {
|
|
11277
11898
|
--data-grid-row-height: 28px;
|
|
@@ -11280,6 +11901,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11280
11901
|
--data-grid-header-bg: var(--surface-2);
|
|
11281
11902
|
--data-grid-border: var(--border);
|
|
11282
11903
|
--data-grid-header-text: var(--surface-3-text);
|
|
11904
|
+
position: relative;
|
|
11283
11905
|
display: block;
|
|
11284
11906
|
overflow: hidden;
|
|
11285
11907
|
border: var(--data-grid-border);
|
|
@@ -11373,6 +11995,55 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11373
11995
|
`
|
|
11374
11996
|
];
|
|
11375
11997
|
}
|
|
11998
|
+
#searchRegistry;
|
|
11999
|
+
#unregisterSearchCommands;
|
|
12000
|
+
#searchKeyByRow;
|
|
12001
|
+
/** Command registry carrying this grid's `list.search.*` commands. */
|
|
12002
|
+
get registry() {
|
|
12003
|
+
return this.#searchRegistry;
|
|
12004
|
+
}
|
|
12005
|
+
#rowSearchText(row) {
|
|
12006
|
+
if (this.rowSearchText) return this.rowSearchText(row);
|
|
12007
|
+
const rec = row;
|
|
12008
|
+
return this._visibleColumns.map((c) => String(rec[c.key] ?? ``)).join(` `);
|
|
12009
|
+
}
|
|
12010
|
+
#createSearchAdapter() {
|
|
12011
|
+
return {
|
|
12012
|
+
getItems: () => {
|
|
12013
|
+
this.#searchKeyByRow.clear();
|
|
12014
|
+
this._sortedRows.forEach((r, i) => this.#searchKeyByRow.set(r, this.getRowKey(r, i)));
|
|
12015
|
+
return this._sortedRows;
|
|
12016
|
+
},
|
|
12017
|
+
getLabel: (row) => this.#rowSearchText(row),
|
|
12018
|
+
getKey: (row) => this.#searchKeyByRow.get(row) ?? String(this._sortedRows.indexOf(row)),
|
|
12019
|
+
focusItem: (row) => {
|
|
12020
|
+
if (row === void 0) return;
|
|
12021
|
+
const key = this.#searchKeyByRow.get(row);
|
|
12022
|
+
if (key !== void 0) this.tickleRow(key);
|
|
12023
|
+
},
|
|
12024
|
+
selectItems: (rows) => {
|
|
12025
|
+
this.selectKeys(rows.map((r) => this.#searchKeyByRow.get(r)).filter((k) => k !== void 0));
|
|
12026
|
+
},
|
|
12027
|
+
applyFilter: (matched) => {
|
|
12028
|
+
this.filterPredicate = matched === void 0 ? void 0 : (row) => {
|
|
12029
|
+
const k = this.#searchKeyByRow.get(row);
|
|
12030
|
+
return k === void 0 || matched.has(k);
|
|
12031
|
+
};
|
|
12032
|
+
}
|
|
12033
|
+
};
|
|
12034
|
+
}
|
|
12035
|
+
#effectiveSearchModes() {
|
|
12036
|
+
const raw = this.searchModes?.trim();
|
|
12037
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
12038
|
+
const requested = new Set(raw.split(/\s+/));
|
|
12039
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
12040
|
+
}
|
|
12041
|
+
#syncSearchCommands() {
|
|
12042
|
+
if (!this.#searchRegistry) return;
|
|
12043
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
12044
|
+
this.#unregisterSearchCommands?.();
|
|
12045
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
12046
|
+
}
|
|
11376
12047
|
/** Row-key snapshot from the previous update, for the insertion / removal animation. */
|
|
11377
12048
|
#prevFlatKeys;
|
|
11378
12049
|
#enteringRowKeys;
|
|
@@ -11385,17 +12056,29 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11385
12056
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
11386
12057
|
this.addEventListener(`keydown`, this._onKeyDown);
|
|
11387
12058
|
this.addEventListener(`focusout`, this._onFocusOut);
|
|
12059
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
12060
|
+
this.#syncSearchCommands();
|
|
11388
12061
|
}
|
|
11389
12062
|
disconnectedCallback() {
|
|
11390
12063
|
super.disconnectedCallback();
|
|
11391
12064
|
this.removeEventListener(`keydown`, this._onKeyDown);
|
|
11392
12065
|
this.removeEventListener(`focusout`, this._onFocusOut);
|
|
11393
12066
|
cancelAnimationFrame(this._raf);
|
|
12067
|
+
this.#unregisterSearchCommands?.();
|
|
12068
|
+
this.#unregisterSearchCommands = void 0;
|
|
12069
|
+
this.#searchRegistry = void 0;
|
|
11394
12070
|
if (this.#rowReflowTimer) {
|
|
11395
12071
|
clearTimeout(this.#rowReflowTimer);
|
|
11396
12072
|
this.#rowReflowTimer = 0;
|
|
11397
12073
|
}
|
|
11398
12074
|
}
|
|
12075
|
+
/**
|
|
12076
|
+
* Dispatch a command to this grid — routes through the command registry so
|
|
12077
|
+
* `list.search.*` (and any other registered commands) work.
|
|
12078
|
+
*/
|
|
12079
|
+
dispatch(commandId, args) {
|
|
12080
|
+
this.#searchRegistry?.invoke(commandId, args);
|
|
12081
|
+
}
|
|
11399
12082
|
firstUpdated() {
|
|
11400
12083
|
new ResizeObserver(() => {
|
|
11401
12084
|
if (!this._viewportEl) return;
|
|
@@ -11421,6 +12104,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11421
12104
|
willUpdate(changed) {
|
|
11422
12105
|
super.willUpdate(changed);
|
|
11423
12106
|
this.setAttribute(`data-input-mode`, this._tickled.inputMode);
|
|
12107
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
11424
12108
|
if (!this.#appendInProgress) {
|
|
11425
12109
|
if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`)) this._sortedRows = this._applySort([...this.rows]);
|
|
11426
12110
|
this._rebuildFlat();
|
|
@@ -11475,18 +12159,23 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11475
12159
|
return String(this.groupBy(row));
|
|
11476
12160
|
}
|
|
11477
12161
|
_rebuildFlat() {
|
|
12162
|
+
const filtered = this.filterPredicate !== void 0;
|
|
12163
|
+
const source = filtered ? this._sortedRows.filter((r) => this.filterPredicate(r)) : this._sortedRows;
|
|
11478
12164
|
if (!this.groupBy) {
|
|
11479
|
-
this._flat =
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
12165
|
+
this._flat = source.map((r, i) => {
|
|
12166
|
+
const idx = filtered ? this._sortedRows.indexOf(r) : i;
|
|
12167
|
+
return {
|
|
12168
|
+
type: `row`,
|
|
12169
|
+
key: this.getRowKey(r, idx),
|
|
12170
|
+
row: r,
|
|
12171
|
+
idx
|
|
12172
|
+
};
|
|
12173
|
+
});
|
|
11485
12174
|
this._flatKeys = this._flat.map((f) => f.key);
|
|
11486
12175
|
return;
|
|
11487
12176
|
}
|
|
11488
12177
|
const groups = /* @__PURE__ */ new Map();
|
|
11489
|
-
for (const r of
|
|
12178
|
+
for (const r of source) {
|
|
11490
12179
|
const k = this._getGroupKey(r) ?? ``;
|
|
11491
12180
|
const arr = groups.get(k);
|
|
11492
12181
|
if (arr) arr.push(r);
|
|
@@ -12356,6 +13045,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
12356
13045
|
})}
|
|
12357
13046
|
</div>
|
|
12358
13047
|
</div>
|
|
13048
|
+
${this.search.renderField()}
|
|
12359
13049
|
</div>
|
|
12360
13050
|
`;
|
|
12361
13051
|
}
|
|
@@ -12399,6 +13089,13 @@ __decorate([property({
|
|
|
12399
13089
|
attribute: `column-menu`
|
|
12400
13090
|
})], DataGridElement.prototype, "columnMenu", void 0);
|
|
12401
13091
|
__decorate([property({ attribute: false })], DataGridElement.prototype, "globalFormatter", void 0);
|
|
13092
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "filterPredicate", void 0);
|
|
13093
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "rowSearchText", void 0);
|
|
13094
|
+
__decorate([property({
|
|
13095
|
+
type: String,
|
|
13096
|
+
attribute: `search-modes`,
|
|
13097
|
+
reflect: true
|
|
13098
|
+
})], DataGridElement.prototype, "searchModes", void 0);
|
|
12402
13099
|
__decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
|
|
12403
13100
|
__decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
|
|
12404
13101
|
__decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
|
|
@@ -12680,12 +13377,12 @@ __decorate([state()], DataTableElement.prototype, "_stacked", void 0);
|
|
|
12680
13377
|
DataTableElement = __decorate([safeCustomElement(`ixfx-data-table`)], DataTableElement);
|
|
12681
13378
|
//#endregion
|
|
12682
13379
|
//#region src/detail-list/detail-list.ts
|
|
12683
|
-
const ITEM_SELECTOR$
|
|
13380
|
+
const ITEM_SELECTOR$3 = `:scope > li`;
|
|
12684
13381
|
const SEARCH_HIGHLIGHT_KEY = `detail-list-search`;
|
|
12685
13382
|
const LABEL_SPAN_CSS = `overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0`;
|
|
12686
13383
|
let DetailListElement = class DetailListElement extends i {
|
|
12687
|
-
constructor(...
|
|
12688
|
-
super(...
|
|
13384
|
+
constructor(..._args) {
|
|
13385
|
+
super(..._args);
|
|
12689
13386
|
this.selectionMode = `single`;
|
|
12690
13387
|
this.interactionMode = `standard`;
|
|
12691
13388
|
this.sort = `none`;
|
|
@@ -12695,8 +13392,6 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12695
13392
|
this.iconProperty = ``;
|
|
12696
13393
|
this.keyProperty = ``;
|
|
12697
13394
|
this.itemWrap = `stop`;
|
|
12698
|
-
this._searchVisible = false;
|
|
12699
|
-
this._searchQuery = ``;
|
|
12700
13395
|
this.#itemData = /* @__PURE__ */ new WeakMap();
|
|
12701
13396
|
this.#grid = [];
|
|
12702
13397
|
this.#positions = /* @__PURE__ */ new Map();
|
|
@@ -12733,6 +13428,11 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12733
13428
|
onStateChange: () => this._syncItemStates(),
|
|
12734
13429
|
itemWrap: this.itemWrap
|
|
12735
13430
|
});
|
|
13431
|
+
this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
|
|
13432
|
+
itemSelector: ITEM_SELECTOR$3,
|
|
13433
|
+
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
13434
|
+
highlightKey: SEARCH_HIGHLIGHT_KEY
|
|
13435
|
+
}) });
|
|
12736
13436
|
this._onPointerOver = (e) => {
|
|
12737
13437
|
const item = this._findListItem(e.composedPath());
|
|
12738
13438
|
if (item) this.tickled.handlePointerEnter(item);
|
|
@@ -12741,11 +13441,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12741
13441
|
this.tickled.handlePointerLeave(e);
|
|
12742
13442
|
};
|
|
12743
13443
|
this._onKeyDown = (e) => {
|
|
12744
|
-
if (
|
|
12745
|
-
e.preventDefault();
|
|
12746
|
-
this._openSearch();
|
|
12747
|
-
return;
|
|
12748
|
-
}
|
|
13444
|
+
if (this.search.isOpen) return;
|
|
12749
13445
|
if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
|
|
12750
13446
|
if (this.selection.canSelectAll()) {
|
|
12751
13447
|
e.preventDefault();
|
|
@@ -12753,11 +13449,6 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12753
13449
|
}
|
|
12754
13450
|
return;
|
|
12755
13451
|
}
|
|
12756
|
-
if (this._searchVisible && e.key === `Escape`) {
|
|
12757
|
-
e.preventDefault();
|
|
12758
|
-
this._closeSearch();
|
|
12759
|
-
return;
|
|
12760
|
-
}
|
|
12761
13452
|
switch (e.key) {
|
|
12762
13453
|
case `ArrowDown`:
|
|
12763
13454
|
e.preventDefault();
|
|
@@ -12826,44 +13517,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12826
13517
|
e.preventDefault();
|
|
12827
13518
|
this._listEl.scrollLeft += e.deltaY;
|
|
12828
13519
|
};
|
|
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
13520
|
}
|
|
12866
|
-
#search;
|
|
12867
13521
|
#itemData;
|
|
12868
13522
|
#grid;
|
|
12869
13523
|
#positions;
|
|
@@ -12875,6 +13529,24 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12875
13529
|
#builtFromProvider;
|
|
12876
13530
|
#providerBuilt;
|
|
12877
13531
|
#data;
|
|
13532
|
+
#searchRegistry;
|
|
13533
|
+
#unregisterSearchCommands;
|
|
13534
|
+
/** Command registry carrying this list's `list.search.*` commands. */
|
|
13535
|
+
get registry() {
|
|
13536
|
+
return this.#searchRegistry;
|
|
13537
|
+
}
|
|
13538
|
+
#effectiveSearchModes() {
|
|
13539
|
+
const raw = this.searchModes?.trim();
|
|
13540
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
13541
|
+
const requested = new Set(raw.split(/\s+/));
|
|
13542
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
13543
|
+
}
|
|
13544
|
+
#syncSearchCommands() {
|
|
13545
|
+
if (!this.#searchRegistry) return;
|
|
13546
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
13547
|
+
this.#unregisterSearchCommands?.();
|
|
13548
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
13549
|
+
}
|
|
12878
13550
|
connectedCallback() {
|
|
12879
13551
|
super.connectedCallback();
|
|
12880
13552
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
@@ -12896,7 +13568,9 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12896
13568
|
subtree: false,
|
|
12897
13569
|
attributeFilter: [`hidden`]
|
|
12898
13570
|
});
|
|
12899
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13571
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) this.#animatedItems.add(el);
|
|
13572
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
13573
|
+
this.#syncSearchCommands();
|
|
12900
13574
|
}
|
|
12901
13575
|
/**
|
|
12902
13576
|
* Collapse-in items appended to the light DOM (however they got there). A bulk
|
|
@@ -12913,7 +13587,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12913
13587
|
fresh.push(node);
|
|
12914
13588
|
}
|
|
12915
13589
|
if (fresh.length === 0) return;
|
|
12916
|
-
const total = this.querySelectorAll(ITEM_SELECTOR$
|
|
13590
|
+
const total = this.querySelectorAll(ITEM_SELECTOR$3).length;
|
|
12917
13591
|
if (fresh.length >= total) return;
|
|
12918
13592
|
for (const el of fresh) animateListItemIn(el);
|
|
12919
13593
|
window.setTimeout(() => this.#scheduleGeometry(), 230);
|
|
@@ -12930,23 +13604,18 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12930
13604
|
this.#resizeObserver = void 0;
|
|
12931
13605
|
this.#mutationObserver?.disconnect();
|
|
12932
13606
|
this.#mutationObserver = void 0;
|
|
12933
|
-
this.#
|
|
12934
|
-
this.#
|
|
13607
|
+
this.#unregisterSearchCommands?.();
|
|
13608
|
+
this.#unregisterSearchCommands = void 0;
|
|
13609
|
+
this.#searchRegistry = void 0;
|
|
12935
13610
|
}
|
|
12936
13611
|
firstUpdated() {
|
|
12937
13612
|
this.#processChildren();
|
|
12938
13613
|
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
13614
|
this.#recomputeGeometry();
|
|
12947
13615
|
}
|
|
12948
13616
|
updated(changed) {
|
|
12949
13617
|
super.updated(changed);
|
|
13618
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
12950
13619
|
if (changed.has(`provider`)) {
|
|
12951
13620
|
this.#builtFromProvider = [];
|
|
12952
13621
|
this.#providerBuilt = 0;
|
|
@@ -13040,8 +13709,14 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13040
13709
|
getItemData(item) {
|
|
13041
13710
|
return this.#itemData.get(item);
|
|
13042
13711
|
}
|
|
13043
|
-
/**
|
|
13044
|
-
|
|
13712
|
+
/**
|
|
13713
|
+
* Dispatch a command to this list — used by `ixfx-grouped-item-lister` to
|
|
13714
|
+
* forward commands. Routes through the command registry (so `list.search.*`
|
|
13715
|
+
* and any other registered commands work).
|
|
13716
|
+
*/
|
|
13717
|
+
dispatch(commandId, args) {
|
|
13718
|
+
this.#searchRegistry?.invoke(commandId, args);
|
|
13719
|
+
}
|
|
13045
13720
|
select(item) {
|
|
13046
13721
|
this.selection.replace(item);
|
|
13047
13722
|
}
|
|
@@ -13089,7 +13764,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13089
13764
|
});
|
|
13090
13765
|
}
|
|
13091
13766
|
#itemForKey(key) {
|
|
13092
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13767
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) if (keyForElement(el) === key) return el;
|
|
13093
13768
|
}
|
|
13094
13769
|
*selectedFilter(kind) {
|
|
13095
13770
|
const wanted = (el) => {
|
|
@@ -13099,7 +13774,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13099
13774
|
case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);
|
|
13100
13775
|
}
|
|
13101
13776
|
};
|
|
13102
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13777
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) {
|
|
13103
13778
|
if (el.hidden) continue;
|
|
13104
13779
|
if (wanted(el)) yield el;
|
|
13105
13780
|
}
|
|
@@ -13112,7 +13787,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13112
13787
|
if (previousKeys.size > 0 || anchorKey !== null) {
|
|
13113
13788
|
const next = [];
|
|
13114
13789
|
let anchor;
|
|
13115
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13790
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) {
|
|
13116
13791
|
const key = keyOf(el);
|
|
13117
13792
|
if (key !== null && previousKeys.has(key)) next.push(el);
|
|
13118
13793
|
if (key !== null && key === anchorKey) anchor = el;
|
|
@@ -13167,15 +13842,15 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13167
13842
|
}
|
|
13168
13843
|
#applyFilterPredicate() {
|
|
13169
13844
|
if (!this.filterPredicate) {
|
|
13170
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13845
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) el.removeAttribute(`hidden`);
|
|
13171
13846
|
return;
|
|
13172
13847
|
}
|
|
13173
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13848
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) if (this.filterPredicate(el)) el.removeAttribute(`hidden`);
|
|
13174
13849
|
else el.setAttribute(`hidden`, ``);
|
|
13175
13850
|
}
|
|
13176
13851
|
#applySort() {
|
|
13177
13852
|
if (this.sort === `none`) return;
|
|
13178
|
-
const rows = [...this.querySelectorAll(ITEM_SELECTOR$
|
|
13853
|
+
const rows = [...this.querySelectorAll(ITEM_SELECTOR$3)];
|
|
13179
13854
|
if (rows.length < 2) return;
|
|
13180
13855
|
const dir = this.sort === `descending` ? -1 : 1;
|
|
13181
13856
|
const meta = (el) => ({
|
|
@@ -13367,7 +14042,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13367
14042
|
else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findLastCheckable());
|
|
13368
14043
|
}
|
|
13369
14044
|
_getVisibleItems() {
|
|
13370
|
-
return Array.from(this.querySelectorAll(ITEM_SELECTOR$
|
|
14045
|
+
return Array.from(this.querySelectorAll(ITEM_SELECTOR$3)).filter((el) => {
|
|
13371
14046
|
if (el.hidden) return false;
|
|
13372
14047
|
if (this.filterPredicate && !this.filterPredicate(el)) return false;
|
|
13373
14048
|
return true;
|
|
@@ -13422,10 +14097,10 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13422
14097
|
}
|
|
13423
14098
|
/** Scan light-DOM children for unprocessed `data-icon` items. */
|
|
13424
14099
|
#processChildren() {
|
|
13425
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
14100
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) this.#ensureIcon(el);
|
|
13426
14101
|
}
|
|
13427
14102
|
_syncItemStates() {
|
|
13428
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
14103
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) {
|
|
13429
14104
|
el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
|
|
13430
14105
|
el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
|
|
13431
14106
|
if (this.interactionMode === `checked`) el.toggleAttribute(`data-checked`, !el.hasAttribute(`not-checked`) && this.selection.selected.has(el));
|
|
@@ -13525,16 +14200,6 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13525
14200
|
composed: true
|
|
13526
14201
|
}));
|
|
13527
14202
|
}
|
|
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
14203
|
_activateItem(item) {
|
|
13539
14204
|
if (this.interactionMode !== `manual`) this.selection.replace(item);
|
|
13540
14205
|
this._dispatch(`list-activate`, { item });
|
|
@@ -13543,6 +14208,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13543
14208
|
this.styles = [
|
|
13544
14209
|
themeFallbacks,
|
|
13545
14210
|
scrollFadeStyles(`x`),
|
|
14211
|
+
listSearchStyles,
|
|
13546
14212
|
i$3`
|
|
13547
14213
|
:host {
|
|
13548
14214
|
display: block;
|
|
@@ -13664,48 +14330,6 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13664
14330
|
.loading[hidden] {
|
|
13665
14331
|
display: none;
|
|
13666
14332
|
}
|
|
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
14333
|
`
|
|
13710
14334
|
];
|
|
13711
14335
|
}
|
|
@@ -13720,21 +14344,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
13720
14344
|
<div class="loading" part="loading" ?hidden=${!loading}>
|
|
13721
14345
|
${loading ? progress !== void 0 ? `${Math.round(progress * 100)}%` : `loading…` : A}
|
|
13722
14346
|
</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>
|
|
14347
|
+
${this.search.renderField()}
|
|
13738
14348
|
</div>
|
|
13739
14349
|
`;
|
|
13740
14350
|
}
|
|
@@ -13774,9 +14384,11 @@ __decorate([property({
|
|
|
13774
14384
|
attribute: `item-wrap`
|
|
13775
14385
|
})], DetailListElement.prototype, "itemWrap", void 0);
|
|
13776
14386
|
__decorate([property({ attribute: false })], DetailListElement.prototype, "checkboxRenderer", void 0);
|
|
13777
|
-
__decorate([
|
|
13778
|
-
|
|
13779
|
-
|
|
14387
|
+
__decorate([property({
|
|
14388
|
+
type: String,
|
|
14389
|
+
attribute: `search-modes`,
|
|
14390
|
+
reflect: true
|
|
14391
|
+
})], DetailListElement.prototype, "searchModes", void 0);
|
|
13780
14392
|
__decorate([query(`ul`)], DetailListElement.prototype, "_listEl", void 0);
|
|
13781
14393
|
DetailListElement = __decorate([safeCustomElement(`ixfx-detail-list`)], DetailListElement);
|
|
13782
14394
|
//#endregion
|
|
@@ -15511,7 +16123,7 @@ SplitLayoutElement = __decorate([safeCustomElement(`ixfx-split-layout`)], SplitL
|
|
|
15511
16123
|
//#endregion
|
|
15512
16124
|
//#region src/tabs/tab-list.ts
|
|
15513
16125
|
/** Pointer movement threshold (px) before a press becomes a drag. */
|
|
15514
|
-
const DRAG_THRESHOLD_PX$
|
|
16126
|
+
const DRAG_THRESHOLD_PX$4 = 5;
|
|
15515
16127
|
let TabListElement = class TabListElement extends i {
|
|
15516
16128
|
constructor(..._args) {
|
|
15517
16129
|
super(..._args);
|
|
@@ -15625,7 +16237,7 @@ let TabListElement = class TabListElement extends i {
|
|
|
15625
16237
|
const dy = event.clientY - state.startY;
|
|
15626
16238
|
const distance = Math.hypot(dx, dy);
|
|
15627
16239
|
if (!state.active) {
|
|
15628
|
-
if (distance < DRAG_THRESHOLD_PX$
|
|
16240
|
+
if (distance < DRAG_THRESHOLD_PX$4) return;
|
|
15629
16241
|
this.#beginDrag(event);
|
|
15630
16242
|
}
|
|
15631
16243
|
state.ghost?.move(event.clientX, event.clientY);
|
|
@@ -16475,12 +17087,23 @@ let TitlebarElement = class TitlebarElement extends i {
|
|
|
16475
17087
|
const showCaret = this.collapsible && this.toggleInteraction !== `disabled`;
|
|
16476
17088
|
const implicitToggle = this.collapsible && this.toggleInteraction === `implicit`;
|
|
16477
17089
|
const caretRotation = isVertical ? this.collapsed ? `` : `-90deg` : this.collapsed ? `-90deg` : ``;
|
|
17090
|
+
const treeVariant = this.variant === `tree`;
|
|
16478
17091
|
return b`
|
|
16479
17092
|
<div
|
|
16480
17093
|
class="bar"
|
|
16481
17094
|
role="toolbar"
|
|
16482
17095
|
@click=${implicitToggle ? this._onToggleClick : A}
|
|
16483
17096
|
>
|
|
17097
|
+
${treeVariant && showCaret ? b`
|
|
17098
|
+
<button
|
|
17099
|
+
class="caret-btn"
|
|
17100
|
+
type="button"
|
|
17101
|
+
aria-label=${this.collapsed ? `Expand panel` : `Collapse panel`}
|
|
17102
|
+
@click=${this._onToggleClick}
|
|
17103
|
+
>
|
|
17104
|
+
<ixfx-icon name=${caretIcon} prominence .rotate=${caretRotation.length > 0 ? caretRotation : A}></ixfx-icon>
|
|
17105
|
+
</button>
|
|
17106
|
+
` : A}
|
|
16484
17107
|
<div class="title-text-wrapper">
|
|
16485
17108
|
${this.iconName ? b`<ixfx-icon class="title-icon" name=${this.iconName}></ixfx-icon>` : A}
|
|
16486
17109
|
<span class="title-text">${this.title}</span>
|
|
@@ -16498,7 +17121,7 @@ let TitlebarElement = class TitlebarElement extends i {
|
|
|
16498
17121
|
<ixfx-icon name="close"></ixfx-icon>
|
|
16499
17122
|
</button>
|
|
16500
17123
|
` : A}
|
|
16501
|
-
${showCaret ? b`
|
|
17124
|
+
${!treeVariant && showCaret ? b`
|
|
16502
17125
|
<button
|
|
16503
17126
|
class="caret-btn"
|
|
16504
17127
|
type="button"
|
|
@@ -16721,6 +17344,77 @@ let TitlebarElement = class TitlebarElement extends i {
|
|
|
16721
17344
|
-1px 0 0 var(--surface-5),
|
|
16722
17345
|
1px 0 0 var(--surface-1);
|
|
16723
17346
|
}
|
|
17347
|
+
|
|
17348
|
+
/* === Tree variant: VS Code explorer / Outline style === */
|
|
17349
|
+
:host([variant="tree"]) .bar {
|
|
17350
|
+
background: transparent;
|
|
17351
|
+
padding: 0 var(--space-xs);
|
|
17352
|
+
min-height: unset;
|
|
17353
|
+
gap: var(--space-xs);
|
|
17354
|
+
}
|
|
17355
|
+
|
|
17356
|
+
:host([variant="tree"][orientation="vertical"]) .bar {
|
|
17357
|
+
padding: var(--space-xs) 0;
|
|
17358
|
+
flex-direction: column;
|
|
17359
|
+
min-width: unset;
|
|
17360
|
+
writing-mode: horizontal-tb;
|
|
17361
|
+
}
|
|
17362
|
+
|
|
17363
|
+
:host([variant="tree"]) .title-text-wrapper {
|
|
17364
|
+
display: inline-flex;
|
|
17365
|
+
align-items: center;
|
|
17366
|
+
gap: var(--space-xs);
|
|
17367
|
+
padding: 0;
|
|
17368
|
+
background: transparent;
|
|
17369
|
+
}
|
|
17370
|
+
|
|
17371
|
+
:host([variant="tree"]) .title-icon {
|
|
17372
|
+
background: transparent;
|
|
17373
|
+
flex-shrink: 0;
|
|
17374
|
+
}
|
|
17375
|
+
|
|
17376
|
+
:host([variant="tree"]) .title-text {
|
|
17377
|
+
font-weight: normal;
|
|
17378
|
+
color: var(--surface-3-text);
|
|
17379
|
+
padding: 0;
|
|
17380
|
+
background: transparent;
|
|
17381
|
+
box-shadow: none;
|
|
17382
|
+
flex: unset;
|
|
17383
|
+
width: auto;
|
|
17384
|
+
white-space: nowrap;
|
|
17385
|
+
}
|
|
17386
|
+
|
|
17387
|
+
:host([variant="tree"][orientation="vertical"]) .title-text {
|
|
17388
|
+
writing-mode: vertical-rl;
|
|
17389
|
+
transform: rotate(180deg);
|
|
17390
|
+
padding: 0;
|
|
17391
|
+
}
|
|
17392
|
+
|
|
17393
|
+
:host([variant="tree"]) .caret-btn {
|
|
17394
|
+
background: transparent;
|
|
17395
|
+
opacity: var(--prominence-idle-opacity);
|
|
17396
|
+
padding: 0;
|
|
17397
|
+
}
|
|
17398
|
+
|
|
17399
|
+
:host([variant="tree"]) .caret-btn:hover {
|
|
17400
|
+
background: transparent;
|
|
17401
|
+
opacity: var(--prominence-tickled-opacity);
|
|
17402
|
+
}
|
|
17403
|
+
|
|
17404
|
+
:host([variant="tree"]) .toolbar {
|
|
17405
|
+
margin-left: auto;
|
|
17406
|
+
}
|
|
17407
|
+
|
|
17408
|
+
:host([variant="tree"][orientation="vertical"]) .toolbar {
|
|
17409
|
+
flex-direction: row;
|
|
17410
|
+
margin-left: 0;
|
|
17411
|
+
margin-top: auto;
|
|
17412
|
+
}
|
|
17413
|
+
|
|
17414
|
+
:host([variant="tree"]) ixfx-icon {
|
|
17415
|
+
width: 1em;
|
|
17416
|
+
height: 1em;
|
|
17417
|
+
}
|
|
16724
17418
|
`];
|
|
16725
17419
|
}
|
|
16726
17420
|
};
|
|
@@ -16756,7 +17450,7 @@ TitlebarElement = __decorate([safeCustomElement(`ixfx-titlebar`)], TitlebarEleme
|
|
|
16756
17450
|
//#endregion
|
|
16757
17451
|
//#region src/dock/dock-panel.ts
|
|
16758
17452
|
/** Pointer movement threshold before a titlebar press becomes a dock drag. */
|
|
16759
|
-
const DRAG_THRESHOLD_PX$
|
|
17453
|
+
const DRAG_THRESHOLD_PX$3 = 5;
|
|
16760
17454
|
let DockPanelElement = class DockPanelElement extends i {
|
|
16761
17455
|
constructor(..._args) {
|
|
16762
17456
|
super(..._args);
|
|
@@ -16788,7 +17482,7 @@ let DockPanelElement = class DockPanelElement extends i {
|
|
|
16788
17482
|
this._onPointerMove = (event) => {
|
|
16789
17483
|
if (this._dragPointerId === null || event.pointerId !== this._dragPointerId) return;
|
|
16790
17484
|
if (!this._dragStarted) {
|
|
16791
|
-
if (Math.hypot(event.clientX - this._dragStartX, event.clientY - this._dragStartY) < DRAG_THRESHOLD_PX$
|
|
17485
|
+
if (Math.hypot(event.clientX - this._dragStartX, event.clientY - this._dragStartY) < DRAG_THRESHOLD_PX$3) return;
|
|
16792
17486
|
this._dragStarted = true;
|
|
16793
17487
|
const pointerId = this._dragPointerId;
|
|
16794
17488
|
this._teardownDragListeners();
|
|
@@ -16943,7 +17637,7 @@ DockPanelElement = __decorate([safeCustomElement(`ixfx-dock-panel`)], DockPanelE
|
|
|
16943
17637
|
/** Fraction of the leaf rect (from each edge) that forms the split bands. */
|
|
16944
17638
|
const BAND = .25;
|
|
16945
17639
|
/** Pointer movement threshold before a press becomes a dock drag (matches tab-list). */
|
|
16946
|
-
const DRAG_THRESHOLD_PX$
|
|
17640
|
+
const DRAG_THRESHOLD_PX$2 = 5;
|
|
16947
17641
|
/** Tolerance (px) when matching a child gutter endpoint to a parent gutter line. */
|
|
16948
17642
|
const JUNCTION_TOL_PX = 6;
|
|
16949
17643
|
/** Junction points closer than this merge into one handle (a 4-way cross yields one per side). */
|
|
@@ -17058,7 +17752,7 @@ let DockHostElement = class DockHostElement extends i {
|
|
|
17058
17752
|
this._onTabDragMove = (event) => {
|
|
17059
17753
|
const pending = this._tabDragPending;
|
|
17060
17754
|
if (pending === null || event.pointerId !== pending.pointerId) return;
|
|
17061
|
-
if (Math.hypot(event.clientX - pending.startX, event.clientY - pending.startY) < DRAG_THRESHOLD_PX$
|
|
17755
|
+
if (Math.hypot(event.clientX - pending.startX, event.clientY - pending.startY) < DRAG_THRESHOLD_PX$2) return;
|
|
17062
17756
|
this._tabDragPending = null;
|
|
17063
17757
|
this._teardownTabDragListeners();
|
|
17064
17758
|
this._suppressNextClick();
|
|
@@ -18758,7 +19452,7 @@ function WheelNudgeMixin(superClass) {
|
|
|
18758
19452
|
}
|
|
18759
19453
|
//#endregion
|
|
18760
19454
|
//#region src/editable-label/editable-number.ts
|
|
18761
|
-
const DRAG_THRESHOLD_PX = 3;
|
|
19455
|
+
const DRAG_THRESHOLD_PX$1 = 3;
|
|
18762
19456
|
let EditableNumber = class EditableNumber extends WheelNudgeMixin(EditableLabel) {
|
|
18763
19457
|
#dragStartY;
|
|
18764
19458
|
#dragStartValue;
|
|
@@ -18875,7 +19569,7 @@ let EditableNumber = class EditableNumber extends WheelNudgeMixin(EditableLabel)
|
|
|
18875
19569
|
if (this._editing) return;
|
|
18876
19570
|
if (this.disabled || this.noDrag) return;
|
|
18877
19571
|
const deltaY = this.#dragStartY - e.clientY;
|
|
18878
|
-
if (!this.#dragging && Math.abs(deltaY) < DRAG_THRESHOLD_PX) return;
|
|
19572
|
+
if (!this.#dragging && Math.abs(deltaY) < DRAG_THRESHOLD_PX$1) return;
|
|
18879
19573
|
this.#dragging = true;
|
|
18880
19574
|
this.#hasDragged = true;
|
|
18881
19575
|
this.toggleAttribute(`dragging`, true);
|
|
@@ -19484,6 +20178,226 @@ let FormElement = class FormElement extends i {
|
|
|
19484
20178
|
};
|
|
19485
20179
|
FormElement = __decorate([safeCustomElement(`ixfx-form`)], FormElement);
|
|
19486
20180
|
//#endregion
|
|
20181
|
+
//#region src/grid-list/grid-list-source.ts
|
|
20182
|
+
const ITEM_SELECTOR$2 = `:scope > *`;
|
|
20183
|
+
const SYNTHETIC_PREFIX = `gl-auto-`;
|
|
20184
|
+
/** Whether `key` is an auto-assigned key for a keyless light-DOM item. */
|
|
20185
|
+
function isSyntheticKey(key) {
|
|
20186
|
+
return key.startsWith(SYNTHETIC_PREFIX);
|
|
20187
|
+
}
|
|
20188
|
+
function warnDuplicateKeys(keys) {
|
|
20189
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20190
|
+
const dupes = /* @__PURE__ */ new Set();
|
|
20191
|
+
for (const k of keys) {
|
|
20192
|
+
if (isSyntheticKey(k)) continue;
|
|
20193
|
+
if (seen.has(k)) dupes.add(k);
|
|
20194
|
+
else seen.add(k);
|
|
20195
|
+
}
|
|
20196
|
+
if (dupes.size > 0) console.warn(`ixfx-grid-list: duplicate item key(s) ${[...dupes].map((k) => `"${k}"`).join(`, `)} — the first occurrence wins for key-based operations.`);
|
|
20197
|
+
}
|
|
20198
|
+
var ElementGridListSource = class {
|
|
20199
|
+
#host;
|
|
20200
|
+
#items = [];
|
|
20201
|
+
#slotNames = /* @__PURE__ */ new WeakMap();
|
|
20202
|
+
#syntheticKeys = /* @__PURE__ */ new WeakMap();
|
|
20203
|
+
#seq = 0;
|
|
20204
|
+
#predicate;
|
|
20205
|
+
#searchKeys;
|
|
20206
|
+
#effectiveIndices = [];
|
|
20207
|
+
#keysCache;
|
|
20208
|
+
constructor(host) {
|
|
20209
|
+
this.#host = host;
|
|
20210
|
+
this.refresh();
|
|
20211
|
+
}
|
|
20212
|
+
get count() {
|
|
20213
|
+
return this.#effectiveIndices.length;
|
|
20214
|
+
}
|
|
20215
|
+
get rawCount() {
|
|
20216
|
+
return this.#items.length;
|
|
20217
|
+
}
|
|
20218
|
+
keyAt(index) {
|
|
20219
|
+
const eff = this.#effectiveIndices[index];
|
|
20220
|
+
return eff === void 0 ? `` : this.#itemKey(this.#items[eff]);
|
|
20221
|
+
}
|
|
20222
|
+
indexOfKey(key) {
|
|
20223
|
+
return this.#effectiveIndices.findIndex((i) => this.#itemKey(this.#items[i]) === key);
|
|
20224
|
+
}
|
|
20225
|
+
keys() {
|
|
20226
|
+
this.#keysCache ??= this.#effectiveIndices.map((i) => this.#itemKey(this.#items[i]));
|
|
20227
|
+
return this.#keysCache;
|
|
20228
|
+
}
|
|
20229
|
+
labelAt(index) {
|
|
20230
|
+
const eff = this.#effectiveIndices[index];
|
|
20231
|
+
return eff === void 0 ? `` : this.#captionFor(this.#items[eff]);
|
|
20232
|
+
}
|
|
20233
|
+
rawKeyAt(index) {
|
|
20234
|
+
const el = this.#items[index];
|
|
20235
|
+
return el === void 0 ? `` : this.#itemKey(el);
|
|
20236
|
+
}
|
|
20237
|
+
rawSearchTextAt(index) {
|
|
20238
|
+
const el = this.#items[index];
|
|
20239
|
+
if (el === void 0) return ``;
|
|
20240
|
+
return el.getAttribute(`data-search-label`) ?? el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.textContent?.trim() ?? ``;
|
|
20241
|
+
}
|
|
20242
|
+
isSelectable(index) {
|
|
20243
|
+
const eff = this.#effectiveIndices[index];
|
|
20244
|
+
return eff !== void 0 && !this.#items[eff].hasAttribute(`not-checked`);
|
|
20245
|
+
}
|
|
20246
|
+
renderCellContent(index) {
|
|
20247
|
+
const eff = this.#effectiveIndices[index];
|
|
20248
|
+
if (eff === void 0) return void 0;
|
|
20249
|
+
return b`<slot name=${this.slotNameFor(this.#items[eff])}></slot>`;
|
|
20250
|
+
}
|
|
20251
|
+
setFilters(opts) {
|
|
20252
|
+
if (`predicate` in opts) this.#predicate = opts.predicate;
|
|
20253
|
+
if (`searchKeys` in opts) this.#searchKeys = opts.searchKeys;
|
|
20254
|
+
this.#recomputeEffective();
|
|
20255
|
+
}
|
|
20256
|
+
refresh() {
|
|
20257
|
+
this.#items = Array.from(this.#host.querySelectorAll(ITEM_SELECTOR$2)).filter((el) => !el.hidden);
|
|
20258
|
+
for (const el of this.#items) {
|
|
20259
|
+
const name = this.slotNameFor(el);
|
|
20260
|
+
if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
|
|
20261
|
+
}
|
|
20262
|
+
warnDuplicateKeys(this.#items.map((el) => this.#itemKey(el)));
|
|
20263
|
+
this.#recomputeEffective();
|
|
20264
|
+
}
|
|
20265
|
+
/** Effective-order elements — the array `GridListElement` renders and slices. */
|
|
20266
|
+
effectiveElements() {
|
|
20267
|
+
return this.#effectiveIndices.map((i) => this.#items[i]);
|
|
20268
|
+
}
|
|
20269
|
+
/** Raw-order element at `index`, or `undefined`. */
|
|
20270
|
+
rawElementAt(index) {
|
|
20271
|
+
return this.#items[index];
|
|
20272
|
+
}
|
|
20273
|
+
/** Stable per-element `<slot>` name. */
|
|
20274
|
+
slotNameFor(el) {
|
|
20275
|
+
let name = this.#slotNames.get(el);
|
|
20276
|
+
if (name === void 0) {
|
|
20277
|
+
this.#seq += 1;
|
|
20278
|
+
name = `gl-item-${this.#seq}`;
|
|
20279
|
+
this.#slotNames.set(el, name);
|
|
20280
|
+
}
|
|
20281
|
+
return name;
|
|
20282
|
+
}
|
|
20283
|
+
resolveKeyToElement(key) {
|
|
20284
|
+
if (isSyntheticKey(key)) return this.#items.find((el) => this.#syntheticKeys.get(el) === key);
|
|
20285
|
+
return this.#items.find((el) => keyForElement(el) === key);
|
|
20286
|
+
}
|
|
20287
|
+
resolveKeyToData(_key) {}
|
|
20288
|
+
#recomputeEffective() {
|
|
20289
|
+
const effective = [];
|
|
20290
|
+
for (let i = 0; i < this.#items.length; i++) {
|
|
20291
|
+
if (this.#predicate !== void 0 && !this.#predicate(i)) continue;
|
|
20292
|
+
if (this.#searchKeys !== void 0) {
|
|
20293
|
+
const key = this.#itemKey(this.#items[i]);
|
|
20294
|
+
if (!isSyntheticKey(key) && !this.#searchKeys.has(key)) continue;
|
|
20295
|
+
}
|
|
20296
|
+
effective.push(i);
|
|
20297
|
+
}
|
|
20298
|
+
this.#effectiveIndices = effective;
|
|
20299
|
+
this.#keysCache = void 0;
|
|
20300
|
+
}
|
|
20301
|
+
#itemKey(el) {
|
|
20302
|
+
const userKey = keyForElement(el);
|
|
20303
|
+
if (userKey !== void 0) return userKey;
|
|
20304
|
+
let synthetic = this.#syntheticKeys.get(el);
|
|
20305
|
+
if (synthetic === void 0) {
|
|
20306
|
+
this.#seq += 1;
|
|
20307
|
+
synthetic = `${SYNTHETIC_PREFIX}${this.#seq}`;
|
|
20308
|
+
this.#syntheticKeys.set(el, synthetic);
|
|
20309
|
+
}
|
|
20310
|
+
return synthetic;
|
|
20311
|
+
}
|
|
20312
|
+
#captionFor(el) {
|
|
20313
|
+
return el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? ``;
|
|
20314
|
+
}
|
|
20315
|
+
};
|
|
20316
|
+
var DataGridListSource = class {
|
|
20317
|
+
#items;
|
|
20318
|
+
#adapter;
|
|
20319
|
+
#predicate;
|
|
20320
|
+
#searchKeys;
|
|
20321
|
+
#effectiveIndices = [];
|
|
20322
|
+
#keysCache;
|
|
20323
|
+
constructor(items, adapter) {
|
|
20324
|
+
this.#items = items;
|
|
20325
|
+
this.#adapter = adapter;
|
|
20326
|
+
warnDuplicateKeys(items.map((it, i) => adapter.key(it, i)));
|
|
20327
|
+
this.#recomputeEffective();
|
|
20328
|
+
}
|
|
20329
|
+
get count() {
|
|
20330
|
+
return this.#effectiveIndices.length;
|
|
20331
|
+
}
|
|
20332
|
+
get rawCount() {
|
|
20333
|
+
return this.#items.length;
|
|
20334
|
+
}
|
|
20335
|
+
keyAt(index) {
|
|
20336
|
+
const eff = this.#effectiveIndices[index];
|
|
20337
|
+
return eff === void 0 ? `` : this.#adapter.key(this.#items[eff], eff);
|
|
20338
|
+
}
|
|
20339
|
+
indexOfKey(key) {
|
|
20340
|
+
return this.#effectiveIndices.findIndex((i) => this.#adapter.key(this.#items[i], i) === key);
|
|
20341
|
+
}
|
|
20342
|
+
keys() {
|
|
20343
|
+
this.#keysCache ??= this.#effectiveIndices.map((i) => this.#adapter.key(this.#items[i], i));
|
|
20344
|
+
return this.#keysCache;
|
|
20345
|
+
}
|
|
20346
|
+
labelAt(index) {
|
|
20347
|
+
const eff = this.#effectiveIndices[index];
|
|
20348
|
+
if (eff === void 0) return ``;
|
|
20349
|
+
return this.#adapter.label?.(this.#items[eff], eff) ?? this.#adapter.key(this.#items[eff], eff);
|
|
20350
|
+
}
|
|
20351
|
+
rawKeyAt(index) {
|
|
20352
|
+
return index < 0 || index >= this.#items.length ? `` : this.#adapter.key(this.#items[index], index);
|
|
20353
|
+
}
|
|
20354
|
+
rawSearchTextAt(index) {
|
|
20355
|
+
if (index < 0 || index >= this.#items.length) return ``;
|
|
20356
|
+
const item = this.#items[index];
|
|
20357
|
+
return this.#adapter.searchableText?.(item, index) ?? this.#adapter.label?.(item, index) ?? ``;
|
|
20358
|
+
}
|
|
20359
|
+
isSelectable(index) {
|
|
20360
|
+
const eff = this.#effectiveIndices[index];
|
|
20361
|
+
return eff !== void 0 && !this.#adapter.disabled?.(this.#items[eff], eff);
|
|
20362
|
+
}
|
|
20363
|
+
renderCellContent(index) {
|
|
20364
|
+
const eff = this.#effectiveIndices[index];
|
|
20365
|
+
return eff === void 0 ? void 0 : this.#adapter.render(this.#items[eff], eff);
|
|
20366
|
+
}
|
|
20367
|
+
setFilters(opts) {
|
|
20368
|
+
if (`predicate` in opts) this.#predicate = opts.predicate;
|
|
20369
|
+
if (`searchKeys` in opts) this.#searchKeys = opts.searchKeys;
|
|
20370
|
+
this.#recomputeEffective();
|
|
20371
|
+
}
|
|
20372
|
+
refresh() {
|
|
20373
|
+
this.#recomputeEffective();
|
|
20374
|
+
}
|
|
20375
|
+
/** Raw-order item at `index`, or `undefined`. */
|
|
20376
|
+
rawItemAt(index) {
|
|
20377
|
+
return this.#items[index];
|
|
20378
|
+
}
|
|
20379
|
+
/** Effective-order item at `index`, or `undefined`. */
|
|
20380
|
+
getItemAt(index) {
|
|
20381
|
+
const eff = this.#effectiveIndices[index];
|
|
20382
|
+
return eff === void 0 ? void 0 : this.#items[eff];
|
|
20383
|
+
}
|
|
20384
|
+
resolveKeyToElement(_key) {}
|
|
20385
|
+
resolveKeyToData(key) {
|
|
20386
|
+
const idx = this.#items.findIndex((item, i) => this.#adapter.key(item, i) === key);
|
|
20387
|
+
return idx >= 0 ? this.#items[idx] : void 0;
|
|
20388
|
+
}
|
|
20389
|
+
#recomputeEffective() {
|
|
20390
|
+
const effective = [];
|
|
20391
|
+
for (let i = 0; i < this.#items.length; i++) {
|
|
20392
|
+
if (this.#predicate !== void 0 && !this.#predicate(i)) continue;
|
|
20393
|
+
if (this.#searchKeys !== void 0 && !this.#searchKeys.has(this.#adapter.key(this.#items[i], i))) continue;
|
|
20394
|
+
effective.push(i);
|
|
20395
|
+
}
|
|
20396
|
+
this.#effectiveIndices = effective;
|
|
20397
|
+
this.#keysCache = void 0;
|
|
20398
|
+
}
|
|
20399
|
+
};
|
|
20400
|
+
//#endregion
|
|
19487
20401
|
//#region src/grid-list/grid-list.ts
|
|
19488
20402
|
const ITEM_SELECTOR$1 = `:scope > *`;
|
|
19489
20403
|
const MIN_ITEM_SIZE = 16;
|
|
@@ -19578,6 +20492,7 @@ let GridListElement = class GridListElement extends i {
|
|
|
19578
20492
|
this.captionBackdrop = `scrim`;
|
|
19579
20493
|
this.captionHeight = 24;
|
|
19580
20494
|
this.itemWrap = `stop`;
|
|
20495
|
+
this.items = [];
|
|
19581
20496
|
this._scrollTop = 0;
|
|
19582
20497
|
this._scrollLeft = 0;
|
|
19583
20498
|
this._viewportHeight = 0;
|
|
@@ -19588,37 +20503,37 @@ let GridListElement = class GridListElement extends i {
|
|
|
19588
20503
|
this.#itemWrap = `stop`;
|
|
19589
20504
|
this.#entering = /* @__PURE__ */ new Set();
|
|
19590
20505
|
this.#reflowTimer = 0;
|
|
19591
|
-
this.#
|
|
19592
|
-
this.#
|
|
20506
|
+
this.#sourceElement = new ElementGridListSource(this);
|
|
20507
|
+
this.#source = this.#sourceElement;
|
|
19593
20508
|
this.selection = new ListSelectionController({
|
|
19594
|
-
getOrderedItems: () => this.
|
|
19595
|
-
isSelectable: () =>
|
|
20509
|
+
getOrderedItems: () => this.#source.keys(),
|
|
20510
|
+
isSelectable: (k) => {
|
|
20511
|
+
const i = this.#source.indexOfKey(k);
|
|
20512
|
+
return i >= 0 && this.#source.isSelectable(i);
|
|
20513
|
+
},
|
|
19596
20514
|
getModes: () => ({
|
|
19597
20515
|
selectionMode: this.selectionMode,
|
|
19598
20516
|
interactionMode: this.interactionMode
|
|
19599
20517
|
}),
|
|
19600
|
-
getKey:
|
|
20518
|
+
getKey: (k) => k,
|
|
19601
20519
|
onChange: (selected, previous) => {
|
|
19602
20520
|
this._syncItemStates();
|
|
19603
20521
|
this.requestUpdate();
|
|
19604
|
-
this
|
|
19605
|
-
selected,
|
|
19606
|
-
previous,
|
|
19607
|
-
selectedKeys: this.selection.selectedKeys
|
|
19608
|
-
});
|
|
20522
|
+
this.#emitSelect(selected, previous);
|
|
19609
20523
|
}
|
|
19610
20524
|
});
|
|
19611
20525
|
this.tickled = new TickledController(this, {
|
|
19612
|
-
getItems: () => this.#
|
|
19613
|
-
id:
|
|
19614
|
-
enabled:
|
|
20526
|
+
getItems: () => this.#source.keys().map((k, i) => ({
|
|
20527
|
+
id: k,
|
|
20528
|
+
enabled: this.#source.isSelectable(i)
|
|
19615
20529
|
})),
|
|
19616
|
-
onTickle: (
|
|
19617
|
-
if (
|
|
20530
|
+
onTickle: (k) => {
|
|
20531
|
+
if (k !== void 0) this.#emitItemEvent(`list-tickle`, k);
|
|
19618
20532
|
},
|
|
19619
20533
|
onStateChange: () => this._syncItemStates(),
|
|
19620
20534
|
itemWrap: this.#itemWrap
|
|
19621
20535
|
});
|
|
20536
|
+
this.#selectedSnapshot = /* @__PURE__ */ new Set();
|
|
19622
20537
|
this.#onScroll = (e) => {
|
|
19623
20538
|
const el = e.target;
|
|
19624
20539
|
this.#cancelScrollRaf();
|
|
@@ -19638,14 +20553,17 @@ let GridListElement = class GridListElement extends i {
|
|
|
19638
20553
|
}
|
|
19639
20554
|
return;
|
|
19640
20555
|
}
|
|
19641
|
-
const count = this.#
|
|
20556
|
+
const count = this.#source.count;
|
|
19642
20557
|
if (count === 0) return;
|
|
19643
20558
|
const win = this.#currentWindow();
|
|
19644
20559
|
const move = (delta) => {
|
|
19645
|
-
const from = this.tickled.tickledId ? this.#
|
|
20560
|
+
const from = this.tickled.tickledId ? this.#source.indexOfKey(this.tickled.tickledId) : -1;
|
|
19646
20561
|
const nextIndex = Math.max(0, Math.min(count - 1, from + delta));
|
|
19647
|
-
const
|
|
19648
|
-
if (
|
|
20562
|
+
const nextKey = this.#source.keyAt(nextIndex);
|
|
20563
|
+
if (nextKey) {
|
|
20564
|
+
this.tickled.handleKeyboardNavigation(nextKey);
|
|
20565
|
+
this.#scrollItemIntoView(nextKey);
|
|
20566
|
+
}
|
|
19649
20567
|
};
|
|
19650
20568
|
const perLine = this.orientation === `grid` ? win.cols : 1;
|
|
19651
20569
|
switch (e.key) {
|
|
@@ -19663,9 +20581,11 @@ let GridListElement = class GridListElement extends i {
|
|
|
19663
20581
|
break;
|
|
19664
20582
|
case `Home`:
|
|
19665
20583
|
this.tickled.handleKeyboardNavigation(`first`);
|
|
20584
|
+
if (this.tickled.tickledId) this.#scrollItemIntoView(this.tickled.tickledId);
|
|
19666
20585
|
break;
|
|
19667
20586
|
case `End`:
|
|
19668
20587
|
this.tickled.handleKeyboardNavigation(`last`);
|
|
20588
|
+
if (this.tickled.tickledId) this.#scrollItemIntoView(this.tickled.tickledId);
|
|
19669
20589
|
break;
|
|
19670
20590
|
case `PageUp`:
|
|
19671
20591
|
case `PageDown`: {
|
|
@@ -19679,7 +20599,7 @@ let GridListElement = class GridListElement extends i {
|
|
|
19679
20599
|
const id = this.tickled.tickledId;
|
|
19680
20600
|
if (id) {
|
|
19681
20601
|
e.preventDefault();
|
|
19682
|
-
this
|
|
20602
|
+
this.#emitItemEvent(`list-item-click`, id);
|
|
19683
20603
|
this.#onCellActivate(id);
|
|
19684
20604
|
}
|
|
19685
20605
|
break;
|
|
@@ -19708,11 +20628,13 @@ let GridListElement = class GridListElement extends i {
|
|
|
19708
20628
|
scrollFadeStyles(`x`),
|
|
19709
20629
|
scrollFadeStyles(`y`),
|
|
19710
20630
|
listItemFadeKeyframes,
|
|
20631
|
+
listSearchStyles,
|
|
19711
20632
|
i$3`
|
|
19712
20633
|
:host {
|
|
19713
20634
|
--grid-list-item-bg: var(--item-bg);
|
|
19714
20635
|
--grid-list-radius: var(--radius-m);
|
|
19715
20636
|
--grid-list-gap: var(--space-xs);
|
|
20637
|
+
position: relative;
|
|
19716
20638
|
display: block;
|
|
19717
20639
|
height: 100%;
|
|
19718
20640
|
min-height: 2em;
|
|
@@ -19732,6 +20654,12 @@ let GridListElement = class GridListElement extends i {
|
|
|
19732
20654
|
height: 100%;
|
|
19733
20655
|
overflow-y: auto;
|
|
19734
20656
|
overflow-x: hidden;
|
|
20657
|
+
/* Consumer control over the native scrollbar (both orientations): set
|
|
20658
|
+
--grid-list-scrollbar-width to "none" to hide it or "thin" to slim it.
|
|
20659
|
+
The container is also exposed as ::part(viewport). */
|
|
20660
|
+
scrollbar-width: var(--grid-list-scrollbar-width, auto);
|
|
20661
|
+
scrollbar-gutter: var(--grid-list-scrollbar-gutter, auto);
|
|
20662
|
+
scrollbar-color: var(--grid-list-scrollbar-color, auto);
|
|
19735
20663
|
}
|
|
19736
20664
|
:host([orientation="row"]) .viewport {
|
|
19737
20665
|
overflow-x: auto;
|
|
@@ -19825,16 +20753,19 @@ let GridListElement = class GridListElement extends i {
|
|
|
19825
20753
|
overflow: hidden;
|
|
19826
20754
|
transition: background var(--transition), border-color var(--transition);
|
|
19827
20755
|
}
|
|
19828
|
-
:host(:not([caption-style="separate"])) .cell:hover
|
|
20756
|
+
:host(:not([caption-style="separate"])) .cell:hover,
|
|
20757
|
+
:host(:not([caption-style="separate"])) .cell.tickled {
|
|
19829
20758
|
background: var(--item-bg-tickled);
|
|
19830
|
-
border
|
|
20759
|
+
border: var(--item-border-tickled);
|
|
19831
20760
|
}
|
|
19832
20761
|
:host(:not([caption-style="separate"])) .cell.selected {
|
|
19833
20762
|
outline: 2px solid var(--accent);
|
|
19834
20763
|
outline-offset: -2px;
|
|
19835
20764
|
}
|
|
19836
|
-
:host(:not([caption-style="separate"])) .cell.tickled {
|
|
19837
|
-
|
|
20765
|
+
:host(:not([caption-style="separate"])) .cell.selected.tickled {
|
|
20766
|
+
background: var(--item-bg-selected-tickled);
|
|
20767
|
+
outline: 2px dashed var(--surface-3);
|
|
20768
|
+
outline-offset: -2px;
|
|
19838
20769
|
}
|
|
19839
20770
|
|
|
19840
20771
|
/* under: caption is a strip inside the box, below the content */
|
|
@@ -19893,9 +20824,10 @@ let GridListElement = class GridListElement extends i {
|
|
|
19893
20824
|
background: var(--grid-list-item-bg);
|
|
19894
20825
|
border: 1px solid transparent;
|
|
19895
20826
|
}
|
|
19896
|
-
:host([caption-style="separate"]) .cell:hover .thumb
|
|
20827
|
+
:host([caption-style="separate"]) .cell:hover .thumb,
|
|
20828
|
+
:host([caption-style="separate"]) .cell.tickled .thumb {
|
|
19897
20829
|
background: var(--item-bg-tickled);
|
|
19898
|
-
border
|
|
20830
|
+
border: var(--item-border-tickled);
|
|
19899
20831
|
}
|
|
19900
20832
|
:host([caption-style="separate"]) .caption {
|
|
19901
20833
|
display: flex;
|
|
@@ -19907,20 +20839,27 @@ let GridListElement = class GridListElement extends i {
|
|
|
19907
20839
|
}
|
|
19908
20840
|
:host([caption-style="separate"][caption-align="left"]) .caption { align-self: flex-start; }
|
|
19909
20841
|
: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 {
|
|
20842
|
+
:host([caption-style="separate"]) .cell.selected .thumb {
|
|
19912
20843
|
outline: 2px solid var(--accent);
|
|
19913
20844
|
outline-offset: 2px;
|
|
19914
20845
|
}
|
|
19915
|
-
:host([caption-style="separate"]) .cell.tickled .thumb {
|
|
19916
|
-
|
|
20846
|
+
:host([caption-style="separate"]) .cell.selected.tickled .thumb {
|
|
20847
|
+
background: var(--item-bg-selected-tickled);
|
|
20848
|
+
outline: 2px dashed var(--surface-3);
|
|
20849
|
+
outline-offset: 2px;
|
|
19917
20850
|
}
|
|
19918
20851
|
:host([caption-style="separate"]) .cell.selected .caption {
|
|
19919
20852
|
background: var(--accent);
|
|
19920
20853
|
color: var(--accent-text, #000);
|
|
19921
20854
|
}
|
|
19922
20855
|
:host([caption-style="separate"]) .cell.tickled .caption {
|
|
19923
|
-
|
|
20856
|
+
background: var(--item-bg-tickled);
|
|
20857
|
+
outline: var(--item-border-tickled);
|
|
20858
|
+
}
|
|
20859
|
+
:host([caption-style="separate"]) .cell.selected.tickled .caption {
|
|
20860
|
+
background: var(--item-bg-selected-tickled);
|
|
20861
|
+
outline: 2px dashed var(--surface-3);
|
|
20862
|
+
outline-offset: -2px;
|
|
19924
20863
|
}
|
|
19925
20864
|
|
|
19926
20865
|
/* ---- Insertion / removal animation ----
|
|
@@ -19966,16 +20905,71 @@ let GridListElement = class GridListElement extends i {
|
|
|
19966
20905
|
*/
|
|
19967
20906
|
#zoomAnchor;
|
|
19968
20907
|
/**
|
|
19969
|
-
*
|
|
19970
|
-
*
|
|
19971
|
-
*
|
|
19972
|
-
*
|
|
20908
|
+
* The active view over the items. Element mode projects light-DOM children
|
|
20909
|
+
* through named `<slot>`s (owned by {@link ElementGridListSource}); data mode
|
|
20910
|
+
* renders from {@link items} via {@link itemAdapter}. Everything else here
|
|
20911
|
+
* addresses items by effective index + key and never branches on the mode.
|
|
20912
|
+
*
|
|
20913
|
+
* Initialised eagerly (element mode) so the public getters — `count`,
|
|
20914
|
+
* `selectedKeys`, … — are safe to touch before the first Lit update. The
|
|
20915
|
+
* first `willUpdate` rebuilds it once the light DOM / `itemAdapter` is known.
|
|
19973
20916
|
*/
|
|
19974
|
-
#
|
|
19975
|
-
#
|
|
20917
|
+
#sourceElement;
|
|
20918
|
+
#source;
|
|
20919
|
+
#searchMode;
|
|
20920
|
+
#searchFilterKeys;
|
|
20921
|
+
/** (Re)build {@link search} for the current mode; a no-op if it already matches. */
|
|
20922
|
+
#ensureSearch() {
|
|
20923
|
+
const mode = this.itemAdapter !== void 0 ? `data` : `element`;
|
|
20924
|
+
if (this.#searchMode === mode && this.search !== void 0) return;
|
|
20925
|
+
const previous = this.search;
|
|
20926
|
+
if (previous) {
|
|
20927
|
+
previous.hostDisconnected();
|
|
20928
|
+
this.removeController(previous);
|
|
20929
|
+
}
|
|
20930
|
+
this.search = mode === `data` ? new ListSearchController(this, { adapter: createDataListSearchAdapter(this, {
|
|
20931
|
+
rawCount: () => this.#source?.rawCount ?? 0,
|
|
20932
|
+
rawKeyAt: (i) => this.#source?.rawKeyAt(i) ?? ``,
|
|
20933
|
+
rawSearchTextAt: (i) => this.#source?.rawSearchTextAt(i) ?? ``,
|
|
20934
|
+
onFilterChange: (keys) => {
|
|
20935
|
+
this.#searchFilterKeys = keys;
|
|
20936
|
+
this.#source?.setFilters({ searchKeys: keys });
|
|
20937
|
+
this.requestUpdate();
|
|
20938
|
+
this.updateComplete.then(() => this.#emitViewportChange());
|
|
20939
|
+
},
|
|
20940
|
+
onActivate: (key) => this.#onCellActivate(key),
|
|
20941
|
+
getHighlightRoot: () => this.renderRoot
|
|
20942
|
+
}) }) : new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
|
|
20943
|
+
itemSelector: ITEM_SELECTOR$1,
|
|
20944
|
+
getLabel: (el) => el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
20945
|
+
highlightKey: `grid-list-search`
|
|
20946
|
+
}) });
|
|
20947
|
+
this.#searchMode = mode;
|
|
20948
|
+
}
|
|
20949
|
+
#searchRegistry;
|
|
20950
|
+
#unregisterSearchCommands;
|
|
20951
|
+
/** Command registry carrying this grid's `list.search.*` commands. */
|
|
20952
|
+
get registry() {
|
|
20953
|
+
return this.#searchRegistry;
|
|
20954
|
+
}
|
|
20955
|
+
/** Effective search modes: this component's capability (all three) ∩ the `search-modes` attribute. */
|
|
20956
|
+
#effectiveSearchModes() {
|
|
20957
|
+
const raw = this.searchModes?.trim();
|
|
20958
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
20959
|
+
const requested = new Set(raw.split(/\s+/));
|
|
20960
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
20961
|
+
}
|
|
20962
|
+
#syncSearchCommands() {
|
|
20963
|
+
if (!this.#searchRegistry || !this.search) return;
|
|
20964
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
20965
|
+
this.#unregisterSearchCommands?.();
|
|
20966
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
20967
|
+
}
|
|
19976
20968
|
connectedCallback() {
|
|
19977
20969
|
super.connectedCallback();
|
|
19978
20970
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
20971
|
+
if (!this.hasAttribute(`role`)) this.setAttribute(`role`, `listbox`);
|
|
20972
|
+
this.#syncAriaMultiselectable();
|
|
19979
20973
|
for (const [attr, value] of [
|
|
19980
20974
|
[`caption-style`, this.captionStyle],
|
|
19981
20975
|
[`caption-align`, this.captionAlign],
|
|
@@ -19984,7 +20978,13 @@ let GridListElement = class GridListElement extends i {
|
|
|
19984
20978
|
]) if (!this.hasAttribute(attr)) this.setAttribute(attr, value);
|
|
19985
20979
|
this.addEventListener(`keydown`, this._onKeyDown);
|
|
19986
20980
|
this.#mutationObserver ??= new MutationObserver((records) => this.#onChildrenChanged(records));
|
|
19987
|
-
this.#mutationObserver.observe(this, {
|
|
20981
|
+
this.#mutationObserver.observe(this, {
|
|
20982
|
+
childList: true,
|
|
20983
|
+
attributeFilter: [`hidden`]
|
|
20984
|
+
});
|
|
20985
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
20986
|
+
this.#ensureSearch();
|
|
20987
|
+
this.#syncSearchCommands();
|
|
19988
20988
|
}
|
|
19989
20989
|
disconnectedCallback() {
|
|
19990
20990
|
super.disconnectedCallback();
|
|
@@ -19993,6 +20993,9 @@ let GridListElement = class GridListElement extends i {
|
|
|
19993
20993
|
this.#resizeObserver = void 0;
|
|
19994
20994
|
this.#mutationObserver?.disconnect();
|
|
19995
20995
|
this.#cancelScrollRaf();
|
|
20996
|
+
this.#unregisterSearchCommands?.();
|
|
20997
|
+
this.#unregisterSearchCommands = void 0;
|
|
20998
|
+
this.#searchRegistry = void 0;
|
|
19996
20999
|
if (this.#reflowTimer) {
|
|
19997
21000
|
clearTimeout(this.#reflowTimer);
|
|
19998
21001
|
this.#reflowTimer = 0;
|
|
@@ -20001,6 +21004,19 @@ let GridListElement = class GridListElement extends i {
|
|
|
20001
21004
|
#onChildrenChanged(records) {
|
|
20002
21005
|
const previousLength = this.#items.length;
|
|
20003
21006
|
this.#syncItems();
|
|
21007
|
+
const removedKeys = [];
|
|
21008
|
+
for (const record of records ?? []) for (const node of record.removedNodes) {
|
|
21009
|
+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
21010
|
+
const key = keyForElement(node);
|
|
21011
|
+
if (key !== void 0) removedKeys.push(key);
|
|
21012
|
+
}
|
|
21013
|
+
if (removedKeys.length > 0) {
|
|
21014
|
+
const present = new Set(this.#source.keys());
|
|
21015
|
+
const gone = removedKeys.filter((k) => !present.has(k));
|
|
21016
|
+
if (gone.length > 0) this.selection.removeKeys(gone, (k) => k);
|
|
21017
|
+
const tickledId = this.tickled.tickledId;
|
|
21018
|
+
if (tickledId && gone.includes(tickledId)) this.tickled.focus(void 0);
|
|
21019
|
+
}
|
|
20004
21020
|
this._syncItemStates();
|
|
20005
21021
|
this.#noteChildMutation(previousLength, records);
|
|
20006
21022
|
this.requestUpdate();
|
|
@@ -20042,31 +21058,86 @@ let GridListElement = class GridListElement extends i {
|
|
|
20042
21058
|
}, REFLOW_WINDOW_MS);
|
|
20043
21059
|
}
|
|
20044
21060
|
#emitViewportChange() {
|
|
20045
|
-
this._dispatch(`grid-list-viewport-change`, {
|
|
21061
|
+
this._dispatch(`grid-list-viewport-change`, {
|
|
21062
|
+
renderedKeys: this.renderedKeys(),
|
|
21063
|
+
visibleKeys: this.visibleKeys()
|
|
21064
|
+
});
|
|
20046
21065
|
}
|
|
20047
21066
|
firstUpdated() {
|
|
21067
|
+
if (!this.#source) this.#rebuildSource();
|
|
20048
21068
|
const el = this.#viewportRef.value;
|
|
20049
21069
|
if (!el) return;
|
|
20050
|
-
this._viewportWidth = el.clientWidth;
|
|
20051
|
-
this._viewportHeight = el.clientHeight;
|
|
20052
21070
|
this.#resizeObserver = new ResizeObserver((entries) => {
|
|
20053
21071
|
for (const entry of entries) {
|
|
20054
|
-
|
|
20055
|
-
|
|
21072
|
+
const w = entry.contentRect.width;
|
|
21073
|
+
const h = entry.contentRect.height;
|
|
21074
|
+
if (w <= 0 || h <= 0) return;
|
|
21075
|
+
this._viewportWidth = w;
|
|
21076
|
+
this._viewportHeight = h;
|
|
20056
21077
|
}
|
|
20057
21078
|
});
|
|
20058
21079
|
this.#resizeObserver.observe(el);
|
|
21080
|
+
this.#measureViewport(false);
|
|
20059
21081
|
this.#syncItems();
|
|
20060
21082
|
this.requestUpdate();
|
|
20061
21083
|
}
|
|
21084
|
+
/** (Re)construct {@link #source} for the current mode and re-apply the filters. */
|
|
21085
|
+
#rebuildSource() {
|
|
21086
|
+
if (this.itemAdapter !== void 0) {
|
|
21087
|
+
this.#source = new DataGridListSource(this.items, this.itemAdapter);
|
|
21088
|
+
this.#sourceElement = void 0;
|
|
21089
|
+
} else {
|
|
21090
|
+
this.#sourceElement = new ElementGridListSource(this);
|
|
21091
|
+
this.#source = this.#sourceElement;
|
|
21092
|
+
}
|
|
21093
|
+
this.#applyFilters();
|
|
21094
|
+
if (this.#searchFilterKeys) this.#source.setFilters({ searchKeys: this.#searchFilterKeys });
|
|
21095
|
+
}
|
|
21096
|
+
/** Push the current filter predicate into whichever source is active. */
|
|
21097
|
+
#applyFilters() {
|
|
21098
|
+
if (this.#source instanceof ElementGridListSource) {
|
|
21099
|
+
const fp = this.filterPredicate;
|
|
21100
|
+
const src = this.#source;
|
|
21101
|
+
src.setFilters({ predicate: fp ? (i) => fp(src.rawElementAt(i)) : void 0 });
|
|
21102
|
+
this.#items = src.effectiveElements();
|
|
21103
|
+
} else if (this.#source instanceof DataGridListSource) {
|
|
21104
|
+
const ip = this.itemFilterPredicate;
|
|
21105
|
+
const src = this.#source;
|
|
21106
|
+
src.setFilters({ predicate: ip ? (i) => ip(src.rawItemAt(i), i) : void 0 });
|
|
21107
|
+
}
|
|
21108
|
+
}
|
|
20062
21109
|
willUpdate(changed) {
|
|
21110
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
21111
|
+
if (changed.has(`filterPredicate`) || changed.has(`itemFilterPredicate`)) {
|
|
21112
|
+
this.#applyFilters();
|
|
21113
|
+
this.updateComplete.then(() => this.#emitViewportChange());
|
|
21114
|
+
}
|
|
21115
|
+
if (changed.has(`items`) || changed.has(`itemAdapter`)) {
|
|
21116
|
+
this.#zoomAnchor = void 0;
|
|
21117
|
+
const prevKeys = this.#source?.keys() ?? [];
|
|
21118
|
+
const modeFlipped = this.itemAdapter !== void 0 !== this.#source instanceof DataGridListSource;
|
|
21119
|
+
this.#rebuildSource();
|
|
21120
|
+
const nextKeys = this.#source.keys();
|
|
21121
|
+
const nextKeySet = new Set(nextKeys);
|
|
21122
|
+
const removedKeys = prevKeys.filter((k) => !nextKeySet.has(k));
|
|
21123
|
+
if (removedKeys.length > 0) this.selection.removeKeys(removedKeys, (k) => k);
|
|
21124
|
+
const tickledId = this.tickled.tickledId;
|
|
21125
|
+
if (tickledId && !nextKeySet.has(tickledId)) this.tickled.focus(void 0);
|
|
21126
|
+
this._syncItemStates();
|
|
21127
|
+
this.updateComplete.then(() => this.#emitViewportChange());
|
|
21128
|
+
if (modeFlipped) {
|
|
21129
|
+
this.#ensureSearch();
|
|
21130
|
+
this.#syncSearchCommands();
|
|
21131
|
+
}
|
|
21132
|
+
this.search?.refresh();
|
|
21133
|
+
}
|
|
20063
21134
|
if (changed.has(`itemSize`) || changed.has(`gap`)) {
|
|
20064
21135
|
this.itemSize = Math.max(MIN_ITEM_SIZE, Math.round(this.itemSize));
|
|
20065
21136
|
this.gap = Math.max(0, Math.round(this.gap));
|
|
20066
21137
|
}
|
|
20067
21138
|
if (changed.has(`captionHeight`)) this.captionHeight = Math.max(0, Math.round(this.captionHeight));
|
|
20068
21139
|
if (changed.has(`itemWrap`)) this.#itemWrap = this.itemWrap;
|
|
20069
|
-
if ((changed.has(`itemSize`) || changed.has(`gap`) || changed.has(`captionStyle`) || changed.has(`captionHeight`) || changed.has(`orientation`)) && this.hasUpdated && this.#
|
|
21140
|
+
if ((changed.has(`itemSize`) || changed.has(`gap`) || changed.has(`captionStyle`) || changed.has(`captionHeight`) || changed.has(`orientation`)) && this.hasUpdated && this.#source.count > 0) {
|
|
20070
21141
|
const prevOrientation = changed.get(`orientation`) ?? this.orientation;
|
|
20071
21142
|
const horizontal = prevOrientation === `row`;
|
|
20072
21143
|
const prevOffset = horizontal ? this._scrollLeft : this._scrollTop;
|
|
@@ -20076,19 +21147,13 @@ let GridListElement = class GridListElement extends i {
|
|
|
20076
21147
|
const prevGap = changed.get(`gap`) ?? this.gap;
|
|
20077
21148
|
const prevCaptionStyle = changed.get(`captionStyle`) ?? this.captionStyle;
|
|
20078
21149
|
const prevCaptionHeight = changed.get(`captionHeight`) ?? this.captionHeight;
|
|
20079
|
-
|
|
20080
|
-
const prev = computeGridWindow(this.#items.length, prevItemSize, prevRowHeight, prevGap, this._viewportWidth, this._viewportHeight, prevOffset, prevOrientation);
|
|
20081
|
-
const mainStride = horizontal ? prev.colStride : prev.rowStride;
|
|
20082
|
-
const centerLine = (prevOffset + prevExtent / 2) / mainStride;
|
|
20083
|
-
const lineFloor = Math.max(0, Math.floor(centerLine));
|
|
20084
|
-
const index = horizontal ? Math.min(this.#items.length - 1, lineFloor) : Math.min(this.#items.length - 1, lineFloor * prev.cols + Math.max(0, Math.floor((prev.cols - 1) / 2)));
|
|
20085
|
-
this.#zoomAnchor = {
|
|
20086
|
-
index,
|
|
20087
|
-
lineFrac: centerLine - lineFloor
|
|
20088
|
-
};
|
|
21150
|
+
this.#captureCentreAnchor(prevOffset, prevExtent, prevItemSize, prevGap, prevCaptionStyle, prevCaptionHeight, prevOrientation);
|
|
20089
21151
|
}
|
|
20090
21152
|
}
|
|
20091
21153
|
}
|
|
21154
|
+
#syncAriaMultiselectable() {
|
|
21155
|
+
this.setAttribute(`aria-multiselectable`, this.selection.canSelectMultiple() ? `true` : `false`);
|
|
21156
|
+
}
|
|
20092
21157
|
/** Full cell height: the square, plus a caption band in `separate` / `under`. */
|
|
20093
21158
|
#rowHeight() {
|
|
20094
21159
|
const captionBand = this.captionStyle === `separate` || this.captionStyle === `under`;
|
|
@@ -20100,7 +21165,10 @@ let GridListElement = class GridListElement extends i {
|
|
|
20100
21165
|
updated(changed) {
|
|
20101
21166
|
super.updated(changed);
|
|
20102
21167
|
this.#syncItems();
|
|
20103
|
-
if (changed.has(`selectionMode`) || changed.has(`interactionMode`))
|
|
21168
|
+
if (changed.has(`selectionMode`) || changed.has(`interactionMode`)) {
|
|
21169
|
+
this.selection.clearAnchor();
|
|
21170
|
+
this.#syncAriaMultiselectable();
|
|
21171
|
+
}
|
|
20104
21172
|
if (changed.has(`orientation`)) {
|
|
20105
21173
|
const vp = this.#viewportRef.value;
|
|
20106
21174
|
if (vp) {
|
|
@@ -20113,6 +21181,11 @@ let GridListElement = class GridListElement extends i {
|
|
|
20113
21181
|
}
|
|
20114
21182
|
}
|
|
20115
21183
|
}
|
|
21184
|
+
const vp = this.#viewportRef.value;
|
|
21185
|
+
if (vp) {
|
|
21186
|
+
if (Math.abs(vp.scrollTop - this._scrollTop) > .5) vp.scrollTop = this._scrollTop;
|
|
21187
|
+
if (Math.abs(vp.scrollLeft - this._scrollLeft) > .5) vp.scrollLeft = this._scrollLeft;
|
|
21188
|
+
}
|
|
20116
21189
|
this.#applyZoomAnchor();
|
|
20117
21190
|
}
|
|
20118
21191
|
/** Re-anchor the scroll so the item captured in `willUpdate` stays centred. */
|
|
@@ -20143,30 +21216,67 @@ let GridListElement = class GridListElement extends i {
|
|
|
20143
21216
|
this._scrollTop = next;
|
|
20144
21217
|
}
|
|
20145
21218
|
}
|
|
20146
|
-
#
|
|
20147
|
-
this.#
|
|
20148
|
-
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
21219
|
+
#captureCentreAnchor(prevOffset, prevExtent, prevItemSize, prevGap, prevCaptionStyle, prevCaptionHeight, prevOrientation) {
|
|
21220
|
+
const count = this.#source?.count ?? 0;
|
|
21221
|
+
if (count === 0) return;
|
|
21222
|
+
const prevRowHeight = prevItemSize + (prevCaptionStyle === `separate` || prevCaptionStyle === `under` ? prevCaptionHeight : 0);
|
|
21223
|
+
const horizontal = prevOrientation === `row`;
|
|
21224
|
+
const prev = computeGridWindow(count, prevItemSize, prevRowHeight, prevGap, this._viewportWidth, this._viewportHeight, prevOffset, prevOrientation);
|
|
21225
|
+
const mainStride = horizontal ? prev.colStride : prev.rowStride;
|
|
21226
|
+
const centerLine = (prevOffset + prevExtent / 2) / mainStride;
|
|
21227
|
+
const lineFloor = Math.max(0, Math.floor(centerLine));
|
|
21228
|
+
const index = horizontal ? Math.min(count - 1, lineFloor) : Math.min(count - 1, lineFloor * prev.cols + Math.max(0, Math.floor((prev.cols - 1) / 2)));
|
|
21229
|
+
this.#zoomAnchor = {
|
|
21230
|
+
index,
|
|
21231
|
+
lineFrac: centerLine - lineFloor
|
|
21232
|
+
};
|
|
20152
21233
|
}
|
|
20153
|
-
|
|
20154
|
-
|
|
20155
|
-
|
|
20156
|
-
|
|
20157
|
-
|
|
20158
|
-
|
|
21234
|
+
/**
|
|
21235
|
+
* Re-read the viewport after an ancestor's visibility/layout changed.
|
|
21236
|
+
* Safe while hidden (zero-sized reads are ignored); forces a repaint of the
|
|
21237
|
+
* virtual window even when the size is unchanged.
|
|
21238
|
+
*/
|
|
21239
|
+
refreshLayout() {
|
|
21240
|
+
this.#measureViewport(true);
|
|
21241
|
+
}
|
|
21242
|
+
/** Alias of {@link refreshLayout}. */
|
|
21243
|
+
remeasure() {
|
|
21244
|
+
this.#measureViewport(true);
|
|
21245
|
+
}
|
|
21246
|
+
#measureViewport(forceRender = false) {
|
|
21247
|
+
const vp = this.#viewportRef.value;
|
|
21248
|
+
if (!vp) return;
|
|
21249
|
+
const w = vp.clientWidth;
|
|
21250
|
+
const h = vp.clientHeight;
|
|
21251
|
+
if (w <= 0 || h <= 0) return;
|
|
21252
|
+
const horizontal = this.orientation === `row`;
|
|
21253
|
+
const changed = w !== this._viewportWidth || h !== this._viewportHeight;
|
|
21254
|
+
if (changed) {
|
|
21255
|
+
const prevOffset = horizontal ? this._scrollLeft : this._scrollTop;
|
|
21256
|
+
const prevExtent = horizontal ? w : h;
|
|
21257
|
+
this.#captureCentreAnchor(prevOffset, prevExtent, this.itemSize, this.gap, this.captionStyle, this.captionHeight, this.orientation);
|
|
21258
|
+
this._viewportWidth = w;
|
|
21259
|
+
this._viewportHeight = h;
|
|
20159
21260
|
}
|
|
20160
|
-
|
|
21261
|
+
if (forceRender && !changed) {
|
|
21262
|
+
this.#captureCentreAnchor(horizontal ? this._scrollLeft : this._scrollTop, horizontal ? w : h, this.itemSize, this.gap, this.captionStyle, this.captionHeight, this.orientation);
|
|
21263
|
+
this.requestUpdate();
|
|
21264
|
+
}
|
|
21265
|
+
if (changed || forceRender) this.updateComplete.then(() => this.#emitViewportChange());
|
|
20161
21266
|
}
|
|
20162
|
-
|
|
20163
|
-
|
|
20164
|
-
|
|
20165
|
-
|
|
20166
|
-
|
|
21267
|
+
/**
|
|
21268
|
+
* Element mode only: re-read the light DOM into the source and refresh the
|
|
21269
|
+
* `#items` cache + filter. A no-op in data mode (the source is rebuilt through
|
|
21270
|
+
* `#rebuildSource` when `items` / `itemAdapter` change).
|
|
21271
|
+
*/
|
|
21272
|
+
#syncItems() {
|
|
21273
|
+
if (this.#source instanceof ElementGridListSource) {
|
|
21274
|
+
this.#source.refresh();
|
|
21275
|
+
this.#applyFilters();
|
|
21276
|
+
}
|
|
20167
21277
|
}
|
|
20168
21278
|
get count() {
|
|
20169
|
-
return this.#
|
|
21279
|
+
return this.#source.count;
|
|
20170
21280
|
}
|
|
20171
21281
|
/**
|
|
20172
21282
|
* Number of cells currently rendered in the shadow DOM. With virtualization
|
|
@@ -20177,20 +21287,38 @@ let GridListElement = class GridListElement extends i {
|
|
|
20177
21287
|
return this.renderRoot.querySelectorAll(`.cell`).length;
|
|
20178
21288
|
}
|
|
20179
21289
|
get selectedItems() {
|
|
20180
|
-
|
|
21290
|
+
const els = /* @__PURE__ */ new Set();
|
|
21291
|
+
for (const key of this.selection.selectedKeys) {
|
|
21292
|
+
const el = this.#source.resolveKeyToElement(key);
|
|
21293
|
+
if (el) els.add(el);
|
|
21294
|
+
}
|
|
21295
|
+
return els;
|
|
20181
21296
|
}
|
|
20182
|
-
/**
|
|
21297
|
+
/**
|
|
21298
|
+
* The selection as stable `data-key` / `data-value` strings. Auto-assigned
|
|
21299
|
+
* keys for keyless light-DOM items are omitted (they are not a stable contract
|
|
21300
|
+
* for the consumer).
|
|
21301
|
+
*/
|
|
20183
21302
|
get selectedKeys() {
|
|
20184
|
-
|
|
21303
|
+
const out = /* @__PURE__ */ new Set();
|
|
21304
|
+
for (const k of this.selection.selectedKeys) if (!isSyntheticKey(k)) out.add(k);
|
|
21305
|
+
return out;
|
|
20185
21306
|
}
|
|
20186
21307
|
select(item) {
|
|
20187
|
-
|
|
21308
|
+
const key = keyForElement(item);
|
|
21309
|
+
if (key) this.selection.replace(key);
|
|
20188
21310
|
}
|
|
20189
21311
|
selectMany(items) {
|
|
20190
|
-
|
|
21312
|
+
const keys = [];
|
|
21313
|
+
for (const item of items) {
|
|
21314
|
+
const key = keyForElement(item);
|
|
21315
|
+
if (key) keys.push(key);
|
|
21316
|
+
}
|
|
21317
|
+
this.selection.selectMany(keys);
|
|
20191
21318
|
}
|
|
20192
21319
|
deselect(item) {
|
|
20193
|
-
|
|
21320
|
+
const key = keyForElement(item);
|
|
21321
|
+
if (key) this.selection.deselect(key);
|
|
20194
21322
|
}
|
|
20195
21323
|
clearSelection() {
|
|
20196
21324
|
this.selection.clear();
|
|
@@ -20204,44 +21332,126 @@ let GridListElement = class GridListElement extends i {
|
|
|
20204
21332
|
* have a `data-key` (or `data-value`).
|
|
20205
21333
|
*/
|
|
20206
21334
|
selectKeys(keys) {
|
|
20207
|
-
this.selection.selectKeys(keys, (
|
|
21335
|
+
this.selection.selectKeys(keys, (k) => k);
|
|
20208
21336
|
}
|
|
20209
21337
|
/** Add items by key to the selection (multi-select modes only). */
|
|
20210
21338
|
addSelectedKeys(keys) {
|
|
20211
|
-
this.selection.addKeys(keys, (
|
|
21339
|
+
this.selection.addKeys(keys, (k) => k);
|
|
20212
21340
|
}
|
|
20213
21341
|
/** Remove items by key from the selection. */
|
|
20214
21342
|
removeSelectedKeys(keys) {
|
|
20215
|
-
this.selection.removeKeys(keys, (
|
|
21343
|
+
this.selection.removeKeys(keys, (k) => k);
|
|
21344
|
+
}
|
|
21345
|
+
/**
|
|
21346
|
+
* Data mode only: raw index of `key` in `this.items`, or `-1`. Data-mode keys
|
|
21347
|
+
* come straight from the adapter, so this is exact even under a filter/search.
|
|
21348
|
+
*/
|
|
21349
|
+
#rawIndexOfKey(key) {
|
|
21350
|
+
const adapter = this.itemAdapter;
|
|
21351
|
+
if (!adapter) return -1;
|
|
21352
|
+
return this.items.findIndex((it, i) => adapter.key(it, i) === key);
|
|
21353
|
+
}
|
|
21354
|
+
/**
|
|
21355
|
+
* Data mode: replace the whole item array. Selection and cursor keep any key
|
|
21356
|
+
* that is still present; keys that vanish are dropped (one `list-select`).
|
|
21357
|
+
* A no-op in element mode.
|
|
21358
|
+
*/
|
|
21359
|
+
setItems(items) {
|
|
21360
|
+
if (this.itemAdapter) this.items = items;
|
|
21361
|
+
}
|
|
21362
|
+
/**
|
|
21363
|
+
* Data mode: append items to the end. Scroll position, selection and cursor
|
|
21364
|
+
* are untouched; appended items don't play the mid-list `[reflowing]` slide.
|
|
21365
|
+
* A no-op in element mode.
|
|
21366
|
+
*/
|
|
21367
|
+
appendItems(items) {
|
|
21368
|
+
if (this.itemAdapter && items.length > 0) this.items = [...this.items, ...items];
|
|
21369
|
+
}
|
|
21370
|
+
/**
|
|
21371
|
+
* Data mode: replace the item carrying `key`. A visible cell re-renders in
|
|
21372
|
+
* place (the keyed subtree is kept); an offscreen item shows its new content
|
|
21373
|
+
* when scrolled into view. Scroll and selection are untouched. Unknown keys
|
|
21374
|
+
* are ignored.
|
|
21375
|
+
*/
|
|
21376
|
+
updateItem(key, item) {
|
|
21377
|
+
if (!this.itemAdapter) return;
|
|
21378
|
+
const idx = this.#rawIndexOfKey(key);
|
|
21379
|
+
if (idx < 0) return;
|
|
21380
|
+
const next = [...this.items];
|
|
21381
|
+
next[idx] = item;
|
|
21382
|
+
this.items = next;
|
|
21383
|
+
}
|
|
21384
|
+
/**
|
|
21385
|
+
* Data mode: remove the items carrying these keys. Removed keys are dropped
|
|
21386
|
+
* from selection and cursor (via the `items` change). A no-op in element mode.
|
|
21387
|
+
*/
|
|
21388
|
+
removeItems(keys) {
|
|
21389
|
+
if (!this.itemAdapter) return;
|
|
21390
|
+
const adapter = this.itemAdapter;
|
|
21391
|
+
const keySet = new Set(keys);
|
|
21392
|
+
this.items = this.items.filter((it, i) => !keySet.has(adapter.key(it, i)));
|
|
21393
|
+
}
|
|
21394
|
+
/**
|
|
21395
|
+
* Data mode: drop every item. Selection and cursor are reset and one
|
|
21396
|
+
* `list-select` with empty sets is emitted. A no-op in element mode.
|
|
21397
|
+
*/
|
|
21398
|
+
clearItems() {
|
|
21399
|
+
if (!this.itemAdapter) return;
|
|
21400
|
+
this.selection.reset();
|
|
21401
|
+
this.tickled.focus(void 0);
|
|
21402
|
+
this.items = [];
|
|
21403
|
+
this.#emitSelect(/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
|
|
20216
21404
|
}
|
|
20217
21405
|
/** Move the cursor to the item with this key and scroll it into view. */
|
|
20218
21406
|
focusKey(key) {
|
|
20219
|
-
|
|
20220
|
-
|
|
20221
|
-
this
|
|
20222
|
-
this.#scrollItemIntoView(el);
|
|
21407
|
+
if (this.#source.indexOfKey(key) < 0) return;
|
|
21408
|
+
this.tickled.focus(key);
|
|
21409
|
+
this.#scrollItemIntoView(key);
|
|
20223
21410
|
}
|
|
20224
21411
|
/** The key of the item under the cursor, or `undefined`. */
|
|
20225
21412
|
get tickledKey() {
|
|
20226
|
-
|
|
20227
|
-
return id ? keyForElement(id) : void 0;
|
|
21413
|
+
return this.tickled.tickledId;
|
|
20228
21414
|
}
|
|
21415
|
+
/** Element mode: the light-DOM element carrying `key`, or `undefined`. */
|
|
20229
21416
|
getItem(key) {
|
|
20230
|
-
return this.#
|
|
21417
|
+
return this.#source.resolveKeyToElement(key);
|
|
20231
21418
|
}
|
|
21419
|
+
/** Element mode: the light-DOM element at effective index `index`. */
|
|
20232
21420
|
getItemByIndex(index) {
|
|
20233
21421
|
return this.#items[index];
|
|
20234
21422
|
}
|
|
21423
|
+
/** Data mode: the data value carrying `key`, or `undefined`. */
|
|
21424
|
+
getData(key) {
|
|
21425
|
+
return this.#source.resolveKeyToData(key);
|
|
21426
|
+
}
|
|
20235
21427
|
getKey(item) {
|
|
20236
21428
|
return keyForElement(item);
|
|
20237
21429
|
}
|
|
21430
|
+
/**
|
|
21431
|
+
* Keys of items in the rendered window (viewport + overscan) — the priority
|
|
21432
|
+
* set for prefetching. Use {@link visibleKeys} for the strict viewport
|
|
21433
|
+
* intersection. Auto-assigned keyless-item keys are omitted.
|
|
21434
|
+
*/
|
|
21435
|
+
renderedKeys() {
|
|
21436
|
+
return this.#windowKeys(this.#currentWindow());
|
|
21437
|
+
}
|
|
21438
|
+
/**
|
|
21439
|
+
* Keys of items actually intersecting the viewport (no overscan).
|
|
21440
|
+
* Auto-assigned keyless-item keys are omitted.
|
|
21441
|
+
*/
|
|
20238
21442
|
visibleKeys() {
|
|
20239
|
-
|
|
20240
|
-
|
|
21443
|
+
return this.#windowKeys(this.#currentWindow(true));
|
|
21444
|
+
}
|
|
21445
|
+
#windowKeys(win) {
|
|
21446
|
+
return this.#source.keys().slice(win.startIndex, win.endIndex).filter((k) => !isSyntheticKey(k));
|
|
21447
|
+
}
|
|
21448
|
+
#currentWindow(noOverscan = false) {
|
|
21449
|
+
const overscan = noOverscan ? 0 : 2;
|
|
21450
|
+
return computeGridWindow(this.#source.count, this.itemSize, this.#rowHeight(), this.gap, this._viewportWidth, this._viewportHeight, this.orientation === `row` ? this._scrollLeft : this._scrollTop, this.orientation, overscan);
|
|
20241
21451
|
}
|
|
20242
|
-
/** Adjust the scroll offset so `
|
|
20243
|
-
#scrollItemIntoView(
|
|
20244
|
-
const index = this.#
|
|
21452
|
+
/** Adjust the scroll offset so `key` is fully within the viewport. */
|
|
21453
|
+
#scrollItemIntoView(key) {
|
|
21454
|
+
const index = this.#source.indexOfKey(key);
|
|
20245
21455
|
const vp = this.#viewportRef.value;
|
|
20246
21456
|
if (index < 0 || !vp) return;
|
|
20247
21457
|
const win = this.#currentWindow();
|
|
@@ -20270,12 +21480,43 @@ let GridListElement = class GridListElement extends i {
|
|
|
20270
21480
|
}
|
|
20271
21481
|
}
|
|
20272
21482
|
_syncItemStates() {
|
|
21483
|
+
const selected = this.selection.selectedKeys;
|
|
20273
21484
|
for (const el of this.#items) {
|
|
20274
|
-
|
|
20275
|
-
el.toggleAttribute(`data-
|
|
21485
|
+
const key = keyForElement(el);
|
|
21486
|
+
el.toggleAttribute(`data-tickled`, key !== void 0 && this.tickled.isTickled(key));
|
|
21487
|
+
el.toggleAttribute(`data-selected`, key !== void 0 && selected.has(key));
|
|
20276
21488
|
}
|
|
20277
21489
|
this.setAttribute(`data-input-mode`, this.tickled.inputMode);
|
|
20278
21490
|
}
|
|
21491
|
+
#emitSelect(selected, previous) {
|
|
21492
|
+
const selectedEls = /* @__PURE__ */ new Set();
|
|
21493
|
+
const previousEls = /* @__PURE__ */ new Set();
|
|
21494
|
+
for (const key of selected) {
|
|
21495
|
+
const el = this.#source.resolveKeyToElement(key);
|
|
21496
|
+
if (el) selectedEls.add(el);
|
|
21497
|
+
}
|
|
21498
|
+
for (const key of previous) {
|
|
21499
|
+
const el = this.#source.resolveKeyToElement(key);
|
|
21500
|
+
if (el) previousEls.add(el);
|
|
21501
|
+
}
|
|
21502
|
+
this._dispatch(`list-select`, {
|
|
21503
|
+
selected: selectedEls,
|
|
21504
|
+
previous: previousEls,
|
|
21505
|
+
selectedKeys: this.selectedKeys,
|
|
21506
|
+
previousKeys: new Set([...previous].filter((k) => !isSyntheticKey(k)))
|
|
21507
|
+
});
|
|
21508
|
+
}
|
|
21509
|
+
#emitItemEvent(name, key) {
|
|
21510
|
+
const index = this.#source.indexOfKey(key);
|
|
21511
|
+
const element = this.#source.resolveKeyToElement(key);
|
|
21512
|
+
const item = element ?? this.#source.resolveKeyToData(key);
|
|
21513
|
+
this._dispatch(name, {
|
|
21514
|
+
item,
|
|
21515
|
+
key,
|
|
21516
|
+
index,
|
|
21517
|
+
element
|
|
21518
|
+
});
|
|
21519
|
+
}
|
|
20279
21520
|
_dispatch(name, detail) {
|
|
20280
21521
|
this.dispatchEvent(new CustomEvent(name, {
|
|
20281
21522
|
detail,
|
|
@@ -20283,9 +21524,11 @@ let GridListElement = class GridListElement extends i {
|
|
|
20283
21524
|
composed: true
|
|
20284
21525
|
}));
|
|
20285
21526
|
}
|
|
21527
|
+
/** Selected keys, snapshotted once per render so the cell renderers don't each rebuild the set. */
|
|
21528
|
+
#selectedSnapshot;
|
|
20286
21529
|
render() {
|
|
20287
21530
|
const win = this.#currentWindow();
|
|
20288
|
-
|
|
21531
|
+
this.#selectedSnapshot = this.selection.selectedKeys;
|
|
20289
21532
|
const spacerStyle = win.horizontal ? `width:${win.totalWidth}px;height:100%` : `height:${win.totalHeight}px`;
|
|
20290
21533
|
const scrollFade = win.horizontal ? `scroll-fade-x` : `scroll-fade-y`;
|
|
20291
21534
|
return b`
|
|
@@ -20297,14 +21540,56 @@ let GridListElement = class GridListElement extends i {
|
|
|
20297
21540
|
@scroll=${this.#onScroll}
|
|
20298
21541
|
>
|
|
20299
21542
|
<div class="spacer" style=${spacerStyle}>
|
|
20300
|
-
${
|
|
21543
|
+
${this.#source instanceof DataGridListSource ? this.#renderDataCells(win) : this.#renderElementCells(win)}
|
|
20301
21544
|
</div>
|
|
20302
21545
|
</div>
|
|
21546
|
+
${this.search?.renderField()}
|
|
20303
21547
|
`;
|
|
20304
21548
|
}
|
|
21549
|
+
#renderDataCells(win) {
|
|
21550
|
+
const slice = Array.from({ length: win.endIndex - win.startIndex }, (_, i) => win.startIndex + i);
|
|
21551
|
+
return repeat(slice, (index) => this.#source.keyAt(index), (index) => this.#renderDataCell(index, win));
|
|
21552
|
+
}
|
|
21553
|
+
#renderDataCell(index, win) {
|
|
21554
|
+
const key = this.#source.keyAt(index);
|
|
21555
|
+
const selected = this.#selectedSnapshot.has(key);
|
|
21556
|
+
const tickled = this.tickled.isTickled(key);
|
|
21557
|
+
const style = this.captionStyle;
|
|
21558
|
+
const separate = style === `separate`;
|
|
21559
|
+
const caption = style === `hidden` ? `` : this.#source.labelAt(index);
|
|
21560
|
+
const showCaption = caption !== ``;
|
|
21561
|
+
const cellHeight = win.rowStride - this.gap;
|
|
21562
|
+
const x = win.horizontal ? index * win.colStride : columnOf(index, win.cols) * win.colStride;
|
|
21563
|
+
const y = win.horizontal ? Math.max(0, (this._viewportHeight - cellHeight) / 2) : rowOf(index, win.cols) * win.rowStride;
|
|
21564
|
+
const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
|
|
21565
|
+
return b`
|
|
21566
|
+
<div
|
|
21567
|
+
class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
|
|
21568
|
+
part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
|
|
21569
|
+
role="option"
|
|
21570
|
+
aria-selected=${selected ? `true` : `false`}
|
|
21571
|
+
style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
|
|
21572
|
+
data-index=${index}
|
|
21573
|
+
data-key=${key}
|
|
21574
|
+
?data-selected=${selected}
|
|
21575
|
+
?data-tickled=${tickled}
|
|
21576
|
+
@click=${(e) => this.#onCellClick(e, key)}
|
|
21577
|
+
@dblclick=${() => this.#onCellActivate(key)}
|
|
21578
|
+
@pointerenter=${() => this.tickled.handlePointerEnter(key)}
|
|
21579
|
+
>
|
|
21580
|
+
<div class="content" part="content">${this.#source.renderCellContent(index)}</div>
|
|
21581
|
+
${showCaption ? b`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>` : ``}
|
|
21582
|
+
</div>
|
|
21583
|
+
`;
|
|
21584
|
+
}
|
|
21585
|
+
#renderElementCells(win) {
|
|
21586
|
+
const slice = this.#items.slice(win.startIndex, win.endIndex);
|
|
21587
|
+
return repeat(slice, (_el, i) => this.#source.keyAt(win.startIndex + i), (el, i) => this.#renderCell(el, win.startIndex + i, win));
|
|
21588
|
+
}
|
|
20305
21589
|
#renderCell(el, index, win) {
|
|
20306
|
-
const
|
|
20307
|
-
const
|
|
21590
|
+
const key = this.#source.keyAt(index);
|
|
21591
|
+
const selected = this.#selectedSnapshot.has(key);
|
|
21592
|
+
const tickled = this.tickled.isTickled(key);
|
|
20308
21593
|
const entering = this.#entering.has(el);
|
|
20309
21594
|
const style = this.captionStyle;
|
|
20310
21595
|
const separate = style === `separate`;
|
|
@@ -20318,17 +21603,22 @@ let GridListElement = class GridListElement extends i {
|
|
|
20318
21603
|
return b`
|
|
20319
21604
|
<div
|
|
20320
21605
|
class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}"
|
|
20321
|
-
part="item"
|
|
21606
|
+
part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
|
|
21607
|
+
role="option"
|
|
21608
|
+
aria-selected=${selected ? `true` : `false`}
|
|
20322
21609
|
style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
|
|
20323
21610
|
data-index=${index}
|
|
20324
|
-
|
|
20325
|
-
|
|
20326
|
-
|
|
21611
|
+
data-key=${key}
|
|
21612
|
+
?data-selected=${selected}
|
|
21613
|
+
?data-tickled=${tickled}
|
|
21614
|
+
@click=${(e) => this.#onCellClick(e, key)}
|
|
21615
|
+
@dblclick=${() => this.#onCellActivate(key)}
|
|
21616
|
+
@pointerenter=${() => this.tickled.handlePointerEnter(key)}
|
|
20327
21617
|
>
|
|
20328
21618
|
<div class="thumb" part="thumb" style=${thumbStyle}>
|
|
20329
|
-
|
|
21619
|
+
${this.#source.renderCellContent(index)}
|
|
20330
21620
|
</div>
|
|
20331
|
-
${showCaption ? b`<div class="caption" part="caption" style=${captionCss}>${caption}</div>` : ``}
|
|
21621
|
+
${showCaption ? b`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>` : ``}
|
|
20332
21622
|
</div>
|
|
20333
21623
|
`;
|
|
20334
21624
|
}
|
|
@@ -20339,16 +21629,13 @@ let GridListElement = class GridListElement extends i {
|
|
|
20339
21629
|
this.#scrollRaf = 0;
|
|
20340
21630
|
}
|
|
20341
21631
|
}
|
|
20342
|
-
#onCellClick(e,
|
|
20343
|
-
this.selection.handleClick(
|
|
20344
|
-
this
|
|
21632
|
+
#onCellClick(e, key) {
|
|
21633
|
+
this.selection.handleClick(key, e);
|
|
21634
|
+
this.#emitItemEvent(`list-item-click`, key);
|
|
20345
21635
|
}
|
|
20346
|
-
#onCellActivate(
|
|
20347
|
-
if (this.interactionMode !== `manual`) this.selection.replace(
|
|
20348
|
-
this
|
|
20349
|
-
}
|
|
20350
|
-
#currentWindow() {
|
|
20351
|
-
return computeGridWindow(this.#items.length, this.itemSize, this.#rowHeight(), this.gap, this._viewportWidth, this._viewportHeight, this.orientation === `row` ? this._scrollLeft : this._scrollTop, this.orientation);
|
|
21636
|
+
#onCellActivate(key) {
|
|
21637
|
+
if (this.interactionMode !== `manual`) this.selection.replace(key);
|
|
21638
|
+
this.#emitItemEvent(`list-activate`, key);
|
|
20352
21639
|
}
|
|
20353
21640
|
};
|
|
20354
21641
|
__decorate([property({
|
|
@@ -20396,6 +21683,15 @@ __decorate([property({
|
|
|
20396
21683
|
type: String,
|
|
20397
21684
|
attribute: `item-wrap`
|
|
20398
21685
|
})], GridListElement.prototype, "itemWrap", void 0);
|
|
21686
|
+
__decorate([property({ attribute: false })], GridListElement.prototype, "filterPredicate", void 0);
|
|
21687
|
+
__decorate([property({ attribute: false })], GridListElement.prototype, "itemFilterPredicate", void 0);
|
|
21688
|
+
__decorate([property({
|
|
21689
|
+
type: String,
|
|
21690
|
+
attribute: `search-modes`,
|
|
21691
|
+
reflect: true
|
|
21692
|
+
})], GridListElement.prototype, "searchModes", void 0);
|
|
21693
|
+
__decorate([property({ attribute: false })], GridListElement.prototype, "items", void 0);
|
|
21694
|
+
__decorate([property({ attribute: false })], GridListElement.prototype, "itemAdapter", void 0);
|
|
20399
21695
|
__decorate([state()], GridListElement.prototype, "_scrollTop", void 0);
|
|
20400
21696
|
__decorate([state()], GridListElement.prototype, "_scrollLeft", void 0);
|
|
20401
21697
|
__decorate([state()], GridListElement.prototype, "_viewportHeight", void 0);
|
|
@@ -20415,6 +21711,7 @@ let PanelElement = class PanelElement extends i {
|
|
|
20415
21711
|
this.toggleInteraction = `implicit`;
|
|
20416
21712
|
this.variant = `titlebar`;
|
|
20417
21713
|
this.hideTitlebar = false;
|
|
21714
|
+
this.keepToolbar = false;
|
|
20418
21715
|
}
|
|
20419
21716
|
_onTitlebarToggle(event) {
|
|
20420
21717
|
event.stopPropagation();
|
|
@@ -20434,6 +21731,7 @@ let PanelElement = class PanelElement extends i {
|
|
|
20434
21731
|
this.remove();
|
|
20435
21732
|
}
|
|
20436
21733
|
render() {
|
|
21734
|
+
const showToolbar = this.keepToolbar || !this.collapsed;
|
|
20437
21735
|
return b`
|
|
20438
21736
|
${this.hideTitlebar ? A : b`
|
|
20439
21737
|
<ixfx-titlebar
|
|
@@ -20448,7 +21746,7 @@ let PanelElement = class PanelElement extends i {
|
|
|
20448
21746
|
@toggle=${this._onTitlebarToggle}
|
|
20449
21747
|
@close=${this._onCloseClick}
|
|
20450
21748
|
>
|
|
20451
|
-
|
|
21749
|
+
${showToolbar ? b`<slot name="toolbar" slot="toolbar"></slot>` : A}
|
|
20452
21750
|
</ixfx-titlebar>
|
|
20453
21751
|
`}
|
|
20454
21752
|
|
|
@@ -20548,6 +21846,30 @@ let PanelElement = class PanelElement extends i {
|
|
|
20548
21846
|
:host([variant="midline"][orientation="vertical"]) .content-wrapper {
|
|
20549
21847
|
background: transparent;
|
|
20550
21848
|
}
|
|
21849
|
+
|
|
21850
|
+
/* === Tree variant: VS Code explorer / Outline style === */
|
|
21851
|
+
|
|
21852
|
+
:host([variant="tree"]) {
|
|
21853
|
+
border: none !important;
|
|
21854
|
+
border-radius: 0 !important;
|
|
21855
|
+
background: transparent;
|
|
21856
|
+
}
|
|
21857
|
+
|
|
21858
|
+
:host([variant="tree"]) .content-inner {
|
|
21859
|
+
box-shadow: none;
|
|
21860
|
+
background: transparent;
|
|
21861
|
+
padding: 0;
|
|
21862
|
+
}
|
|
21863
|
+
|
|
21864
|
+
:host([variant="tree"]) .content-wrapper {
|
|
21865
|
+
background: transparent;
|
|
21866
|
+
}
|
|
21867
|
+
|
|
21868
|
+
:host([variant="tree"][orientation="vertical"]) .content-inner {
|
|
21869
|
+
width: max-content;
|
|
21870
|
+
height: 100%;
|
|
21871
|
+
transform: none;
|
|
21872
|
+
}
|
|
20551
21873
|
`];
|
|
20552
21874
|
}
|
|
20553
21875
|
};
|
|
@@ -20583,6 +21905,10 @@ __decorate([property({
|
|
|
20583
21905
|
type: Boolean,
|
|
20584
21906
|
attribute: `hide-titlebar`
|
|
20585
21907
|
})], PanelElement.prototype, "hideTitlebar", void 0);
|
|
21908
|
+
__decorate([property({
|
|
21909
|
+
type: Boolean,
|
|
21910
|
+
attribute: `keep-toolbar`
|
|
21911
|
+
})], PanelElement.prototype, "keepToolbar", void 0);
|
|
20586
21912
|
PanelElement = __decorate([safeCustomElement(`ixfx-panel`)], PanelElement);
|
|
20587
21913
|
//#endregion
|
|
20588
21914
|
//#region src/grouped-item-lister/grouped-item-lister.ts
|
|
@@ -20778,6 +22104,10 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
20778
22104
|
}
|
|
20779
22105
|
});
|
|
20780
22106
|
this._focusedDepth = 0;
|
|
22107
|
+
this.search = new ListSearchController(this, { adapter: createTreeListSearchAdapter(this, {
|
|
22108
|
+
labelSelector: `.tree-label`,
|
|
22109
|
+
highlightKey: `incr-search-highlight`
|
|
22110
|
+
}) });
|
|
20781
22111
|
this.#config = { indent: 20 };
|
|
20782
22112
|
this.#renderedKeys = [];
|
|
20783
22113
|
this.#lastNodeByKey = /* @__PURE__ */ new Map();
|
|
@@ -20786,6 +22116,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
20786
22116
|
this.#leavingAnimating = /* @__PURE__ */ new Set();
|
|
20787
22117
|
this.#treeAnimReady = false;
|
|
20788
22118
|
this.#onKeyDown = (event) => {
|
|
22119
|
+
if (this.search.isOpen) return;
|
|
20789
22120
|
const nodes = this.#getAllNodes();
|
|
20790
22121
|
if (nodes.length === 0) return;
|
|
20791
22122
|
if ((event.ctrlKey || event.metaKey) && event.key === `a`) {
|
|
@@ -20906,6 +22237,19 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
20906
22237
|
get selectedNodes() {
|
|
20907
22238
|
return this._selectedNodes;
|
|
20908
22239
|
}
|
|
22240
|
+
#unregisterSearchCommands;
|
|
22241
|
+
#effectiveSearchModes() {
|
|
22242
|
+
const raw = this.searchModes?.trim();
|
|
22243
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
22244
|
+
const requested = new Set(raw.split(/\s+/));
|
|
22245
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
22246
|
+
}
|
|
22247
|
+
#syncSearchCommands() {
|
|
22248
|
+
if (!this.#ownedRegistry) return;
|
|
22249
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
22250
|
+
this.#unregisterSearchCommands?.();
|
|
22251
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#ownedRegistry, this.search);
|
|
22252
|
+
}
|
|
20909
22253
|
#pendingNode;
|
|
20910
22254
|
#pendingController;
|
|
20911
22255
|
#modelUnsubscribe;
|
|
@@ -20948,6 +22292,13 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
20948
22292
|
_isLeaving(node) {
|
|
20949
22293
|
return this.#leaving.has(node.item.key);
|
|
20950
22294
|
}
|
|
22295
|
+
/**
|
|
22296
|
+
* True when `filterPredicate` is set and neither `node` nor any descendant
|
|
22297
|
+
* matches — the row should carry the `filtered` class (`display: none`).
|
|
22298
|
+
*/
|
|
22299
|
+
_isFilteredOut(node, depth) {
|
|
22300
|
+
return this.filterPredicate !== void 0 && !this.#matchesFilter(node, depth);
|
|
22301
|
+
}
|
|
20951
22302
|
/** Splice the fading ghost rows back into the visible list near where they were. */
|
|
20952
22303
|
#weaveLeaving(nodes) {
|
|
20953
22304
|
if (this.#leaving.size === 0) return nodes;
|
|
@@ -21044,11 +22395,14 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21044
22395
|
execute: () => this.deselectAll()
|
|
21045
22396
|
});
|
|
21046
22397
|
this.registry = registry;
|
|
22398
|
+
this.#syncSearchCommands();
|
|
21047
22399
|
}
|
|
21048
22400
|
disconnectedCallback() {
|
|
21049
22401
|
super.disconnectedCallback();
|
|
21050
22402
|
this.#modelUnsubscribe?.();
|
|
21051
22403
|
this.#modelUnsubscribe = void 0;
|
|
22404
|
+
this.#unregisterSearchCommands?.();
|
|
22405
|
+
this.#unregisterSearchCommands = void 0;
|
|
21052
22406
|
if (this.#ownedRegistry) {
|
|
21053
22407
|
this.#ownedRegistry.unregister(`select-all`);
|
|
21054
22408
|
this.#ownedRegistry.unregister(`deselect-all`);
|
|
@@ -21146,6 +22500,28 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21146
22500
|
if (!keyPath) return false;
|
|
21147
22501
|
return this.navigateTo(keyPath);
|
|
21148
22502
|
}
|
|
22503
|
+
/** Expand ancestors + move the cursor to `key`, without touching the selection (search "navigate" mode). */
|
|
22504
|
+
revealKey(key) {
|
|
22505
|
+
if (!this.root?.children) return false;
|
|
22506
|
+
const path = this.#findNodePath(this.root.children, (n) => n.item.key === key);
|
|
22507
|
+
if (!path) return false;
|
|
22508
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
22509
|
+
this.#applyExclusivity(path[i]);
|
|
22510
|
+
this._expandedKeys.add(path[i]);
|
|
22511
|
+
}
|
|
22512
|
+
this.tickled.setTickled(key);
|
|
22513
|
+
this.requestUpdate();
|
|
22514
|
+
this.updateComplete.then(() => this.#scrollKeyIntoView(key));
|
|
22515
|
+
return true;
|
|
22516
|
+
}
|
|
22517
|
+
/** Replace the selection with `nodes` (subject to `selectionFilter`) — search "select" mode. */
|
|
22518
|
+
selectMany(nodes) {
|
|
22519
|
+
const prev = new Set(this._selectedNodes);
|
|
22520
|
+
this._selectedNodes = new Set([...nodes].filter((n) => this.#canSelect(n)));
|
|
22521
|
+
this.selectedNode = [...this._selectedNodes].at(-1);
|
|
22522
|
+
this.#dispatchSelect(prev);
|
|
22523
|
+
this.requestUpdate();
|
|
22524
|
+
}
|
|
21149
22525
|
getNodes() {
|
|
21150
22526
|
if (!this.root?.children) return [];
|
|
21151
22527
|
const result = [];
|
|
@@ -21549,7 +22925,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21549
22925
|
node: n,
|
|
21550
22926
|
depth: d
|
|
21551
22927
|
});
|
|
21552
|
-
if (
|
|
22928
|
+
if (n.children && this._isExpanded(n, d)) for (const child of n.children) visit(child, d + 1);
|
|
21553
22929
|
};
|
|
21554
22930
|
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
21555
22931
|
return result;
|
|
@@ -21606,6 +22982,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21606
22982
|
>
|
|
21607
22983
|
${this.root ? this.#renderNodes(nodes) : A}
|
|
21608
22984
|
</div>
|
|
22985
|
+
${this.search.renderField()}
|
|
21609
22986
|
`;
|
|
21610
22987
|
}
|
|
21611
22988
|
#onDblClick;
|
|
@@ -21617,7 +22994,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21617
22994
|
`;
|
|
21618
22995
|
return b`
|
|
21619
22996
|
<ul class="tree-list" role="tree">
|
|
21620
|
-
${this.#weaveLeaving(nodes).
|
|
22997
|
+
${repeat(this.#weaveLeaving(nodes), ({ node }) => node.item.key, ({ node, depth }) => this._renderNode(node, depth))}
|
|
21621
22998
|
</ul>
|
|
21622
22999
|
`;
|
|
21623
23000
|
}
|
|
@@ -21627,7 +23004,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21627
23004
|
const isBranch = node.item.isLeaf !== true;
|
|
21628
23005
|
const isExpanded = this._isExpanded(node, depth);
|
|
21629
23006
|
const isPending = this.#pendingNode === node;
|
|
21630
|
-
const isFiltered = this.
|
|
23007
|
+
const isFiltered = this._isFilteredOut(node, depth);
|
|
21631
23008
|
const isLeaving = this._isLeaving(node);
|
|
21632
23009
|
return b`
|
|
21633
23010
|
<li
|
|
@@ -21662,6 +23039,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21662
23039
|
themeFallbacks,
|
|
21663
23040
|
tickledStyles,
|
|
21664
23041
|
tickledItemStyles,
|
|
23042
|
+
listSearchStyles,
|
|
21665
23043
|
i$3`
|
|
21666
23044
|
:host {
|
|
21667
23045
|
--tree-bg: var(--surface-3);
|
|
@@ -21672,16 +23050,23 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
21672
23050
|
--tree-caret-size: 10px;
|
|
21673
23051
|
--tree-radius: var(--radius-s);
|
|
21674
23052
|
|
|
23053
|
+
position: relative;
|
|
21675
23054
|
display: block;
|
|
21676
|
-
overflow:
|
|
23055
|
+
overflow: hidden;
|
|
21677
23056
|
box-sizing: border-box;
|
|
21678
23057
|
background: var(--tree-bg);
|
|
21679
23058
|
color: var(--tree-text);
|
|
21680
23059
|
}
|
|
21681
23060
|
|
|
23061
|
+
/* min-height (not height) so the tree still grows to its content when the
|
|
23062
|
+
host has no explicit height; when it does, this fills it and scrolls.
|
|
23063
|
+
The scroll lives here, not on :host, so the search popover (a sibling of
|
|
23064
|
+
#container, positioned against :host) stays put while the list scrolls. */
|
|
21682
23065
|
#container {
|
|
21683
23066
|
outline: none;
|
|
21684
23067
|
min-height: 100%;
|
|
23068
|
+
max-height: 100%;
|
|
23069
|
+
overflow: auto;
|
|
21685
23070
|
}
|
|
21686
23071
|
|
|
21687
23072
|
.tree-list {
|
|
@@ -21795,6 +23180,11 @@ __decorate([property({
|
|
|
21795
23180
|
attribute: `disable-keyboard-navigation`
|
|
21796
23181
|
})], TreeBaseElement.prototype, "disableKeyboardNavigation", void 0);
|
|
21797
23182
|
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "registry", void 0);
|
|
23183
|
+
__decorate([property({
|
|
23184
|
+
type: String,
|
|
23185
|
+
attribute: `search-modes`,
|
|
23186
|
+
reflect: true
|
|
23187
|
+
})], TreeBaseElement.prototype, "searchModes", void 0);
|
|
21798
23188
|
__decorate([state()], TreeBaseElement.prototype, "_expandedKeys", void 0);
|
|
21799
23189
|
__decorate([state()], TreeBaseElement.prototype, "_focusedDepth", void 0);
|
|
21800
23190
|
TreeBaseElement = __decorate([safeCustomElement(`ixfx-tree-base`)], TreeBaseElement);
|
|
@@ -21875,9 +23265,10 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
|
21875
23265
|
const isPending = this._pendingNode === node;
|
|
21876
23266
|
const hasIconAtDepth = this.#depthsWithIcons.has(depth);
|
|
21877
23267
|
const isLeaving = this._isLeaving(node);
|
|
23268
|
+
const isFiltered = this._isFilteredOut(node, depth);
|
|
21878
23269
|
return b`
|
|
21879
23270
|
<li
|
|
21880
|
-
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``}"
|
|
23271
|
+
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``} ${isFiltered ? `filtered` : ``}"
|
|
21881
23272
|
data-key=${node.item.key}
|
|
21882
23273
|
role="treeitem"
|
|
21883
23274
|
aria-selected=${selected}
|
|
@@ -22146,13 +23537,11 @@ function createTreeListAdapter(toTreeItem, options) {
|
|
|
22146
23537
|
//#region src/vertical-list/vertical-list.ts
|
|
22147
23538
|
const ITEM_SELECTOR = `li, [data-list-item]`;
|
|
22148
23539
|
let VerticalListElement = class VerticalListElement extends i {
|
|
22149
|
-
constructor(...
|
|
22150
|
-
super(...
|
|
23540
|
+
constructor(..._args) {
|
|
23541
|
+
super(..._args);
|
|
22151
23542
|
this.selectionMode = `single`;
|
|
22152
23543
|
this.interactionMode = `standard`;
|
|
22153
23544
|
this.itemWrap = `stop`;
|
|
22154
|
-
this._searchVisible = false;
|
|
22155
|
-
this._searchQuery = ``;
|
|
22156
23545
|
this.#customWrappers = /* @__PURE__ */ new Map();
|
|
22157
23546
|
this.#wrappedElements = /* @__PURE__ */ new Set();
|
|
22158
23547
|
this.#animatedItems = /* @__PURE__ */ new WeakSet();
|
|
@@ -22184,6 +23573,11 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22184
23573
|
onStateChange: () => this._syncItemStates(),
|
|
22185
23574
|
itemWrap: this.itemWrap
|
|
22186
23575
|
});
|
|
23576
|
+
this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
|
|
23577
|
+
itemSelector: ITEM_SELECTOR,
|
|
23578
|
+
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
23579
|
+
highlightKey: `vertical-list-search`
|
|
23580
|
+
}) });
|
|
22187
23581
|
this._onPointerOver = (e) => {
|
|
22188
23582
|
const item = this._findListItem(e.composedPath());
|
|
22189
23583
|
if (item) this.tickled.handlePointerEnter(item);
|
|
@@ -22192,11 +23586,7 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22192
23586
|
this.tickled.handlePointerLeave(e);
|
|
22193
23587
|
};
|
|
22194
23588
|
this._onKeyDown = (e) => {
|
|
22195
|
-
if (
|
|
22196
|
-
e.preventDefault();
|
|
22197
|
-
this._openSearch();
|
|
22198
|
-
return;
|
|
22199
|
-
}
|
|
23589
|
+
if (this.search.isOpen) return;
|
|
22200
23590
|
if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
|
|
22201
23591
|
if (this.selection.canSelectAll()) {
|
|
22202
23592
|
e.preventDefault();
|
|
@@ -22204,7 +23594,6 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22204
23594
|
}
|
|
22205
23595
|
return;
|
|
22206
23596
|
}
|
|
22207
|
-
if (this._searchVisible) return;
|
|
22208
23597
|
switch (e.key) {
|
|
22209
23598
|
case `ArrowDown`:
|
|
22210
23599
|
e.preventDefault();
|
|
@@ -22252,40 +23641,31 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22252
23641
|
const item = this._findListItem(e.composedPath());
|
|
22253
23642
|
if (item) this._activateItem(item);
|
|
22254
23643
|
};
|
|
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
23644
|
}
|
|
22282
|
-
#search;
|
|
22283
23645
|
#customWrappers;
|
|
22284
23646
|
#wrappedElements;
|
|
22285
23647
|
/** Watches for items added to the light DOM so they can be animated in. */
|
|
22286
23648
|
#itemObserver;
|
|
22287
23649
|
/** Items that have already played (or been skipped for) their enter animation. */
|
|
22288
23650
|
#animatedItems;
|
|
23651
|
+
#searchRegistry;
|
|
23652
|
+
#unregisterSearchCommands;
|
|
23653
|
+
/** Command registry carrying this list's `list.search.*` commands. */
|
|
23654
|
+
get registry() {
|
|
23655
|
+
return this.#searchRegistry;
|
|
23656
|
+
}
|
|
23657
|
+
#effectiveSearchModes() {
|
|
23658
|
+
const raw = this.searchModes?.trim();
|
|
23659
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
23660
|
+
const requested = new Set(raw.split(/\s+/));
|
|
23661
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
23662
|
+
}
|
|
23663
|
+
#syncSearchCommands() {
|
|
23664
|
+
if (!this.#searchRegistry) return;
|
|
23665
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
23666
|
+
this.#unregisterSearchCommands?.();
|
|
23667
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
23668
|
+
}
|
|
22289
23669
|
connectedCallback() {
|
|
22290
23670
|
super.connectedCallback();
|
|
22291
23671
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
@@ -22297,6 +23677,8 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22297
23677
|
for (const el of this.querySelectorAll(ITEM_SELECTOR)) this.#animatedItems.add(el);
|
|
22298
23678
|
this.#itemObserver ??= new MutationObserver((records) => this.#onItemsMutated(records));
|
|
22299
23679
|
this.#itemObserver.observe(this, { childList: true });
|
|
23680
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
23681
|
+
this.#syncSearchCommands();
|
|
22300
23682
|
}
|
|
22301
23683
|
disconnectedCallback() {
|
|
22302
23684
|
super.disconnectedCallback();
|
|
@@ -22306,8 +23688,9 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22306
23688
|
this.removeEventListener(`click`, this._onClick);
|
|
22307
23689
|
this.removeEventListener(`dblclick`, this._onDblClick);
|
|
22308
23690
|
this.#itemObserver?.disconnect();
|
|
22309
|
-
this.#
|
|
22310
|
-
this.#
|
|
23691
|
+
this.#unregisterSearchCommands?.();
|
|
23692
|
+
this.#unregisterSearchCommands = void 0;
|
|
23693
|
+
this.#searchRegistry = void 0;
|
|
22311
23694
|
}
|
|
22312
23695
|
/**
|
|
22313
23696
|
* Animate in items appended to the light DOM (however they got there —
|
|
@@ -22330,17 +23713,9 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22330
23713
|
if (fresh.length >= total) return;
|
|
22331
23714
|
for (const el of fresh) animateListItemIn(el);
|
|
22332
23715
|
}
|
|
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
23716
|
updated(changed) {
|
|
22343
23717
|
super.updated(changed);
|
|
23718
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
22344
23719
|
if (changed.has(`interactionMode`)) {
|
|
22345
23720
|
this.selection.clearAnchor();
|
|
22346
23721
|
const previousSelected = this.selection.selected;
|
|
@@ -22408,11 +23783,13 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22408
23783
|
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
22409
23784
|
}
|
|
22410
23785
|
/**
|
|
22411
|
-
* Dispatch a command to this list
|
|
22412
|
-
*
|
|
22413
|
-
*
|
|
23786
|
+
* Dispatch a command to this list — used by `ixfx-grouped-item-lister` to
|
|
23787
|
+
* forward commands to all sub-components. Routes through the command registry
|
|
23788
|
+
* (so `list.search.*` and any other registered commands work).
|
|
22414
23789
|
*/
|
|
22415
|
-
dispatch(
|
|
23790
|
+
dispatch(commandId, args) {
|
|
23791
|
+
this.#searchRegistry?.invoke(commandId, args);
|
|
23792
|
+
}
|
|
22416
23793
|
select(item) {
|
|
22417
23794
|
this.selection.replace(item);
|
|
22418
23795
|
}
|
|
@@ -22622,22 +23999,15 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22622
23999
|
composed: true
|
|
22623
24000
|
}));
|
|
22624
24001
|
}
|
|
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
24002
|
_activateItem(item) {
|
|
22636
24003
|
if (this.interactionMode !== `manual`) this.selection.replace(item);
|
|
22637
24004
|
this._dispatch(`list-activate`, { item });
|
|
22638
24005
|
}
|
|
22639
24006
|
static {
|
|
22640
|
-
this.styles = [
|
|
24007
|
+
this.styles = [
|
|
24008
|
+
themeFallbacks,
|
|
24009
|
+
listSearchStyles,
|
|
24010
|
+
i$3`
|
|
22641
24011
|
:host {
|
|
22642
24012
|
display: block;
|
|
22643
24013
|
position: relative;
|
|
@@ -22688,6 +24058,10 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22688
24058
|
display: flex;
|
|
22689
24059
|
align-items: center;
|
|
22690
24060
|
padding: var(--ixfx-list-item-padding);
|
|
24061
|
+
/* Inset each row from the scroll container's edges (margin, not padding:
|
|
24062
|
+
padding is inside the border box, so the tickle/selection outlines
|
|
24063
|
+
would still sit flush against the edge and be clipped). */
|
|
24064
|
+
margin: 0 var(--list-gutter, 4px);
|
|
22691
24065
|
min-height: var(--list-item-height, 28px);
|
|
22692
24066
|
cursor: pointer;
|
|
22693
24067
|
user-select: none;
|
|
@@ -22700,6 +24074,7 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22700
24074
|
/* Custom shadow-DOM components handle their own internal layout/padding */
|
|
22701
24075
|
::slotted([data-list-item]) {
|
|
22702
24076
|
display: block;
|
|
24077
|
+
margin: 0 var(--list-gutter, 4px);
|
|
22703
24078
|
cursor: pointer;
|
|
22704
24079
|
user-select: none;
|
|
22705
24080
|
transition: background var(--transition, 0.1s ease-out),
|
|
@@ -22755,49 +24130,8 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22755
24130
|
color: inherit;
|
|
22756
24131
|
outline: var(--item-border-tickled);
|
|
22757
24132
|
}
|
|
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
|
-
`];
|
|
24133
|
+
`
|
|
24134
|
+
];
|
|
22801
24135
|
}
|
|
22802
24136
|
render() {
|
|
22803
24137
|
return b`
|
|
@@ -22805,21 +24139,7 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
22805
24139
|
<ul part="list">
|
|
22806
24140
|
<slot></slot>
|
|
22807
24141
|
</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>
|
|
24142
|
+
${this.search.renderField()}
|
|
22823
24143
|
</div>
|
|
22824
24144
|
`;
|
|
22825
24145
|
}
|
|
@@ -22838,9 +24158,11 @@ __decorate([property({
|
|
|
22838
24158
|
attribute: `item-wrap`
|
|
22839
24159
|
})], VerticalListElement.prototype, "itemWrap", void 0);
|
|
22840
24160
|
__decorate([property({ attribute: false })], VerticalListElement.prototype, "checkboxRenderer", void 0);
|
|
22841
|
-
__decorate([
|
|
22842
|
-
|
|
22843
|
-
|
|
24161
|
+
__decorate([property({
|
|
24162
|
+
type: String,
|
|
24163
|
+
attribute: `search-modes`,
|
|
24164
|
+
reflect: true
|
|
24165
|
+
})], VerticalListElement.prototype, "searchModes", void 0);
|
|
22844
24166
|
VerticalListElement = __decorate([safeCustomElement(`ixfx-vertical-list`)], VerticalListElement);
|
|
22845
24167
|
//#endregion
|
|
22846
24168
|
//#region src/grouped-item-lister/vertical-list-adapter.ts
|
|
@@ -27255,15 +28577,62 @@ let LedElement = class LedElement extends i {
|
|
|
27255
28577
|
this.value = 0;
|
|
27256
28578
|
this.lit = false;
|
|
27257
28579
|
this.color = `oklch(50% 0.15 25)`;
|
|
28580
|
+
this.glow = false;
|
|
28581
|
+
this.mode = `brightness`;
|
|
27258
28582
|
this.blinking = false;
|
|
27259
28583
|
this.rate = 500;
|
|
28584
|
+
this.attackTime = 40;
|
|
28585
|
+
this.releaseTime = 400;
|
|
28586
|
+
this.pulseRetrigger = false;
|
|
28587
|
+
this.#appliedMode = `brightness`;
|
|
28588
|
+
this.#pulseStart = 0;
|
|
28589
|
+
this.#pulsePeak = 0;
|
|
28590
|
+
this.#pulseFrom = 0;
|
|
28591
|
+
this.#pulseLevel = 0;
|
|
28592
|
+
this.#pulseTick = (now) => {
|
|
28593
|
+
const attack = Math.max(0, this.attackTime);
|
|
28594
|
+
const release = Math.max(0, this.releaseTime);
|
|
28595
|
+
const from = this.#pulseFrom;
|
|
28596
|
+
const peak = this.#pulsePeak;
|
|
28597
|
+
const elapsed = now - this.#pulseStart;
|
|
28598
|
+
let level;
|
|
28599
|
+
if (attack > 0 && elapsed < attack) level = from + (peak - from) * (elapsed / attack);
|
|
28600
|
+
else if (elapsed < attack + release) level = release > 0 ? peak * (1 - (elapsed - attack) / release) : 0;
|
|
28601
|
+
else level = 0;
|
|
28602
|
+
level = Math.max(0, Math.min(Math.max(from, peak), level));
|
|
28603
|
+
this.#pulseLevel = level;
|
|
28604
|
+
this.style.setProperty(`--value`, String(level));
|
|
28605
|
+
if (elapsed < attack + release) this.#pulseRaf = requestAnimationFrame(this.#pulseTick);
|
|
28606
|
+
else {
|
|
28607
|
+
this.#pulseRaf = void 0;
|
|
28608
|
+
this.#pulseLevel = 0;
|
|
28609
|
+
}
|
|
28610
|
+
};
|
|
27260
28611
|
}
|
|
27261
28612
|
#blinkInterval;
|
|
28613
|
+
#appliedMode;
|
|
28614
|
+
#pulseRaf;
|
|
28615
|
+
#pulseStart;
|
|
28616
|
+
#pulsePeak;
|
|
28617
|
+
#pulseFrom;
|
|
28618
|
+
#pulseLevel;
|
|
27262
28619
|
willUpdate(changedProperties) {
|
|
27263
|
-
if (changedProperties.has(`
|
|
27264
|
-
|
|
27265
|
-
|
|
28620
|
+
if (changedProperties.has(`blinking`)) {
|
|
28621
|
+
if (this.blinking) this.mode = `blink`;
|
|
28622
|
+
else if (this.mode === `blink`) this.mode = `brightness`;
|
|
28623
|
+
}
|
|
28624
|
+
if (this.mode !== this.#appliedMode) {
|
|
28625
|
+
this.#appliedMode = this.mode;
|
|
28626
|
+
this.#onModeChange();
|
|
28627
|
+
}
|
|
28628
|
+
if (changedProperties.has(`lit`)) {
|
|
28629
|
+
if (this.lit) this.value = 1;
|
|
28630
|
+
else if (changedProperties.get(`lit`) !== void 0) this.value = 0;
|
|
28631
|
+
}
|
|
28632
|
+
if (changedProperties.has(`color`)) this.style.setProperty(`--_color-attr`, this.color);
|
|
28633
|
+
if (this.mode !== `pulse` && (changedProperties.has(`value`) || changedProperties.has(`lit`))) this.style.setProperty(`--value`, String(this.value));
|
|
27266
28634
|
if (changedProperties.has(`rate`)) {
|
|
28635
|
+
this.#updateBlinking();
|
|
27267
28636
|
const transitionMs = this.rate < 300 ? this.rate * .5 : 150;
|
|
27268
28637
|
this.style.setProperty(`--transition-duration`, `${transitionMs}ms`);
|
|
27269
28638
|
}
|
|
@@ -27271,18 +28640,43 @@ let LedElement = class LedElement extends i {
|
|
|
27271
28640
|
disconnectedCallback() {
|
|
27272
28641
|
super.disconnectedCallback();
|
|
27273
28642
|
this.#stopBlinking();
|
|
28643
|
+
this.#stopPulse();
|
|
27274
28644
|
}
|
|
27275
|
-
|
|
27276
|
-
|
|
27277
|
-
|
|
27278
|
-
|
|
27279
|
-
|
|
27280
|
-
|
|
27281
|
-
|
|
27282
|
-
|
|
27283
|
-
|
|
28645
|
+
/**
|
|
28646
|
+
* Pulse-mode trigger: lights the LED to {@link value} using an attack/release
|
|
28647
|
+
* envelope, then decays back to unlit. Calling again restarts the envelope
|
|
28648
|
+
* (from 0 if {@link pulseRetrigger} is set, otherwise from the current level).
|
|
28649
|
+
* No-op unless {@link mode} is `pulse`.
|
|
28650
|
+
*/
|
|
28651
|
+
pulse() {
|
|
28652
|
+
if (this.mode !== `pulse`) return;
|
|
28653
|
+
this.#pulsePeak = this.value;
|
|
28654
|
+
this.#pulseFrom = this.pulseRetrigger ? 0 : this.#pulseLevel;
|
|
28655
|
+
this.#pulseStart = performance.now();
|
|
28656
|
+
if (this.#pulseRaf === void 0) this.#pulseRaf = requestAnimationFrame(this.#pulseTick);
|
|
28657
|
+
}
|
|
28658
|
+
#onModeChange() {
|
|
28659
|
+
this.#stopPulse();
|
|
28660
|
+
this.#pulseLevel = 0;
|
|
28661
|
+
this.blinking = this.mode === `blink`;
|
|
28662
|
+
this.#updateBlinking();
|
|
28663
|
+
this.style.setProperty(`--value`, this.mode === `pulse` ? `0` : String(this.value));
|
|
28664
|
+
}
|
|
28665
|
+
#pulseTick;
|
|
28666
|
+
#stopPulse() {
|
|
28667
|
+
if (this.#pulseRaf !== void 0) {
|
|
28668
|
+
cancelAnimationFrame(this.#pulseRaf);
|
|
28669
|
+
this.#pulseRaf = void 0;
|
|
27284
28670
|
}
|
|
27285
28671
|
}
|
|
28672
|
+
#updateBlinking() {
|
|
28673
|
+
const wasBlinking = this.#blinkInterval !== void 0;
|
|
28674
|
+
this.#stopBlinking();
|
|
28675
|
+
if (this.mode === `blink` && this.rate > 0) this.#blinkInterval = window.setInterval(() => {
|
|
28676
|
+
this.value = this.value === 0 ? 1 : 0;
|
|
28677
|
+
}, this.rate);
|
|
28678
|
+
else if (wasBlinking || this.mode === `blink`) this.value = 0;
|
|
28679
|
+
}
|
|
27286
28680
|
#stopBlinking() {
|
|
27287
28681
|
if (this.#blinkInterval !== void 0) {
|
|
27288
28682
|
clearInterval(this.#blinkInterval);
|
|
@@ -27296,6 +28690,8 @@ let LedElement = class LedElement extends i {
|
|
|
27296
28690
|
this.styles = i$3`
|
|
27297
28691
|
:host {
|
|
27298
28692
|
--_size: var(--size, 1ex);
|
|
28693
|
+
/* Resolved colour: explicit --colour wins, then the \`color\` attribute, then a default. */
|
|
28694
|
+
--_colour: var(--colour, var(--_color-attr, oklch(50% 0.15 25)));
|
|
27299
28695
|
display: inline-block;
|
|
27300
28696
|
min-width: 1ex;
|
|
27301
28697
|
min-height: 1ex;
|
|
@@ -27305,12 +28701,61 @@ let LedElement = class LedElement extends i {
|
|
|
27305
28701
|
}
|
|
27306
28702
|
|
|
27307
28703
|
div {
|
|
28704
|
+
position: relative;
|
|
27308
28705
|
width: 100%;
|
|
27309
28706
|
height: 100%;
|
|
27310
28707
|
border-radius: 50%;
|
|
27311
|
-
background: color-mix(in oklch, var(--
|
|
28708
|
+
background: color-mix(in oklch, var(--_colour), white calc((1 - var(--value, 0)) * 50%));
|
|
27312
28709
|
transition: background var(--transition-duration, 150ms) ease-out;
|
|
27313
28710
|
}
|
|
28711
|
+
|
|
28712
|
+
/* Glow: a more realistic domed LED with a specular highlight and coloured bloom. */
|
|
28713
|
+
:host([glow]) {
|
|
28714
|
+
/* Body darkens toward black as the LED dims, so an unlit LED reads as a dark dome. */
|
|
28715
|
+
--_body: color-mix(in oklch, var(--_colour), black calc((1 - var(--value, 0)) * 60%));
|
|
28716
|
+
overflow: visible;
|
|
28717
|
+
}
|
|
28718
|
+
|
|
28719
|
+
:host([glow]) div {
|
|
28720
|
+
background:
|
|
28721
|
+
radial-gradient(circle at 32% 28%,
|
|
28722
|
+
color-mix(in oklch, var(--_body), white 55%) 0%,
|
|
28723
|
+
var(--_body) 45%,
|
|
28724
|
+
color-mix(in oklch, var(--_body), black 32%) 100%);
|
|
28725
|
+
box-shadow:
|
|
28726
|
+
/* inner rim shading for depth */
|
|
28727
|
+
inset 0 calc(var(--_size) * -0.1) calc(var(--_size) * 0.18)
|
|
28728
|
+
color-mix(in oklch, var(--_body), black 45%),
|
|
28729
|
+
inset 0 calc(var(--_size) * 0.08) calc(var(--_size) * 0.14)
|
|
28730
|
+
color-mix(in oklch, var(--_body), white 25%),
|
|
28731
|
+
/* coloured bloom, scaled by value so it only glows when lit */
|
|
28732
|
+
0 0 calc(var(--_size) * 0.6 * var(--value, 0)) calc(var(--_size) * 0.12 * var(--value, 0))
|
|
28733
|
+
var(--_colour),
|
|
28734
|
+
0 0 calc(var(--_size) * 1.7 * var(--value, 0)) calc(var(--_size) * 0.5 * var(--value, 0))
|
|
28735
|
+
color-mix(in oklch, var(--_colour), transparent 45%);
|
|
28736
|
+
transition:
|
|
28737
|
+
background var(--transition-duration, 150ms) ease-out,
|
|
28738
|
+
box-shadow var(--transition-duration, 150ms) ease-out;
|
|
28739
|
+
}
|
|
28740
|
+
|
|
28741
|
+
/* Specular highlight blob near the top-left. */
|
|
28742
|
+
:host([glow]) div::after {
|
|
28743
|
+
content: "";
|
|
28744
|
+
position: absolute;
|
|
28745
|
+
inset: 0;
|
|
28746
|
+
border-radius: 50%;
|
|
28747
|
+
background: radial-gradient(ellipse 55% 45% at 30% 22%,
|
|
28748
|
+
color-mix(in oklch, white, transparent 10%) 0%,
|
|
28749
|
+
transparent 60%);
|
|
28750
|
+
pointer-events: none;
|
|
28751
|
+
}
|
|
28752
|
+
|
|
28753
|
+
/* Pulse mode animates --value frame-by-frame, so drop the CSS transition
|
|
28754
|
+
that would otherwise smear the envelope. Placed last to win over the
|
|
28755
|
+
glow rule for elements matching both. */
|
|
28756
|
+
:host([mode="pulse"]) div {
|
|
28757
|
+
transition: none;
|
|
28758
|
+
}
|
|
27314
28759
|
`;
|
|
27315
28760
|
}
|
|
27316
28761
|
};
|
|
@@ -27321,6 +28766,15 @@ __decorate([property({
|
|
|
27321
28766
|
reflect: true
|
|
27322
28767
|
})], LedElement.prototype, "lit", void 0);
|
|
27323
28768
|
__decorate([property({ type: String })], LedElement.prototype, "color", void 0);
|
|
28769
|
+
__decorate([property({
|
|
28770
|
+
type: Boolean,
|
|
28771
|
+
attribute: `glow`,
|
|
28772
|
+
reflect: true
|
|
28773
|
+
})], LedElement.prototype, "glow", void 0);
|
|
28774
|
+
__decorate([property({
|
|
28775
|
+
type: String,
|
|
28776
|
+
reflect: true
|
|
28777
|
+
})], LedElement.prototype, "mode", void 0);
|
|
27324
28778
|
__decorate([property({
|
|
27325
28779
|
type: Boolean,
|
|
27326
28780
|
attribute: `blinking`,
|
|
@@ -27330,6 +28784,18 @@ __decorate([property({
|
|
|
27330
28784
|
type: Number,
|
|
27331
28785
|
attribute: `rate`
|
|
27332
28786
|
})], LedElement.prototype, "rate", void 0);
|
|
28787
|
+
__decorate([property({
|
|
28788
|
+
type: Number,
|
|
28789
|
+
attribute: `attack-time`
|
|
28790
|
+
})], LedElement.prototype, "attackTime", void 0);
|
|
28791
|
+
__decorate([property({
|
|
28792
|
+
type: Number,
|
|
28793
|
+
attribute: `release-time`
|
|
28794
|
+
})], LedElement.prototype, "releaseTime", void 0);
|
|
28795
|
+
__decorate([property({
|
|
28796
|
+
type: Boolean,
|
|
28797
|
+
attribute: `pulse-retrigger`
|
|
28798
|
+
})], LedElement.prototype, "pulseRetrigger", void 0);
|
|
27333
28799
|
LedElement = __decorate([safeCustomElement(`ixfx-led`)], LedElement);
|
|
27334
28800
|
//#endregion
|
|
27335
28801
|
//#region src/miller/miller-base.ts
|
|
@@ -27338,6 +28804,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27338
28804
|
constructor(..._args) {
|
|
27339
28805
|
super(..._args);
|
|
27340
28806
|
this.headers = false;
|
|
28807
|
+
this.stretchLastColumn = false;
|
|
27341
28808
|
this.interactionMode = `standard`;
|
|
27342
28809
|
this.selectionFilter = `leaf`;
|
|
27343
28810
|
this.exclusivity = `none`;
|
|
@@ -27349,6 +28816,10 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27349
28816
|
this.#resizingDepth = -1;
|
|
27350
28817
|
this.#resizeStartX = 0;
|
|
27351
28818
|
this.#resizeStartWidth = 0;
|
|
28819
|
+
this.search = new ListSearchController(this, { adapter: createTreeListSearchAdapter(this, {
|
|
28820
|
+
labelSelector: `.label`,
|
|
28821
|
+
highlightKey: `incr-search-highlight`
|
|
28822
|
+
}) });
|
|
27352
28823
|
this.tickled = new TickledController(this, { getItems: () => {
|
|
27353
28824
|
const items = [];
|
|
27354
28825
|
for (let depth = 0; depth < this._columns.length; depth++) {
|
|
@@ -27358,6 +28829,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27358
28829
|
return items;
|
|
27359
28830
|
} });
|
|
27360
28831
|
this.#onKeyDown = (event) => {
|
|
28832
|
+
if (this.search.isOpen) return;
|
|
27361
28833
|
const currentDepth = this.#getTickledDepthOrDefault();
|
|
27362
28834
|
const col = this._columns[currentDepth];
|
|
27363
28835
|
if (!col || col.length === 0) {
|
|
@@ -27564,9 +29036,31 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27564
29036
|
#resizeStartX;
|
|
27565
29037
|
#resizeStartWidth;
|
|
27566
29038
|
#modelUnsubscribe;
|
|
29039
|
+
#searchRegistry;
|
|
29040
|
+
#unregisterSearchCommands;
|
|
29041
|
+
/** Command registry carrying this component's `list.search.*` commands. */
|
|
29042
|
+
get registry() {
|
|
29043
|
+
return this.#searchRegistry;
|
|
29044
|
+
}
|
|
29045
|
+
#effectiveSearchModes() {
|
|
29046
|
+
const raw = this.searchModes?.trim();
|
|
29047
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
29048
|
+
const requested = new Set(raw.split(/\s+/));
|
|
29049
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
29050
|
+
}
|
|
29051
|
+
#syncSearchCommands() {
|
|
29052
|
+
if (!this.#searchRegistry) return;
|
|
29053
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
29054
|
+
this.#unregisterSearchCommands?.();
|
|
29055
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
29056
|
+
}
|
|
27567
29057
|
#buildTickledId(node, depth) {
|
|
27568
29058
|
return `${depth}:${node.item.key}`;
|
|
27569
29059
|
}
|
|
29060
|
+
/** The node column 0 renders the children of: `viewRoot` when rebased, else `root`. */
|
|
29061
|
+
get #displayRoot() {
|
|
29062
|
+
return this.viewRoot ?? this.root;
|
|
29063
|
+
}
|
|
27570
29064
|
#getTickledDepth() {
|
|
27571
29065
|
const id = this.tickled.tickledId;
|
|
27572
29066
|
if (!id) return void 0;
|
|
@@ -27575,16 +29069,20 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27575
29069
|
return Number(id.substring(0, colonIndex));
|
|
27576
29070
|
}
|
|
27577
29071
|
updated(changedProps) {
|
|
29072
|
+
if (changedProps.has(`searchModes`)) this.#syncSearchCommands();
|
|
27578
29073
|
if (changedProps.has(`root`) && this.root) {
|
|
27579
29074
|
this.#abortPending();
|
|
27580
|
-
this.
|
|
29075
|
+
if (this.viewRoot !== void 0) this.viewRoot = void 0;
|
|
27581
29076
|
this._selectedByDepth.clear();
|
|
27582
29077
|
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
27583
29078
|
this._selectedNodes.clear();
|
|
27584
29079
|
this.#lastSelectedNode = void 0;
|
|
27585
29080
|
this.#anchor = void 0;
|
|
27586
29081
|
this.tickled.clearTickled();
|
|
29082
|
+
const display = this.#displayRoot;
|
|
29083
|
+
this._columns = display?.children ? [display.children] : [];
|
|
27587
29084
|
}
|
|
29085
|
+
if (changedProps.has(`viewRoot`)) this.#applyViewRoot();
|
|
27588
29086
|
if (changedProps.has(`model`)) {
|
|
27589
29087
|
this.#modelUnsubscribe?.();
|
|
27590
29088
|
this.#modelUnsubscribe = void 0;
|
|
@@ -27626,10 +29124,18 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27626
29124
|
});
|
|
27627
29125
|
}
|
|
27628
29126
|
}
|
|
29127
|
+
connectedCallback() {
|
|
29128
|
+
super.connectedCallback();
|
|
29129
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
29130
|
+
this.#syncSearchCommands();
|
|
29131
|
+
}
|
|
27629
29132
|
disconnectedCallback() {
|
|
27630
29133
|
super.disconnectedCallback();
|
|
27631
29134
|
this.#modelUnsubscribe?.();
|
|
27632
29135
|
this.#modelUnsubscribe = void 0;
|
|
29136
|
+
this.#unregisterSearchCommands?.();
|
|
29137
|
+
this.#unregisterSearchCommands = void 0;
|
|
29138
|
+
this.#searchRegistry = void 0;
|
|
27633
29139
|
}
|
|
27634
29140
|
getConfig() {
|
|
27635
29141
|
const columnWidths = {};
|
|
@@ -27725,15 +29231,17 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27725
29231
|
return results;
|
|
27726
29232
|
}
|
|
27727
29233
|
/**
|
|
27728
|
-
* Programmatically navigate to a node by key path
|
|
29234
|
+
* Programmatically navigate to a node by key path, relative to the current
|
|
29235
|
+
* view root (rebased or full tree).
|
|
27729
29236
|
*/
|
|
27730
29237
|
navigateTo(keys) {
|
|
27731
|
-
|
|
29238
|
+
const display = this.#displayRoot;
|
|
29239
|
+
if (!display?.children) return false;
|
|
27732
29240
|
this.#abortPending();
|
|
27733
29241
|
this._selectedByDepth.clear();
|
|
27734
29242
|
this.tickled.clearTickled();
|
|
27735
|
-
const newColumns = [
|
|
27736
|
-
let currentColumn =
|
|
29243
|
+
const newColumns = [display.children];
|
|
29244
|
+
let currentColumn = display.children;
|
|
27737
29245
|
let success = true;
|
|
27738
29246
|
let lastNode;
|
|
27739
29247
|
let lastDepth = 0;
|
|
@@ -27768,13 +29276,51 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27768
29276
|
* Programmatically navigate to the first node (breadth-first) matching a predicate.
|
|
27769
29277
|
*/
|
|
27770
29278
|
navigateToNode(predicate) {
|
|
27771
|
-
|
|
27772
|
-
|
|
29279
|
+
const display = this.#displayRoot;
|
|
29280
|
+
if (!display?.children) return false;
|
|
29281
|
+
const keyPath = this.#findNodePath(display.children, predicate);
|
|
27773
29282
|
if (!keyPath) return false;
|
|
27774
29283
|
return this.navigateTo(keyPath);
|
|
27775
29284
|
}
|
|
29285
|
+
/** Open the columns down to `key` and move the cursor to it, without changing the selection (search "navigate" mode). */
|
|
29286
|
+
revealKey(key) {
|
|
29287
|
+
const display = this.#displayRoot;
|
|
29288
|
+
if (!display?.children) return false;
|
|
29289
|
+
const path = this.#findNodePath(display.children, (n) => n.item.key === key);
|
|
29290
|
+
if (!path) return false;
|
|
29291
|
+
const columns = [display.children];
|
|
29292
|
+
let current = display.children;
|
|
29293
|
+
let lastNode;
|
|
29294
|
+
let lastDepth = 0;
|
|
29295
|
+
for (let depth = 0; depth < path.length; depth++) {
|
|
29296
|
+
const node = current.find((n) => n.item.key === path[depth]);
|
|
29297
|
+
if (!node) return false;
|
|
29298
|
+
lastNode = node;
|
|
29299
|
+
lastDepth = depth;
|
|
29300
|
+
if (node.children && node.children.length > 0) {
|
|
29301
|
+
columns.push(node.children);
|
|
29302
|
+
current = node.children;
|
|
29303
|
+
} else break;
|
|
29304
|
+
}
|
|
29305
|
+
this._columns = columns;
|
|
29306
|
+
if (lastNode) this.tickled.setTickled(this.#buildTickledId(lastNode, lastDepth));
|
|
29307
|
+
this.requestUpdate();
|
|
29308
|
+
this.#scrollNavigatedItemIntoView();
|
|
29309
|
+
return true;
|
|
29310
|
+
}
|
|
29311
|
+
/** Replace the selection with `nodes` (search "select" mode). */
|
|
29312
|
+
selectMany(nodes) {
|
|
29313
|
+
const arr = this.selectionFilter === `none` ? [] : [...nodes];
|
|
29314
|
+
const prev = new Set(this._selectedNodes);
|
|
29315
|
+
this._selectedNodes = new Set(arr);
|
|
29316
|
+
this._selectedKeys = new Set(arr.map((n) => n.item.key));
|
|
29317
|
+
this.#lastSelectedNode = arr.at(-1);
|
|
29318
|
+
this.#dispatchSelectEvent(prev);
|
|
29319
|
+
this.requestUpdate();
|
|
29320
|
+
}
|
|
27776
29321
|
getNodes() {
|
|
27777
|
-
|
|
29322
|
+
const display = this.#displayRoot;
|
|
29323
|
+
if (!display?.children) return [];
|
|
27778
29324
|
const result = [];
|
|
27779
29325
|
const visit = (node, depth) => {
|
|
27780
29326
|
result.push({
|
|
@@ -27783,7 +29329,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27783
29329
|
});
|
|
27784
29330
|
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
27785
29331
|
};
|
|
27786
|
-
for (const child of
|
|
29332
|
+
for (const child of display.children) visit(child, 0);
|
|
27787
29333
|
return result;
|
|
27788
29334
|
}
|
|
27789
29335
|
#findNodePath(nodes, predicate, prefix = []) {
|
|
@@ -27796,6 +29342,124 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27796
29342
|
}
|
|
27797
29343
|
}
|
|
27798
29344
|
}
|
|
29345
|
+
/**
|
|
29346
|
+
* Re-render the columns for the current `#displayRoot`, clearing selection.
|
|
29347
|
+
* Runs on every `viewRoot` change.
|
|
29348
|
+
*/
|
|
29349
|
+
#applyViewRoot() {
|
|
29350
|
+
this.#abortPending();
|
|
29351
|
+
const prev = new Set(this._selectedNodes);
|
|
29352
|
+
this._selectedByDepth.clear();
|
|
29353
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
29354
|
+
this._selectedNodes.clear();
|
|
29355
|
+
this.#lastSelectedNode = void 0;
|
|
29356
|
+
this.#anchor = void 0;
|
|
29357
|
+
this.tickled.clearTickled();
|
|
29358
|
+
const display = this.#displayRoot;
|
|
29359
|
+
if (display?.children !== void 0) {
|
|
29360
|
+
this._columns = display.children ? [display.children] : [];
|
|
29361
|
+
this._loadingDepth = void 0;
|
|
29362
|
+
} else if (display) {
|
|
29363
|
+
this._columns = [];
|
|
29364
|
+
this.#loadUnloadedChildren(display, -1, false);
|
|
29365
|
+
} else {
|
|
29366
|
+
this._columns = [];
|
|
29367
|
+
this._loadingDepth = void 0;
|
|
29368
|
+
}
|
|
29369
|
+
if (prev.size > 0) this.#dispatchSelectEvent(prev);
|
|
29370
|
+
}
|
|
29371
|
+
/**
|
|
29372
|
+
* Rebase the view to show `target`'s children in column 0 (Finder column-view
|
|
29373
|
+
* behavior on double-click), keeping the full tree in `root`.
|
|
29374
|
+
*
|
|
29375
|
+
* - Pass a `MillerNode` (used as-is) or a key (looked up in the full tree).
|
|
29376
|
+
* - Pass `undefined` to restore the full-tree view.
|
|
29377
|
+
* - Rebasing to the true root is equivalent to restoring the full view.
|
|
29378
|
+
* - Leaves are refused (returns `false`).
|
|
29379
|
+
*
|
|
29380
|
+
* Selection is cleared; all depths, headers, and key paths are relative to
|
|
29381
|
+
* the new view root. Fires a `rebase` event when the view actually changes.
|
|
29382
|
+
* Returns `false` when nothing changed (unknown key, leaf, or same view).
|
|
29383
|
+
*/
|
|
29384
|
+
rebaseTo(target) {
|
|
29385
|
+
if (target === void 0) return this.#clearViewRoot();
|
|
29386
|
+
const node = typeof target === `string` ? this.#findNodeByKey(target) : target;
|
|
29387
|
+
if (!node || node.item.isLeaf === true) return false;
|
|
29388
|
+
if (node === this.viewRoot || node.item.key === this.viewRoot?.item.key) return false;
|
|
29389
|
+
if (node === this.root || !this.viewRoot && node.item.key === this.root?.item.key) return this.#clearViewRoot();
|
|
29390
|
+
this.viewRoot = node;
|
|
29391
|
+
this.#dispatchRebaseEvent();
|
|
29392
|
+
return true;
|
|
29393
|
+
}
|
|
29394
|
+
/**
|
|
29395
|
+
* Rebase one level up from the current view root (no-op at the full tree).
|
|
29396
|
+
* Fires a `rebase` event when the view actually changes.
|
|
29397
|
+
*/
|
|
29398
|
+
rebaseUp() {
|
|
29399
|
+
if (!this.viewRoot) return false;
|
|
29400
|
+
const parent = this.#findParentOf(this.viewRoot);
|
|
29401
|
+
if (!parent) return false;
|
|
29402
|
+
if (parent === this.root) return this.#clearViewRoot();
|
|
29403
|
+
this.viewRoot = parent;
|
|
29404
|
+
this.#dispatchRebaseEvent();
|
|
29405
|
+
return true;
|
|
29406
|
+
}
|
|
29407
|
+
#clearViewRoot() {
|
|
29408
|
+
if (this.viewRoot === void 0) return false;
|
|
29409
|
+
this.viewRoot = void 0;
|
|
29410
|
+
this.#dispatchRebaseEvent();
|
|
29411
|
+
return true;
|
|
29412
|
+
}
|
|
29413
|
+
#dispatchRebaseEvent() {
|
|
29414
|
+
const viewRoot = this.viewRoot;
|
|
29415
|
+
const detail = {
|
|
29416
|
+
viewRoot,
|
|
29417
|
+
path: viewRoot ? this.#pathToRoot(viewRoot) : []
|
|
29418
|
+
};
|
|
29419
|
+
this.dispatchEvent(new CustomEvent(`rebase`, {
|
|
29420
|
+
detail,
|
|
29421
|
+
bubbles: true,
|
|
29422
|
+
composed: true
|
|
29423
|
+
}));
|
|
29424
|
+
}
|
|
29425
|
+
#findNodeByKey(key) {
|
|
29426
|
+
if (!this.root) return void 0;
|
|
29427
|
+
if (this.root.item.key === key) return this.root;
|
|
29428
|
+
return this.#findNodeIn(this.root.children ?? [], key);
|
|
29429
|
+
}
|
|
29430
|
+
#findNodeIn(nodes, key) {
|
|
29431
|
+
for (const node of nodes) {
|
|
29432
|
+
if (node.item.key === key) return node;
|
|
29433
|
+
if (node.children) {
|
|
29434
|
+
const found = this.#findNodeIn(node.children, key);
|
|
29435
|
+
if (found) return found;
|
|
29436
|
+
}
|
|
29437
|
+
}
|
|
29438
|
+
}
|
|
29439
|
+
/** Find the parent of `target` (by reference, falling back to key) in the full tree. */
|
|
29440
|
+
#findParentOf(target) {
|
|
29441
|
+
if (!this.root || target === this.root) return void 0;
|
|
29442
|
+
return this.#findParentIn(this.root, target);
|
|
29443
|
+
}
|
|
29444
|
+
#findParentIn(node, target) {
|
|
29445
|
+
if (!node.children) return void 0;
|
|
29446
|
+
for (const child of node.children) {
|
|
29447
|
+
if (child === target || child.item.key === target.item.key) return node;
|
|
29448
|
+
const found = this.#findParentIn(child, target);
|
|
29449
|
+
if (found) return found;
|
|
29450
|
+
}
|
|
29451
|
+
}
|
|
29452
|
+
/** Ancestry chain from the true root down to and including `target`. */
|
|
29453
|
+
#pathToRoot(target) {
|
|
29454
|
+
const chain = [target];
|
|
29455
|
+
let current = this.#findParentOf(target);
|
|
29456
|
+
while (current) {
|
|
29457
|
+
chain.unshift(current);
|
|
29458
|
+
if (current === this.root) break;
|
|
29459
|
+
current = this.#findParentOf(current);
|
|
29460
|
+
}
|
|
29461
|
+
return chain;
|
|
29462
|
+
}
|
|
27799
29463
|
renderColumnContent(nodes, depth) {
|
|
27800
29464
|
console.warn(`ixfx-miller-base: renderColumnContent not overridden. depth=${depth}, nodes=${nodes.length}`);
|
|
27801
29465
|
return A;
|
|
@@ -27922,7 +29586,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
27922
29586
|
*/
|
|
27923
29587
|
#buildPath(depth) {
|
|
27924
29588
|
const path = [];
|
|
27925
|
-
if (this
|
|
29589
|
+
if (this.#displayRoot) path.push(this.#displayRoot);
|
|
27926
29590
|
for (let d = 0; d < depth; d++) {
|
|
27927
29591
|
const sel = this._selectedByDepth.get(d);
|
|
27928
29592
|
if (sel) path.push(sel);
|
|
@@ -28025,57 +29689,8 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28025
29689
|
this.requestUpdate();
|
|
28026
29690
|
return;
|
|
28027
29691
|
}
|
|
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 {
|
|
29692
|
+
if (node.children === void 0) this.#loadUnloadedChildren(node, depth, true);
|
|
29693
|
+
else {
|
|
28079
29694
|
const newColumns = this._columns.slice(0, depth + 1);
|
|
28080
29695
|
newColumns.push(node.children);
|
|
28081
29696
|
this._columns = newColumns;
|
|
@@ -28089,6 +29704,68 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28089
29704
|
}));
|
|
28090
29705
|
}
|
|
28091
29706
|
}
|
|
29707
|
+
/**
|
|
29708
|
+
* Fetch children for a node whose `children === undefined` and show them as
|
|
29709
|
+
* the column after `parentDepth` (with the loading placeholder meanwhile).
|
|
29710
|
+
* Shared by branch expansion (`parentDepth` = the branch's column) and view
|
|
29711
|
+
* rebasing (`parentDepth` = -1, so the children become column 0).
|
|
29712
|
+
*
|
|
29713
|
+
* The `depth` reported to `loadChildren`/`model.getChildren` is the node's
|
|
29714
|
+
* own depth: its column for expansions, -1 for a view root (it sits above
|
|
29715
|
+
* column 0, so loaders computing child depth as `depth + 1` stay correct).
|
|
29716
|
+
*/
|
|
29717
|
+
#loadUnloadedChildren(node, parentDepth, fireExpandEvent) {
|
|
29718
|
+
const nodeDepth = parentDepth + 1;
|
|
29719
|
+
this._columns = this._columns.slice(0, nodeDepth);
|
|
29720
|
+
this._loadingDepth = nodeDepth;
|
|
29721
|
+
this.#pendingNode = node;
|
|
29722
|
+
const controller = new AbortController();
|
|
29723
|
+
this.#pendingController = controller;
|
|
29724
|
+
if (fireExpandEvent) this.dispatchEvent(new CustomEvent(`expand`, {
|
|
29725
|
+
detail: {
|
|
29726
|
+
node,
|
|
29727
|
+
depth: parentDepth
|
|
29728
|
+
},
|
|
29729
|
+
bubbles: true,
|
|
29730
|
+
composed: true
|
|
29731
|
+
}));
|
|
29732
|
+
const applyChildren = (children) => {
|
|
29733
|
+
if (controller.signal.aborted) return;
|
|
29734
|
+
node.children = children;
|
|
29735
|
+
this.#pendingNode = void 0;
|
|
29736
|
+
this.#pendingController = void 0;
|
|
29737
|
+
this._loadingDepth = void 0;
|
|
29738
|
+
const cols = this._columns.slice(0, nodeDepth);
|
|
29739
|
+
if (children.length > 0) cols.push(children);
|
|
29740
|
+
this._columns = cols;
|
|
29741
|
+
};
|
|
29742
|
+
const handleError = (err) => {
|
|
29743
|
+
if (err.name !== `AbortError`) console.error(err);
|
|
29744
|
+
node.children = [];
|
|
29745
|
+
this.#pendingNode = void 0;
|
|
29746
|
+
this.#pendingController = void 0;
|
|
29747
|
+
this._loadingDepth = void 0;
|
|
29748
|
+
this.requestUpdate();
|
|
29749
|
+
};
|
|
29750
|
+
if (this.model || this.loadChildren) {
|
|
29751
|
+
if (this.#expandTimer !== void 0) clearTimeout(this.#expandTimer);
|
|
29752
|
+
this.#expandTimer = setTimeout(() => {
|
|
29753
|
+
this.#expandTimer = void 0;
|
|
29754
|
+
if (this.#pendingNode !== node) return;
|
|
29755
|
+
(this.model ? this.model.getChildren(node, nodeDepth - 1, controller.signal) : collectDataResult(this.loadChildren({
|
|
29756
|
+
node,
|
|
29757
|
+
depth: nodeDepth - 1
|
|
29758
|
+
}, controller.signal), controller.signal)).then((children) => applyChildren([...children])).catch(handleError);
|
|
29759
|
+
}, EXPAND_DEBOUNCE_MS);
|
|
29760
|
+
} else {
|
|
29761
|
+
this._loadingDepth = void 0;
|
|
29762
|
+
this.#pendingNode = void 0;
|
|
29763
|
+
this.#pendingController = void 0;
|
|
29764
|
+
const newColumns = this._columns.slice(0, nodeDepth);
|
|
29765
|
+
newColumns.push([]);
|
|
29766
|
+
this._columns = newColumns;
|
|
29767
|
+
}
|
|
29768
|
+
}
|
|
28092
29769
|
#onCheckboxClick(node) {
|
|
28093
29770
|
const currentlySelected = this._selectedKeys.has(node.item.key);
|
|
28094
29771
|
if (!!node.children?.length ? this.#allDescendantsSelected(node) : currentlySelected) this.#applyCheckedToggle(node, false);
|
|
@@ -28158,7 +29835,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28158
29835
|
#onResizeMove;
|
|
28159
29836
|
#onResizeEnd;
|
|
28160
29837
|
#getColumnHeader(depth) {
|
|
28161
|
-
if (depth === 0) return this
|
|
29838
|
+
if (depth === 0) return this.#displayRoot?.item.label ?? ``;
|
|
28162
29839
|
const explicit = this._selectedByDepth.get(depth - 1);
|
|
28163
29840
|
if (explicit) return explicit.item.label;
|
|
28164
29841
|
const prevCol = this._columns[depth - 1];
|
|
@@ -28169,9 +29846,11 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28169
29846
|
}
|
|
28170
29847
|
return ``;
|
|
28171
29848
|
}
|
|
28172
|
-
#columnStyle(depth) {
|
|
29849
|
+
#columnStyle(depth, isLast) {
|
|
28173
29850
|
const w = this.#columnWidths.get(depth);
|
|
28174
|
-
|
|
29851
|
+
if (w !== void 0) return `width:${w}px;flex-grow:0;flex-shrink:0;flex-basis:auto;max-width:none`;
|
|
29852
|
+
if (this.stretchLastColumn && isLast) return `width:max-content;flex-grow:1;flex-shrink:0;flex-basis:auto;min-width:0`;
|
|
29853
|
+
return `width:max-content;flex-grow:0;flex-shrink:0;flex-basis:auto`;
|
|
28175
29854
|
}
|
|
28176
29855
|
#isFocused(node, depth) {
|
|
28177
29856
|
return this.tickled.isTickled(this.#buildTickledId(node, depth));
|
|
@@ -28184,8 +29863,9 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28184
29863
|
}
|
|
28185
29864
|
#renderLoadingColumn() {
|
|
28186
29865
|
const depth = this._loadingDepth;
|
|
29866
|
+
const stretched = this.stretchLastColumn && this.#columnWidths.get(depth) === void 0;
|
|
28187
29867
|
return b`
|
|
28188
|
-
<div class="column loading-column" style=${this.#columnStyle(depth)}>
|
|
29868
|
+
<div class="column loading-column ${stretched ? `stretch-last` : ``}" style=${this.#columnStyle(depth, true)}>
|
|
28189
29869
|
${this.headers ? b`
|
|
28190
29870
|
<div class="column-header">${this.#getColumnHeader(depth)}</div>
|
|
28191
29871
|
` : A}
|
|
@@ -28200,8 +29880,10 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28200
29880
|
<div id="container" @keydown=${this.#onKeyDown} tabindex="0">
|
|
28201
29881
|
${this._columns.map((nodes, depth) => {
|
|
28202
29882
|
const visibleNodes = this.filterPredicate ? nodes.filter((n) => this.filterPredicate(n, depth)) : nodes;
|
|
29883
|
+
const isLast = depth === this._columns.length - 1 && this._loadingDepth === void 0;
|
|
29884
|
+
const stretched = this.stretchLastColumn && isLast && this.#columnWidths.get(depth) === void 0;
|
|
28203
29885
|
return b`
|
|
28204
|
-
<div class="column" style=${this.#columnStyle(depth)}>
|
|
29886
|
+
<div class="column ${stretched ? `stretch-last` : ``}" style=${this.#columnStyle(depth, isLast)}>
|
|
28205
29887
|
${this.headers ? b`
|
|
28206
29888
|
<div class="column-header"
|
|
28207
29889
|
@click=${() => this.#onHeaderClick(depth)}
|
|
@@ -28220,6 +29902,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28220
29902
|
})}
|
|
28221
29903
|
${this._loadingDepth !== void 0 ? this.#renderLoadingColumn() : A}
|
|
28222
29904
|
</div>
|
|
29905
|
+
${this.search.renderField()}
|
|
28223
29906
|
`;
|
|
28224
29907
|
}
|
|
28225
29908
|
_getFocusedIndex(depth) {
|
|
@@ -28255,6 +29938,7 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28255
29938
|
this.styles = [
|
|
28256
29939
|
themeFallbacks,
|
|
28257
29940
|
tickledStyles,
|
|
29941
|
+
listSearchStyles,
|
|
28258
29942
|
i$3`
|
|
28259
29943
|
:host {
|
|
28260
29944
|
--column-bg: var(--surface-3);
|
|
@@ -28289,6 +29973,20 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28289
29973
|
background: var(--column-bg);
|
|
28290
29974
|
border-right: 1px solid var(--column-separator);
|
|
28291
29975
|
flex-shrink: 0;
|
|
29976
|
+
/* Content-sized columns never grow wider than 80% of the available
|
|
29977
|
+
width — pathological labels truncate with ellipsis instead of
|
|
29978
|
+
pushing every other column out of view */
|
|
29979
|
+
max-width: 80%;
|
|
29980
|
+
/* Smoothly animate the last column stretching to fill remaining
|
|
29981
|
+
space and shrinking back to content width when it stops being last */
|
|
29982
|
+
transition: flex-grow var(--transition, 0.2s ease-out);
|
|
29983
|
+
}
|
|
29984
|
+
|
|
29985
|
+
.column.stretch-last {
|
|
29986
|
+
min-width: 0;
|
|
29987
|
+
/* Stretching fills remaining space by definition, so it is exempt
|
|
29988
|
+
from the 80% auto-size cap */
|
|
29989
|
+
max-width: none;
|
|
28292
29990
|
}
|
|
28293
29991
|
|
|
28294
29992
|
.column-header {
|
|
@@ -28372,6 +30070,12 @@ let MillerBaseElement = class MillerBaseElement extends i {
|
|
|
28372
30070
|
__decorate([property({ type: Object })], MillerBaseElement.prototype, "root", void 0);
|
|
28373
30071
|
__decorate([property({ attribute: false })], MillerBaseElement.prototype, "model", void 0);
|
|
28374
30072
|
__decorate([property({ type: Boolean })], MillerBaseElement.prototype, "headers", void 0);
|
|
30073
|
+
__decorate([property({
|
|
30074
|
+
type: Boolean,
|
|
30075
|
+
attribute: `stretch-last-column`,
|
|
30076
|
+
reflect: true
|
|
30077
|
+
})], MillerBaseElement.prototype, "stretchLastColumn", void 0);
|
|
30078
|
+
__decorate([property({ attribute: false })], MillerBaseElement.prototype, "viewRoot", void 0);
|
|
28375
30079
|
__decorate([property({ type: String })], MillerBaseElement.prototype, "interactionMode", void 0);
|
|
28376
30080
|
__decorate([property({
|
|
28377
30081
|
reflect: true,
|
|
@@ -28383,6 +30087,11 @@ __decorate([property({
|
|
|
28383
30087
|
})], MillerBaseElement.prototype, "exclusivity", void 0);
|
|
28384
30088
|
__decorate([property({ type: Object })], MillerBaseElement.prototype, "loadChildren", void 0);
|
|
28385
30089
|
__decorate([property({ attribute: false })], MillerBaseElement.prototype, "filterPredicate", void 0);
|
|
30090
|
+
__decorate([property({
|
|
30091
|
+
type: String,
|
|
30092
|
+
attribute: `search-modes`,
|
|
30093
|
+
reflect: true
|
|
30094
|
+
})], MillerBaseElement.prototype, "searchModes", void 0);
|
|
28386
30095
|
__decorate([state()], MillerBaseElement.prototype, "_columns", void 0);
|
|
28387
30096
|
__decorate([state()], MillerBaseElement.prototype, "_loadingDepth", void 0);
|
|
28388
30097
|
__decorate([state()], MillerBaseElement.prototype, "_selectedNodes", void 0);
|
|
@@ -28620,14 +30329,28 @@ var MillerTreeController = class {
|
|
|
28620
30329
|
* Any ancestor `MillerNode`s with unloaded children are populated
|
|
28621
30330
|
* synchronously before calling `element.navigateTo()`.
|
|
28622
30331
|
*
|
|
30332
|
+
* Key paths are relative to the element's current view root: when the view
|
|
30333
|
+
* is rebased, only matches at or below the view root can be reached.
|
|
30334
|
+
*
|
|
28623
30335
|
* Returns `false` if no matching node is found.
|
|
28624
30336
|
*/
|
|
28625
30337
|
navigateToValue(predicate) {
|
|
28626
30338
|
const treePath = this.#findTreePath(this.#root, predicate);
|
|
28627
30339
|
if (!treePath) return false;
|
|
28628
|
-
|
|
30340
|
+
const base = this.#element.viewRoot ?? this.#element.root;
|
|
30341
|
+
if (!base) return false;
|
|
30342
|
+
let startIndex = 1;
|
|
30343
|
+
if (this.#element.viewRoot) {
|
|
30344
|
+
const mappedBase = this.#nodeToTree.get(base);
|
|
30345
|
+
if (!mappedBase) return false;
|
|
30346
|
+
const baseIndex = treePath.indexOf(mappedBase);
|
|
30347
|
+
if (baseIndex < 0) return false;
|
|
30348
|
+
startIndex = baseIndex + 1;
|
|
30349
|
+
if (base.children === void 0) base.children = this.#childrenOf(mappedBase);
|
|
30350
|
+
}
|
|
30351
|
+
let currentColumn = base.children ?? [];
|
|
28629
30352
|
const keys = [];
|
|
28630
|
-
for (let i =
|
|
30353
|
+
for (let i = startIndex; i < treePath.length; i++) {
|
|
28631
30354
|
const treeNode = treePath[i];
|
|
28632
30355
|
const key = this.#options.valueToItem(treeNode.getValue(), treeNode).key;
|
|
28633
30356
|
keys.push(key);
|
|
@@ -29240,6 +30963,66 @@ __decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
|
|
|
29240
30963
|
PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
|
|
29241
30964
|
//#endregion
|
|
29242
30965
|
//#region src/panel/panel-group.ts
|
|
30966
|
+
/** Pointer movement threshold (px) before a press becomes a drag. */
|
|
30967
|
+
const DRAG_THRESHOLD_PX = 5;
|
|
30968
|
+
/**
|
|
30969
|
+
* Pure helper: nearest gap index for a pointer position along one axis.
|
|
30970
|
+
* `edges` are the [start, end] of each sibling (excluding the drag source),
|
|
30971
|
+
* in DOM order. Returns 0..edges.length.
|
|
30972
|
+
*/
|
|
30973
|
+
function panelGapIndex(edges, pos) {
|
|
30974
|
+
let bestGap = 0;
|
|
30975
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
30976
|
+
for (let i = 0; i < edges.length; i++) {
|
|
30977
|
+
const [start, end] = edges[i];
|
|
30978
|
+
if (pos < (start + end) / 2) {
|
|
30979
|
+
const d = Math.abs(pos - start);
|
|
30980
|
+
if (d < bestDist) {
|
|
30981
|
+
bestDist = d;
|
|
30982
|
+
bestGap = i;
|
|
30983
|
+
}
|
|
30984
|
+
} else {
|
|
30985
|
+
const d = Math.abs(pos - end);
|
|
30986
|
+
if (d < bestDist) {
|
|
30987
|
+
bestDist = d;
|
|
30988
|
+
bestGap = i + 1;
|
|
30989
|
+
}
|
|
30990
|
+
}
|
|
30991
|
+
}
|
|
30992
|
+
return bestGap;
|
|
30993
|
+
}
|
|
30994
|
+
let panelGhostStylesInjected = false;
|
|
30995
|
+
/**
|
|
30996
|
+
* Global ghost styling so the drag preview matches `ixfx-titlebar` chrome
|
|
30997
|
+
* (font, colours, padding). Injected once; the ghost lives outside shadow DOM
|
|
30998
|
+
* (body/theme container) so component styles cannot reach it.
|
|
30999
|
+
*/
|
|
31000
|
+
function injectPanelGhostStyles() {
|
|
31001
|
+
if (panelGhostStylesInjected) return;
|
|
31002
|
+
panelGhostStylesInjected = true;
|
|
31003
|
+
const style = document.createElement(`style`);
|
|
31004
|
+
style.textContent = `
|
|
31005
|
+
.ixfx-drag-ghost.ixfx-panel-group-ghost {
|
|
31006
|
+
padding: var(--space-s, 8px) var(--space-m, 16px);
|
|
31007
|
+
background: var(--surface-4);
|
|
31008
|
+
color: var(--surface-3-text);
|
|
31009
|
+
border: none;
|
|
31010
|
+
border-radius: var(--radius-s, 4px);
|
|
31011
|
+
font-family: var(--font-family);
|
|
31012
|
+
font-size: var(--text-m);
|
|
31013
|
+
font-weight: var(--font-weight-bold, 600);
|
|
31014
|
+
min-height: 2em;
|
|
31015
|
+
user-select: none;
|
|
31016
|
+
-webkit-user-select: none;
|
|
31017
|
+
}
|
|
31018
|
+
.ixfx-drag-ghost.ixfx-panel-group-ghost ixfx-icon {
|
|
31019
|
+
width: 1.25em;
|
|
31020
|
+
height: 1.25em;
|
|
31021
|
+
flex-shrink: 0;
|
|
31022
|
+
}
|
|
31023
|
+
`;
|
|
31024
|
+
document.head.appendChild(style);
|
|
31025
|
+
}
|
|
29243
31026
|
let PanelGroupElement = class PanelGroupElement extends i {
|
|
29244
31027
|
constructor(..._args) {
|
|
29245
31028
|
super(..._args);
|
|
@@ -29247,22 +31030,105 @@ let PanelGroupElement = class PanelGroupElement extends i {
|
|
|
29247
31030
|
this.variant = `titlebar`;
|
|
29248
31031
|
this.toggleInteraction = `implicit`;
|
|
29249
31032
|
this.collapsible = true;
|
|
29250
|
-
this.
|
|
29251
|
-
this.
|
|
31033
|
+
this.accordion = false;
|
|
31034
|
+
this.allowReorder = false;
|
|
31035
|
+
this._dragState = null;
|
|
31036
|
+
this._suppressNextClick = false;
|
|
31037
|
+
this._onClickCapture = (event) => {
|
|
31038
|
+
if (!this._suppressNextClick) return;
|
|
31039
|
+
this._suppressNextClick = false;
|
|
31040
|
+
event.preventDefault();
|
|
31041
|
+
event.stopPropagation();
|
|
31042
|
+
};
|
|
31043
|
+
this._onKeyDown = (event) => {
|
|
31044
|
+
const state = this._dragState;
|
|
31045
|
+
if (event.key !== `Escape` || !state?.active) return;
|
|
31046
|
+
event.preventDefault();
|
|
31047
|
+
const pointerId = state.pointerId;
|
|
31048
|
+
this._abortDrag();
|
|
31049
|
+
this._endDrag(pointerId);
|
|
31050
|
+
};
|
|
31051
|
+
this._onPointerDown = (event) => {
|
|
31052
|
+
if (!this.allowReorder || event.button !== 0) return;
|
|
31053
|
+
const target = this._findDragTarget(event);
|
|
31054
|
+
if (!target?.parentNode) return;
|
|
31055
|
+
const fromIndex = Array.prototype.indexOf.call(target.parentNode.children, target);
|
|
31056
|
+
if (fromIndex < 0) return;
|
|
31057
|
+
this._dragState = {
|
|
31058
|
+
pointerId: event.pointerId,
|
|
31059
|
+
source: target,
|
|
31060
|
+
fromIndex,
|
|
31061
|
+
startX: event.clientX,
|
|
31062
|
+
startY: event.clientY,
|
|
31063
|
+
active: false,
|
|
31064
|
+
ghost: null,
|
|
31065
|
+
gapIndex: void 0
|
|
31066
|
+
};
|
|
31067
|
+
window.addEventListener(`pointermove`, this._onPointerMove);
|
|
31068
|
+
window.addEventListener(`pointerup`, this._onPointerUp);
|
|
31069
|
+
window.addEventListener(`pointercancel`, this._onPointerCancel);
|
|
31070
|
+
};
|
|
31071
|
+
this._onPointerMove = (event) => {
|
|
31072
|
+
const state = this._dragState;
|
|
31073
|
+
if (!state || event.pointerId !== state.pointerId) return;
|
|
31074
|
+
const distance = Math.hypot(event.clientX - state.startX, event.clientY - state.startY);
|
|
31075
|
+
if (!state.active) {
|
|
31076
|
+
if (distance < DRAG_THRESHOLD_PX) return;
|
|
31077
|
+
this._beginDrag(event);
|
|
31078
|
+
}
|
|
31079
|
+
state.ghost?.move(event.clientX, event.clientY);
|
|
31080
|
+
const gap = this._computeGapIndex(event.clientX, event.clientY);
|
|
31081
|
+
if (gap !== state.gapIndex) {
|
|
31082
|
+
state.gapIndex = gap;
|
|
31083
|
+
this.requestUpdate();
|
|
31084
|
+
}
|
|
31085
|
+
};
|
|
31086
|
+
this._onPointerUp = (event) => {
|
|
31087
|
+
const state = this._dragState;
|
|
31088
|
+
if (!state || event.pointerId !== state.pointerId) return;
|
|
31089
|
+
if (state.active) {
|
|
31090
|
+
const gap = this._computeGapIndex(event.clientX, event.clientY);
|
|
31091
|
+
this._commitDrag(state.source, state.fromIndex, gap);
|
|
31092
|
+
}
|
|
31093
|
+
this._endDrag(event.pointerId);
|
|
31094
|
+
};
|
|
31095
|
+
this._onPointerCancel = (event) => {
|
|
31096
|
+
const state = this._dragState;
|
|
31097
|
+
if (!state || event.pointerId !== state.pointerId) return;
|
|
31098
|
+
this._abortDrag();
|
|
31099
|
+
this._endDrag(event.pointerId);
|
|
31100
|
+
};
|
|
29252
31101
|
}
|
|
29253
31102
|
_onPanelToggle(e) {
|
|
29254
31103
|
const panel = e.target;
|
|
29255
31104
|
if (panel.collapsed) return;
|
|
29256
|
-
if (this.
|
|
31105
|
+
if (!this.accordion) return;
|
|
29257
31106
|
for (const p of this._panels) if (p !== panel) p.collapsed = true;
|
|
29258
31107
|
}
|
|
31108
|
+
connectedCallback() {
|
|
31109
|
+
super.connectedCallback();
|
|
31110
|
+
this.addEventListener(`click`, this._onClickCapture, true);
|
|
31111
|
+
}
|
|
31112
|
+
disconnectedCallback() {
|
|
31113
|
+
this.removeEventListener(`click`, this._onClickCapture, true);
|
|
31114
|
+
if (this._dragState) {
|
|
31115
|
+
this._abortDrag();
|
|
31116
|
+
this._endDrag(this._dragState.pointerId);
|
|
31117
|
+
}
|
|
31118
|
+
super.disconnectedCallback();
|
|
31119
|
+
}
|
|
29259
31120
|
firstUpdated() {
|
|
31121
|
+
if (this.gap !== void 0) this.style.setProperty(`--group-gap`, this.gap);
|
|
29260
31122
|
this.updateComplete.then(() => {
|
|
29261
31123
|
this._propagateAll();
|
|
29262
31124
|
});
|
|
29263
31125
|
}
|
|
29264
31126
|
updated(changed) {
|
|
29265
31127
|
super.updated(changed);
|
|
31128
|
+
if (changed.has(`gap`)) {
|
|
31129
|
+
if (this.gap !== void 0) this.style.setProperty(`--group-gap`, this.gap);
|
|
31130
|
+
else this.style.removeProperty(`--group-gap`);
|
|
31131
|
+
}
|
|
29266
31132
|
if (changed.has(`variant`) || changed.has(`toggleInteraction`) || changed.has(`orientation`) || changed.has(`collapsible`)) this.updateComplete.then(() => {
|
|
29267
31133
|
this._propagateAll();
|
|
29268
31134
|
});
|
|
@@ -29277,19 +31143,167 @@ let PanelGroupElement = class PanelGroupElement extends i {
|
|
|
29277
31143
|
else panel.removeAttribute(`collapsible`);
|
|
29278
31144
|
}
|
|
29279
31145
|
}
|
|
31146
|
+
_onSlotChange() {
|
|
31147
|
+
this.updateComplete.then(() => {
|
|
31148
|
+
this._propagateAll();
|
|
31149
|
+
});
|
|
31150
|
+
}
|
|
31151
|
+
/**
|
|
31152
|
+
* Walk composedPath for the nearest ixfx-panel, requiring the press to have
|
|
31153
|
+
* gone through an ixfx-titlebar (titlebar-anywhere drag). Bails when the
|
|
31154
|
+
* path crosses interactive chrome (buttons/links/inputs/toolbar/caret/close)
|
|
31155
|
+
* so those controls always win over dragging.
|
|
31156
|
+
*/
|
|
31157
|
+
_findDragTarget(event) {
|
|
31158
|
+
if (!this.allowReorder) return null;
|
|
31159
|
+
const path = event.composedPath();
|
|
31160
|
+
let panel = null;
|
|
31161
|
+
let viaTitlebar = false;
|
|
31162
|
+
for (const node of path) {
|
|
31163
|
+
if (!(node instanceof HTMLElement)) continue;
|
|
31164
|
+
const tag = node.tagName;
|
|
31165
|
+
if (tag === `BUTTON` || tag === `A` || tag === `INPUT` || tag === `SELECT` || tag === `TEXTAREA`) return null;
|
|
31166
|
+
if (node.classList.contains(`toolbar`) || node.classList.contains(`close-btn`) || node.classList.contains(`caret-btn`)) return null;
|
|
31167
|
+
if (node.hasAttribute?.(`slot`) && node.getAttribute(`slot`) === `toolbar`) return null;
|
|
31168
|
+
if (tag === `IXFX-TITLEBAR`) viaTitlebar = true;
|
|
31169
|
+
if (!panel && tag === `IXFX-PANEL`) panel = node;
|
|
31170
|
+
}
|
|
31171
|
+
if (!panel || !viaTitlebar) return null;
|
|
31172
|
+
return panel;
|
|
31173
|
+
}
|
|
31174
|
+
_beginDrag(event) {
|
|
31175
|
+
const state = this._dragState;
|
|
31176
|
+
if (!state || state.active) return;
|
|
31177
|
+
try {
|
|
31178
|
+
this.setPointerCapture(event.pointerId);
|
|
31179
|
+
} catch {}
|
|
31180
|
+
state.active = true;
|
|
31181
|
+
this.setAttribute(`dragging`, ``);
|
|
31182
|
+
state.source.setAttribute(`drag-source`, ``);
|
|
31183
|
+
state.ghost = this._createGhost(state.source);
|
|
31184
|
+
state.ghost?.move(event.clientX, event.clientY);
|
|
31185
|
+
}
|
|
31186
|
+
_abortDrag() {
|
|
31187
|
+
const state = this._dragState;
|
|
31188
|
+
if (!state) return;
|
|
31189
|
+
state.source.removeAttribute(`drag-source`);
|
|
31190
|
+
if (state.gapIndex !== void 0) {
|
|
31191
|
+
state.gapIndex = void 0;
|
|
31192
|
+
this.requestUpdate();
|
|
31193
|
+
}
|
|
31194
|
+
}
|
|
31195
|
+
_endDrag(pointerId) {
|
|
31196
|
+
const state = this._dragState;
|
|
31197
|
+
if (!state) return;
|
|
31198
|
+
state.ghost?.destroy();
|
|
31199
|
+
state.ghost = null;
|
|
31200
|
+
this.removeAttribute(`dragging`);
|
|
31201
|
+
this._dragState = null;
|
|
31202
|
+
window.removeEventListener(`pointermove`, this._onPointerMove);
|
|
31203
|
+
window.removeEventListener(`pointerup`, this._onPointerUp);
|
|
31204
|
+
window.removeEventListener(`pointercancel`, this._onPointerCancel);
|
|
31205
|
+
try {
|
|
31206
|
+
this.releasePointerCapture(pointerId);
|
|
31207
|
+
} catch {}
|
|
31208
|
+
this.requestUpdate();
|
|
31209
|
+
}
|
|
31210
|
+
_commitDrag(source, fromIndex, gapIndex) {
|
|
31211
|
+
source.removeAttribute(`drag-source`);
|
|
31212
|
+
const siblings = (this._panels ?? []).filter((el) => el !== source);
|
|
31213
|
+
const target = gapIndex === void 0 ? siblings.length : Math.max(0, Math.min(gapIndex, siblings.length));
|
|
31214
|
+
const parent = source.parentNode;
|
|
31215
|
+
if (!parent) return;
|
|
31216
|
+
const ref = siblings[target] ?? null;
|
|
31217
|
+
parent.insertBefore(source, ref);
|
|
31218
|
+
const toIndex = Array.prototype.indexOf.call(parent.children, source);
|
|
31219
|
+
if (toIndex === fromIndex) return;
|
|
31220
|
+
this._suppressNextClick = true;
|
|
31221
|
+
this.dispatchEvent(new CustomEvent(`reorder`, {
|
|
31222
|
+
bubbles: true,
|
|
31223
|
+
composed: true,
|
|
31224
|
+
detail: {
|
|
31225
|
+
fromIndex,
|
|
31226
|
+
toIndex,
|
|
31227
|
+
panel: source
|
|
31228
|
+
}
|
|
31229
|
+
}));
|
|
31230
|
+
}
|
|
31231
|
+
_createGhost(source) {
|
|
31232
|
+
const content = document.createElement(`span`);
|
|
31233
|
+
content.style.display = `inline-flex`;
|
|
31234
|
+
content.style.alignItems = `center`;
|
|
31235
|
+
content.style.padding = `0 var(--space-s, 8px)`;
|
|
31236
|
+
content.style.minWidth = `0`;
|
|
31237
|
+
if (source.iconName) {
|
|
31238
|
+
const icon = document.createElement(`ixfx-icon`);
|
|
31239
|
+
icon.name = source.iconName;
|
|
31240
|
+
content.appendChild(icon);
|
|
31241
|
+
}
|
|
31242
|
+
const label = document.createElement(`span`);
|
|
31243
|
+
label.textContent = (source.title || (source.textContent ?? ``).trim()).slice(0, 60);
|
|
31244
|
+
label.style.paddingLeft = `1ch`;
|
|
31245
|
+
label.style.overflow = `hidden`;
|
|
31246
|
+
label.style.textOverflow = `ellipsis`;
|
|
31247
|
+
label.style.whiteSpace = `nowrap`;
|
|
31248
|
+
content.appendChild(label);
|
|
31249
|
+
try {
|
|
31250
|
+
injectPanelGhostStyles();
|
|
31251
|
+
return createDragGhost({
|
|
31252
|
+
anchor: source,
|
|
31253
|
+
content,
|
|
31254
|
+
className: `ixfx-panel-group-ghost`
|
|
31255
|
+
});
|
|
31256
|
+
} catch {
|
|
31257
|
+
return null;
|
|
31258
|
+
}
|
|
31259
|
+
}
|
|
31260
|
+
/**
|
|
31261
|
+
* Nearest panel gap to the pointer, ignoring the drag source.
|
|
31262
|
+
* The perpendicular axis is ignored — the pointer always resolves to a gap.
|
|
31263
|
+
*/
|
|
31264
|
+
_computeGapIndex(clientX, clientY) {
|
|
31265
|
+
const state = this._dragState;
|
|
31266
|
+
if (!state) return;
|
|
31267
|
+
const siblings = (this._panels ?? []).filter((el) => el !== state.source);
|
|
31268
|
+
if (siblings.length === 0) return 0;
|
|
31269
|
+
const useX = this.orientation === `horizontal`;
|
|
31270
|
+
const pos = useX ? clientX : clientY;
|
|
31271
|
+
return panelGapIndex(siblings.map((el) => {
|
|
31272
|
+
const rect = el.getBoundingClientRect();
|
|
31273
|
+
return [useX ? rect.left : rect.top, useX ? rect.right : rect.bottom];
|
|
31274
|
+
}), pos);
|
|
31275
|
+
}
|
|
31276
|
+
_computeCaretStyle() {
|
|
31277
|
+
const state = this._dragState;
|
|
31278
|
+
if (!state?.active || state.gapIndex === void 0) return;
|
|
31279
|
+
const siblings = (this._panels ?? []).filter((el) => el !== state.source);
|
|
31280
|
+
if (siblings.length === 0) return;
|
|
31281
|
+
const hostRect = this.getBoundingClientRect();
|
|
31282
|
+
if (!hostRect) return;
|
|
31283
|
+
const gap = Math.max(0, Math.min(state.gapIndex, siblings.length));
|
|
31284
|
+
const rect = gap < siblings.length ? siblings[gap].getBoundingClientRect() : siblings[siblings.length - 1].getBoundingClientRect();
|
|
31285
|
+
if (this.orientation === `horizontal`) return `transform: translate(${(gap < siblings.length ? rect.left : rect.right) - hostRect.left - 1}px, 0); width: 2px; height: ${hostRect.height}px;`;
|
|
31286
|
+
return `transform: translate(0, ${(gap < siblings.length ? rect.top : rect.bottom) - hostRect.top - 1}px); width: ${hostRect.width}px; height: 2px;`;
|
|
31287
|
+
}
|
|
29280
31288
|
render() {
|
|
31289
|
+
const caretStyle = this._computeCaretStyle();
|
|
29281
31290
|
return b`
|
|
29282
31291
|
<slot
|
|
29283
31292
|
@toggle=${this._onPanelToggle}
|
|
31293
|
+
@slotchange=${this._onSlotChange}
|
|
31294
|
+
@pointerdown=${this._onPointerDown}
|
|
31295
|
+
@keydown=${this._onKeyDown}
|
|
29284
31296
|
></slot>
|
|
31297
|
+
${caretStyle ? b`<div class="drop-caret" style=${caretStyle}></div>` : A}
|
|
29285
31298
|
`;
|
|
29286
31299
|
}
|
|
29287
31300
|
static {
|
|
29288
31301
|
this.styles = i$3`
|
|
29289
31302
|
:host {
|
|
31303
|
+
position: relative;
|
|
29290
31304
|
display: flex;
|
|
29291
31305
|
flex-direction: column;
|
|
29292
|
-
gap: var(--group-gap, var(--space-
|
|
31306
|
+
gap: var(--group-gap, var(--space-l));
|
|
29293
31307
|
}
|
|
29294
31308
|
|
|
29295
31309
|
:host([orientation="horizontal"]) {
|
|
@@ -29310,6 +31324,36 @@ let PanelGroupElement = class PanelGroupElement extends i {
|
|
|
29310
31324
|
flex-basis: content;
|
|
29311
31325
|
min-width: 0;
|
|
29312
31326
|
}
|
|
31327
|
+
|
|
31328
|
+
:host([allow-reorder]) ::slotted(ixfx-panel) {
|
|
31329
|
+
cursor: grab;
|
|
31330
|
+
}
|
|
31331
|
+
|
|
31332
|
+
:host([allow-reorder]) ::slotted(ixfx-panel[drag-source]) {
|
|
31333
|
+
cursor: grabbing;
|
|
31334
|
+
opacity: 0.4;
|
|
31335
|
+
}
|
|
31336
|
+
|
|
31337
|
+
/* While dragging, suppress text selection inside the panels. */
|
|
31338
|
+
:host([dragging]),
|
|
31339
|
+
:host([dragging]) ::slotted(*) {
|
|
31340
|
+
user-select: none;
|
|
31341
|
+
-webkit-user-select: none;
|
|
31342
|
+
}
|
|
31343
|
+
|
|
31344
|
+
:host([dragging]) ::slotted(ixfx-panel) {
|
|
31345
|
+
cursor: grabbing;
|
|
31346
|
+
}
|
|
31347
|
+
|
|
31348
|
+
.drop-caret {
|
|
31349
|
+
position: absolute;
|
|
31350
|
+
top: 0;
|
|
31351
|
+
left: 0;
|
|
31352
|
+
background: var(--accent);
|
|
31353
|
+
border-radius: 1px;
|
|
31354
|
+
pointer-events: none;
|
|
31355
|
+
z-index: 1;
|
|
31356
|
+
}
|
|
29313
31357
|
`;
|
|
29314
31358
|
}
|
|
29315
31359
|
};
|
|
@@ -29325,8 +31369,14 @@ __decorate([property({
|
|
|
29325
31369
|
__decorate([property({ type: Boolean })], PanelGroupElement.prototype, "collapsible", void 0);
|
|
29326
31370
|
__decorate([property({
|
|
29327
31371
|
type: Boolean,
|
|
29328
|
-
attribute: `
|
|
29329
|
-
|
|
31372
|
+
attribute: `accordion`,
|
|
31373
|
+
reflect: true
|
|
31374
|
+
})], PanelGroupElement.prototype, "accordion", void 0);
|
|
31375
|
+
__decorate([property({
|
|
31376
|
+
type: Boolean,
|
|
31377
|
+
attribute: `allow-reorder`,
|
|
31378
|
+
reflect: true
|
|
31379
|
+
})], PanelGroupElement.prototype, "allowReorder", void 0);
|
|
29330
31380
|
__decorate([property()], PanelGroupElement.prototype, "gap", void 0);
|
|
29331
31381
|
__decorate([queryAssignedElements({ slot: `` })], PanelGroupElement.prototype, "_panels", void 0);
|
|
29332
31382
|
PanelGroupElement = __decorate([safeCustomElement(`ixfx-panel-group`)], PanelGroupElement);
|
|
@@ -30039,11 +32089,18 @@ let PlotHistogram = class PlotHistogram extends i {
|
|
|
30039
32089
|
this.valueTooltips = `hover`;
|
|
30040
32090
|
this.tooltipDelay = 400;
|
|
30041
32091
|
this.valueUpdateInterval = 500;
|
|
32092
|
+
this.interactionMode = `inspect`;
|
|
30042
32093
|
this.valueFormat = (valueMin, valueMax, frequency) => `${valueMin.toFixed(2)} - ${valueMax.toFixed(2)}: ${frequency}`;
|
|
30043
32094
|
this._width = 100;
|
|
30044
32095
|
this._height = 40;
|
|
30045
32096
|
this._hoveredIndex = null;
|
|
30046
32097
|
this._inlineOverlayText = null;
|
|
32098
|
+
this._coloredRangeMin = null;
|
|
32099
|
+
this._coloredRangeMax = null;
|
|
32100
|
+
this._selectionStart = null;
|
|
32101
|
+
this._selectionEnd = null;
|
|
32102
|
+
this._isDragging = false;
|
|
32103
|
+
this._selectionOverlayText = null;
|
|
30047
32104
|
this.#series = new PlotDataSeries({
|
|
30048
32105
|
capacityLimit: 100,
|
|
30049
32106
|
persistentScaling: true
|
|
@@ -30056,6 +32113,22 @@ let PlotHistogram = class PlotHistogram extends i {
|
|
|
30056
32113
|
this.#tooltipPosition = null;
|
|
30057
32114
|
this.#valueRefreshTimer = null;
|
|
30058
32115
|
this.#onPointerMove = (e) => {
|
|
32116
|
+
if (this.interactionMode === `select` && this._isDragging) {
|
|
32117
|
+
const index = this.#bucketIndexFromPosition(e.clientX, e.clientY);
|
|
32118
|
+
this._selectionEnd = index;
|
|
32119
|
+
const start = Math.min(this._selectionStart, this._selectionEnd);
|
|
32120
|
+
const end = Math.max(this._selectionStart, this._selectionEnd);
|
|
32121
|
+
const bars = this.#computeBars();
|
|
32122
|
+
if (start <= end) {
|
|
32123
|
+
const startBar = bars[start];
|
|
32124
|
+
const endBar = bars[end];
|
|
32125
|
+
this.setColoredRange(startBar.valueMin, endBar.valueMax);
|
|
32126
|
+
this._selectionOverlayText = `Select: ${startBar.valueMin.toFixed(2)} – ${endBar.valueMax.toFixed(2)}`;
|
|
32127
|
+
}
|
|
32128
|
+
this.requestUpdate();
|
|
32129
|
+
return;
|
|
32130
|
+
}
|
|
32131
|
+
if (this.interactionMode === `select`) return;
|
|
30059
32132
|
if (this.valueTooltips === `none`) return;
|
|
30060
32133
|
const index = this.#bucketIndexFromPointer(e.clientX, e.clientY);
|
|
30061
32134
|
const bucketIndex = index >= 0 ? index : null;
|
|
@@ -30063,10 +32136,34 @@ let PlotHistogram = class PlotHistogram extends i {
|
|
|
30063
32136
|
else this.#updateBubbleTooltip(bucketIndex, e.clientX, e.clientY);
|
|
30064
32137
|
};
|
|
30065
32138
|
this.#onPointerLeave = () => {
|
|
32139
|
+
if (this.interactionMode === `select`) {
|
|
32140
|
+
if (this._isDragging) {
|
|
32141
|
+
this._isDragging = false;
|
|
32142
|
+
this._selectionOverlayText = null;
|
|
32143
|
+
this.requestUpdate();
|
|
32144
|
+
}
|
|
32145
|
+
return;
|
|
32146
|
+
}
|
|
30066
32147
|
if (this.valueTooltips === `none`) return;
|
|
30067
32148
|
if (this.valueTooltips === `inline`) this.#setInlineIndex(null);
|
|
30068
32149
|
else this.#updateBubbleTooltip(null, 0, 0);
|
|
30069
32150
|
};
|
|
32151
|
+
this.#onPointerDown = (e) => {
|
|
32152
|
+
if (this.interactionMode !== `select`) return;
|
|
32153
|
+
const index = this.#bucketIndexFromPosition(e.clientX, e.clientY);
|
|
32154
|
+
this._isDragging = true;
|
|
32155
|
+
this._selectionStart = index;
|
|
32156
|
+
this._selectionEnd = index;
|
|
32157
|
+
const bars = this.#computeBars();
|
|
32158
|
+
const startBar = bars[index];
|
|
32159
|
+
const endBar = bars[index];
|
|
32160
|
+
this.setColoredRange(startBar.valueMin, endBar.valueMax);
|
|
32161
|
+
this._selectionOverlayText = `Select: ${startBar.valueMin.toFixed(2)} – ${endBar.valueMax.toFixed(2)}`;
|
|
32162
|
+
this.requestUpdate();
|
|
32163
|
+
};
|
|
32164
|
+
this.#onPointerUp = () => {
|
|
32165
|
+
if (this.interactionMode === `select`) this._isDragging = false;
|
|
32166
|
+
};
|
|
30070
32167
|
}
|
|
30071
32168
|
#series;
|
|
30072
32169
|
#hasProgrammaticData;
|
|
@@ -30090,6 +32187,59 @@ let PlotHistogram = class PlotHistogram extends i {
|
|
|
30090
32187
|
this.#series.clear();
|
|
30091
32188
|
this.requestUpdate();
|
|
30092
32189
|
}
|
|
32190
|
+
setColoredRange(min, max) {
|
|
32191
|
+
this._coloredRangeMin = min;
|
|
32192
|
+
this._coloredRangeMax = max;
|
|
32193
|
+
this.requestUpdate();
|
|
32194
|
+
}
|
|
32195
|
+
clearColoredRange() {
|
|
32196
|
+
this._coloredRangeMin = null;
|
|
32197
|
+
this._coloredRangeMax = null;
|
|
32198
|
+
this.requestUpdate();
|
|
32199
|
+
}
|
|
32200
|
+
setSelection(startBarIndex, endBarIndex) {
|
|
32201
|
+
const bars = this.#computeBars();
|
|
32202
|
+
const start = Math.min(startBarIndex, endBarIndex);
|
|
32203
|
+
const end = Math.max(startBarIndex, endBarIndex);
|
|
32204
|
+
const clampedStart = Math.max(0, Math.min(start, bars.length - 1));
|
|
32205
|
+
const clampedEnd = Math.max(0, Math.min(end, bars.length - 1));
|
|
32206
|
+
this._selectionStart = clampedStart;
|
|
32207
|
+
this._selectionEnd = clampedEnd;
|
|
32208
|
+
if (clampedStart <= clampedEnd) {
|
|
32209
|
+
const startBar = bars[clampedStart];
|
|
32210
|
+
const endBar = bars[clampedEnd];
|
|
32211
|
+
this.setColoredRange(startBar.valueMin, endBar.valueMax);
|
|
32212
|
+
}
|
|
32213
|
+
this.requestUpdate();
|
|
32214
|
+
}
|
|
32215
|
+
clearSelection() {
|
|
32216
|
+
this._selectionStart = null;
|
|
32217
|
+
this._selectionEnd = null;
|
|
32218
|
+
this.requestUpdate();
|
|
32219
|
+
}
|
|
32220
|
+
getSelectedRange() {
|
|
32221
|
+
if (this._selectionStart === null || this._selectionEnd === null) return {
|
|
32222
|
+
barIndices: null,
|
|
32223
|
+
dataValues: null
|
|
32224
|
+
};
|
|
32225
|
+
const bars = this.#computeBars();
|
|
32226
|
+
const start = Math.min(this._selectionStart, this._selectionEnd);
|
|
32227
|
+
const end = Math.max(this._selectionStart, this._selectionEnd);
|
|
32228
|
+
const clampedStart = Math.max(0, Math.min(start, bars.length - 1));
|
|
32229
|
+
const clampedEnd = Math.max(0, Math.min(end, bars.length - 1));
|
|
32230
|
+
const startBar = bars[clampedStart];
|
|
32231
|
+
const endBar = bars[clampedEnd];
|
|
32232
|
+
return {
|
|
32233
|
+
barIndices: {
|
|
32234
|
+
start: clampedStart,
|
|
32235
|
+
end: clampedEnd
|
|
32236
|
+
},
|
|
32237
|
+
dataValues: {
|
|
32238
|
+
min: startBar.valueMin,
|
|
32239
|
+
max: endBar.valueMax
|
|
32240
|
+
}
|
|
32241
|
+
};
|
|
32242
|
+
}
|
|
30093
32243
|
firstUpdated(_changed) {
|
|
30094
32244
|
this._width = this.clientWidth || 100;
|
|
30095
32245
|
this._height = this.clientHeight || 40;
|
|
@@ -30224,6 +32374,10 @@ let PlotHistogram = class PlotHistogram extends i {
|
|
|
30224
32374
|
}
|
|
30225
32375
|
});
|
|
30226
32376
|
}
|
|
32377
|
+
#isBarInColoredRange(bar) {
|
|
32378
|
+
if (this._coloredRangeMin === null || this._coloredRangeMax === null) return true;
|
|
32379
|
+
return bar.valueMin < this._coloredRangeMax && bar.valueMax > this._coloredRangeMin;
|
|
32380
|
+
}
|
|
30227
32381
|
/** Index of the bar rect under the pointer, or -1 if none (matches click hit-testing). */
|
|
30228
32382
|
#bucketIndexFromPointer(clientX, clientY) {
|
|
30229
32383
|
const rect = this.getBoundingClientRect();
|
|
@@ -30231,6 +32385,18 @@ let PlotHistogram = class PlotHistogram extends i {
|
|
|
30231
32385
|
const y = clientY - rect.top;
|
|
30232
32386
|
return this.#computeBars().findIndex((b) => x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height);
|
|
30233
32387
|
}
|
|
32388
|
+
/** Gets bucket index from position, clamping to valid range (0 to bucketCount-1). */
|
|
32389
|
+
#bucketIndexFromPosition(clientX, clientY) {
|
|
32390
|
+
const { w, h, isHorizontal, padding, dataMin, dataSpan, bucketCount, availableMain } = this.#computeGeometry();
|
|
32391
|
+
const rect = this.getBoundingClientRect();
|
|
32392
|
+
const x = clientX - rect.left;
|
|
32393
|
+
const y = clientY - rect.top;
|
|
32394
|
+
let relativePos;
|
|
32395
|
+
if (isHorizontal) relativePos = (x - padding) / availableMain;
|
|
32396
|
+
else relativePos = (y - padding) / availableMain;
|
|
32397
|
+
relativePos = Math.max(0, Math.min(1, relativePos));
|
|
32398
|
+
return Math.floor(relativePos * bucketCount);
|
|
32399
|
+
}
|
|
30234
32400
|
#tooltipIndex;
|
|
30235
32401
|
#tooltipVisible;
|
|
30236
32402
|
#tooltipShowTimer;
|
|
@@ -30365,15 +32531,23 @@ let PlotHistogram = class PlotHistogram extends i {
|
|
|
30365
32531
|
}
|
|
30366
32532
|
#onPointerMove;
|
|
30367
32533
|
#onPointerLeave;
|
|
32534
|
+
#onPointerDown;
|
|
32535
|
+
#onPointerUp;
|
|
30368
32536
|
render() {
|
|
30369
32537
|
const { w: w$7, h, padding } = this.#computeGeometry();
|
|
30370
|
-
const barsContent = this.#computeBars().map((b, i) =>
|
|
30371
|
-
|
|
30372
|
-
|
|
30373
|
-
|
|
30374
|
-
|
|
32538
|
+
const barsContent = this.#computeBars().map((b, i) => {
|
|
32539
|
+
const greyStyle = this.#isBarInColoredRange(b) ? `` : `filter: grayscale(1)`;
|
|
32540
|
+
return w`
|
|
32541
|
+
<rect class="bar" x=${b.x} y=${b.y} width=${b.width} height=${b.height}
|
|
32542
|
+
style="--value: ${b.relativeValue}; ${greyStyle}"
|
|
32543
|
+
@click=${() => this.#emitClick(i, b.valueMin, b.valueMax, b.relativeValue)} />
|
|
32544
|
+
`;
|
|
32545
|
+
});
|
|
30375
32546
|
const textColor = `var(--plot-text, var(--surface-2-text, #fff))`;
|
|
30376
|
-
|
|
32547
|
+
let overlayText;
|
|
32548
|
+
if (this.interactionMode === `select` && this._selectionOverlayText !== null) overlayText = this._selectionOverlayText;
|
|
32549
|
+
else if (this.valueTooltips === `inline` && this._inlineOverlayText !== null) overlayText = this._inlineOverlayText;
|
|
32550
|
+
else overlayText = this.title;
|
|
30377
32551
|
const titleContent = overlayText ? w`<text x=${padding + 2} y=${padding + Math.min(10, h * .6)}
|
|
30378
32552
|
class="overlay-text" fill=${textColor}
|
|
30379
32553
|
style="opacity: 0.6">${overlayText}</text>` : w``;
|
|
@@ -30381,7 +32555,9 @@ let PlotHistogram = class PlotHistogram extends i {
|
|
|
30381
32555
|
<svg width=${w$7} height=${h} viewBox="0 0 ${w$7} ${h}"
|
|
30382
32556
|
xmlns="http://www.w3.org/2000/svg"
|
|
30383
32557
|
@pointermove=${this.#onPointerMove}
|
|
30384
|
-
@pointerleave=${this.#onPointerLeave}
|
|
32558
|
+
@pointerleave=${this.#onPointerLeave}
|
|
32559
|
+
@pointerdown=${this.#onPointerDown}
|
|
32560
|
+
@pointerup=${this.#onPointerUp}>
|
|
30385
32561
|
<rect width=${w$7} height=${h} fill="transparent" />
|
|
30386
32562
|
${barsContent}
|
|
30387
32563
|
${titleContent}
|
|
@@ -30443,10 +32619,17 @@ __decorate([property({
|
|
|
30443
32619
|
type: Number,
|
|
30444
32620
|
attribute: `value-update-interval`
|
|
30445
32621
|
})], PlotHistogram.prototype, "valueUpdateInterval", void 0);
|
|
32622
|
+
__decorate([property({ attribute: `interaction-mode` })], PlotHistogram.prototype, "interactionMode", void 0);
|
|
30446
32623
|
__decorate([state()], PlotHistogram.prototype, "_width", void 0);
|
|
30447
32624
|
__decorate([state()], PlotHistogram.prototype, "_height", void 0);
|
|
30448
32625
|
__decorate([state()], PlotHistogram.prototype, "_hoveredIndex", void 0);
|
|
30449
32626
|
__decorate([state()], PlotHistogram.prototype, "_inlineOverlayText", void 0);
|
|
32627
|
+
__decorate([state()], PlotHistogram.prototype, "_coloredRangeMin", void 0);
|
|
32628
|
+
__decorate([state()], PlotHistogram.prototype, "_coloredRangeMax", void 0);
|
|
32629
|
+
__decorate([state()], PlotHistogram.prototype, "_selectionStart", void 0);
|
|
32630
|
+
__decorate([state()], PlotHistogram.prototype, "_selectionEnd", void 0);
|
|
32631
|
+
__decorate([state()], PlotHistogram.prototype, "_isDragging", void 0);
|
|
32632
|
+
__decorate([state()], PlotHistogram.prototype, "_selectionOverlayText", void 0);
|
|
30450
32633
|
PlotHistogram = __decorate([safeCustomElement(`ixfx-plot-histogram`)], PlotHistogram);
|
|
30451
32634
|
//#endregion
|
|
30452
32635
|
//#region src/plots/single-axis.ts
|
|
@@ -37455,29 +39638,24 @@ var TreeController = class {
|
|
|
37455
39638
|
navigateToValue(predicate) {
|
|
37456
39639
|
const treePath = this.#findTreePath(this.#root, predicate);
|
|
37457
39640
|
if (!treePath) return false;
|
|
39641
|
+
let currentColumn = this.#element.root?.children ?? [];
|
|
37458
39642
|
const keys = [];
|
|
37459
39643
|
for (let i = 1; i < treePath.length; i++) {
|
|
37460
39644
|
const treeNode = treePath[i];
|
|
37461
39645
|
const key = this.#options.valueToItem(treeNode.getValue(), treeNode).key;
|
|
37462
39646
|
keys.push(key);
|
|
37463
39647
|
if (i < treePath.length - 1) {
|
|
37464
|
-
const
|
|
37465
|
-
const treeNodeMap = this.#nodeToTree;
|
|
37466
|
-
const node = currentChildren.find((n) => n.item.key === key);
|
|
39648
|
+
const node = currentColumn.find((n) => n.item.key === key);
|
|
37467
39649
|
if (!node) return false;
|
|
37468
39650
|
if (node.children === void 0) {
|
|
37469
|
-
const mapped =
|
|
39651
|
+
const mapped = this.#nodeToTree.get(node);
|
|
37470
39652
|
if (mapped) node.children = this.#childrenOf(mapped);
|
|
37471
39653
|
}
|
|
39654
|
+
currentColumn = node.children ?? [];
|
|
37472
39655
|
}
|
|
37473
39656
|
}
|
|
37474
39657
|
return this.#element.navigateTo(keys);
|
|
37475
39658
|
}
|
|
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
39659
|
#childrenOf(treeNode) {
|
|
37482
39660
|
return Array.from(treeNode.children(), (child) => this.#toTreeNode(child));
|
|
37483
39661
|
}
|
|
@@ -38642,6 +40820,6 @@ function init() {
|
|
|
38642
40820
|
console.log(`Init`);
|
|
38643
40821
|
}
|
|
38644
40822
|
//#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 };
|
|
40823
|
+
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, createDataListSearchAdapter, 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, panelGapIndex, 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
40824
|
|
|
38647
40825
|
//# sourceMappingURL=index.js.map
|