@ixfx/components 0.7.4 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/bundle/index.d.ts +536 -178
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +1324 -442
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-token.js +1 -1
  6. package/dist/crumbs.d.ts +5 -1
  7. package/dist/crumbs.d.ts.map +1 -1
  8. package/dist/crumbs.js +10 -0
  9. package/dist/crumbs.js.map +1 -1
  10. package/dist/{data-grid-BddUjAti.d.ts → data-grid-CtJ35v8L.d.ts} +26 -1
  11. package/dist/data-grid-CtJ35v8L.d.ts.map +1 -0
  12. package/dist/data-grid.d.ts +1 -1
  13. package/dist/data-grid.js +88 -7
  14. package/dist/data-grid.js.map +1 -1
  15. package/dist/{detail-list-Br0WAnry.d.ts → detail-list-BS5dRDda.d.ts} +20 -12
  16. package/dist/detail-list-BS5dRDda.d.ts.map +1 -0
  17. package/dist/detail-list.d.ts +2 -2
  18. package/dist/detail-list.js +51 -135
  19. package/dist/detail-list.js.map +1 -1
  20. package/dist/{grid-list-B_orPsot.d.ts → grid-list-D-GbghSH.d.ts} +18 -1
  21. package/dist/grid-list-D-GbghSH.d.ts.map +1 -0
  22. package/dist/grid-list.d.ts +1 -1
  23. package/dist/grid-list.js +76 -15
  24. package/dist/grid-list.js.map +1 -1
  25. package/dist/incr-search-DpbSJtgj.js +962 -0
  26. package/dist/incr-search-DpbSJtgj.js.map +1 -0
  27. package/dist/incr-search.d.ts +2 -2
  28. package/dist/incr-search.js +2 -3
  29. package/dist/index-CBS4z1v-.d.ts +365 -0
  30. package/dist/index-CBS4z1v-.d.ts.map +1 -0
  31. package/dist/index.d.ts +8 -8
  32. package/dist/index.js +4 -5
  33. package/dist/index.js.map +1 -1
  34. package/dist/{list-selection-controller-CUo-Hhcm.js → list-selection-controller-DTFI40li.js} +2 -76
  35. package/dist/list-selection-controller-DTFI40li.js.map +1 -0
  36. package/dist/miller.d.ts +81 -3
  37. package/dist/miller.d.ts.map +1 -1
  38. package/dist/miller.js +327 -68
  39. package/dist/miller.js.map +1 -1
  40. package/dist/narrowed-text.js +1 -2
  41. package/dist/narrowed-text.js.map +1 -1
  42. package/dist/{tree-B74zdI60.js → tree-DAii2A1g.js} +77 -15
  43. package/dist/tree-DAii2A1g.js.map +1 -0
  44. package/dist/{tree-component-BvV4muxx.d.ts → tree-component-F6qZ0Gdv.d.ts} +9 -1
  45. package/dist/{tree-component-BvV4muxx.d.ts.map → tree-component-F6qZ0Gdv.d.ts.map} +1 -1
  46. package/dist/tree.d.ts +22 -1
  47. package/dist/tree.d.ts.map +1 -1
  48. package/dist/tree.js +1 -1
  49. package/dist/{vertical-list-CQSEVeOT.d.ts → vertical-list-DwtCPy1b.d.ts} +17 -14
  50. package/dist/vertical-list-DwtCPy1b.d.ts.map +1 -0
  51. package/dist/vertical-list.d.ts +1 -1
  52. package/dist/vertical-list.js +59 -127
  53. package/dist/vertical-list.js.map +1 -1
  54. package/docs-user/data-grid.md +19 -0
  55. package/docs-user/detail-list.md +1 -1
  56. package/docs-user/grid-list.md +24 -0
  57. package/docs-user/incr-search.md +12 -2
  58. package/docs-user/index.json +2 -2
  59. package/docs-user/tree.md +22 -1
  60. package/docs-user/vertical-list.md +7 -1
  61. package/package.json +1 -1
  62. package/dist/data-grid-BddUjAti.d.ts.map +0 -1
  63. package/dist/detail-list-Br0WAnry.d.ts.map +0 -1
  64. package/dist/element-search-Y8SRfPGM.js +0 -185
  65. package/dist/element-search-Y8SRfPGM.js.map +0 -1
  66. package/dist/grid-list-B_orPsot.d.ts.map +0 -1
  67. package/dist/incr-search-vCzuao-2.js +0 -137
  68. package/dist/incr-search-vCzuao-2.js.map +0 -1
  69. package/dist/index-Cu5i8zZY.d.ts +0 -160
  70. package/dist/index-Cu5i8zZY.d.ts.map +0 -1
  71. package/dist/list-selection-controller-CUo-Hhcm.js.map +0 -1
  72. package/dist/tree-B74zdI60.js.map +0 -1
  73. package/dist/vertical-list-CQSEVeOT.d.ts.map +0 -1
package/dist/miller.js CHANGED
@@ -2,16 +2,19 @@ import { t as TickledController } from "./tickled-controller-C66DgJHl.js";
2
2
  import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
3
3
  import { t as themeFallbacks } from "./fallbacks-DNI9xt0F.js";
4
4
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
5
+ import { i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, t as createTreeListSearchAdapter } from "./incr-search-DpbSJtgj.js";
5
6
  import "./interaction-Cr13azbM.js";
6
7
  import { t as collectDataResult } from "./data-provider-35TyWclX.js";
7
8
  import { LitElement, css, html, nothing } from "lit";
8
9
  import { property, state } from "lit/decorators.js";
10
+ import { createCommandRegistry } from "@clinth/ui-commands";
9
11
  //#region src/miller/miller-base.ts
10
12
  const EXPAND_DEBOUNCE_MS = 150;
11
13
  let MillerBaseElement = class MillerBaseElement extends LitElement {
12
14
  constructor(..._args) {
13
15
  super(..._args);
14
16
  this.headers = false;
17
+ this.stretchLastColumn = false;
15
18
  this.interactionMode = `standard`;
16
19
  this.selectionFilter = `leaf`;
17
20
  this.exclusivity = `none`;
@@ -23,6 +26,10 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
23
26
  this.#resizingDepth = -1;
24
27
  this.#resizeStartX = 0;
25
28
  this.#resizeStartWidth = 0;
29
+ this.search = new ListSearchController(this, { adapter: createTreeListSearchAdapter(this, {
30
+ labelSelector: `.label`,
31
+ highlightKey: `incr-search-highlight`
32
+ }) });
26
33
  this.tickled = new TickledController(this, { getItems: () => {
27
34
  const items = [];
28
35
  for (let depth = 0; depth < this._columns.length; depth++) {
@@ -32,6 +39,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
32
39
  return items;
33
40
  } });
34
41
  this.#onKeyDown = (event) => {
42
+ if (this.search.isOpen) return;
35
43
  const currentDepth = this.#getTickledDepthOrDefault();
36
44
  const col = this._columns[currentDepth];
37
45
  if (!col || col.length === 0) {
@@ -238,9 +246,31 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
238
246
  #resizeStartX;
239
247
  #resizeStartWidth;
240
248
  #modelUnsubscribe;
249
+ #searchRegistry;
250
+ #unregisterSearchCommands;
251
+ /** Command registry carrying this component's `list.search.*` commands. */
252
+ get registry() {
253
+ return this.#searchRegistry;
254
+ }
255
+ #effectiveSearchModes() {
256
+ const raw = this.searchModes?.trim();
257
+ if (raw === void 0) return [...LIST_SEARCH_MODES];
258
+ const requested = new Set(raw.split(/\s+/));
259
+ return LIST_SEARCH_MODES.filter((m) => requested.has(m));
260
+ }
261
+ #syncSearchCommands() {
262
+ if (!this.#searchRegistry) return;
263
+ this.search.setModes(this.#effectiveSearchModes());
264
+ this.#unregisterSearchCommands?.();
265
+ this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
266
+ }
241
267
  #buildTickledId(node, depth) {
242
268
  return `${depth}:${node.item.key}`;
243
269
  }
270
+ /** The node column 0 renders the children of: `viewRoot` when rebased, else `root`. */
271
+ get #displayRoot() {
272
+ return this.viewRoot ?? this.root;
273
+ }
244
274
  #getTickledDepth() {
245
275
  const id = this.tickled.tickledId;
246
276
  if (!id) return void 0;
@@ -249,16 +279,20 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
249
279
  return Number(id.substring(0, colonIndex));
250
280
  }
251
281
  updated(changedProps) {
282
+ if (changedProps.has(`searchModes`)) this.#syncSearchCommands();
252
283
  if (changedProps.has(`root`) && this.root) {
253
284
  this.#abortPending();
254
- this._columns = this.root.children ? [this.root.children] : [];
285
+ if (this.viewRoot !== void 0) this.viewRoot = void 0;
255
286
  this._selectedByDepth.clear();
256
287
  this._selectedKeys = /* @__PURE__ */ new Set();
257
288
  this._selectedNodes.clear();
258
289
  this.#lastSelectedNode = void 0;
259
290
  this.#anchor = void 0;
260
291
  this.tickled.clearTickled();
292
+ const display = this.#displayRoot;
293
+ this._columns = display?.children ? [display.children] : [];
261
294
  }
295
+ if (changedProps.has(`viewRoot`)) this.#applyViewRoot();
262
296
  if (changedProps.has(`model`)) {
263
297
  this.#modelUnsubscribe?.();
264
298
  this.#modelUnsubscribe = void 0;
@@ -300,10 +334,18 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
300
334
  });
301
335
  }
302
336
  }
337
+ connectedCallback() {
338
+ super.connectedCallback();
339
+ this.#searchRegistry ??= createCommandRegistry();
340
+ this.#syncSearchCommands();
341
+ }
303
342
  disconnectedCallback() {
304
343
  super.disconnectedCallback();
305
344
  this.#modelUnsubscribe?.();
306
345
  this.#modelUnsubscribe = void 0;
346
+ this.#unregisterSearchCommands?.();
347
+ this.#unregisterSearchCommands = void 0;
348
+ this.#searchRegistry = void 0;
307
349
  }
308
350
  getConfig() {
309
351
  const columnWidths = {};
@@ -399,15 +441,17 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
399
441
  return results;
400
442
  }
401
443
  /**
402
- * Programmatically navigate to a node by key path.
444
+ * Programmatically navigate to a node by key path, relative to the current
445
+ * view root (rebased or full tree).
403
446
  */
404
447
  navigateTo(keys) {
405
- if (!this.root?.children) return false;
448
+ const display = this.#displayRoot;
449
+ if (!display?.children) return false;
406
450
  this.#abortPending();
407
451
  this._selectedByDepth.clear();
408
452
  this.tickled.clearTickled();
409
- const newColumns = [this.root.children];
410
- let currentColumn = this.root.children;
453
+ const newColumns = [display.children];
454
+ let currentColumn = display.children;
411
455
  let success = true;
412
456
  let lastNode;
413
457
  let lastDepth = 0;
@@ -442,13 +486,51 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
442
486
  * Programmatically navigate to the first node (breadth-first) matching a predicate.
443
487
  */
444
488
  navigateToNode(predicate) {
445
- if (!this.root?.children) return false;
446
- const keyPath = this.#findNodePath(this.root.children, predicate);
489
+ const display = this.#displayRoot;
490
+ if (!display?.children) return false;
491
+ const keyPath = this.#findNodePath(display.children, predicate);
447
492
  if (!keyPath) return false;
448
493
  return this.navigateTo(keyPath);
449
494
  }
495
+ /** Open the columns down to `key` and move the cursor to it, without changing the selection (search "navigate" mode). */
496
+ revealKey(key) {
497
+ const display = this.#displayRoot;
498
+ if (!display?.children) return false;
499
+ const path = this.#findNodePath(display.children, (n) => n.item.key === key);
500
+ if (!path) return false;
501
+ const columns = [display.children];
502
+ let current = display.children;
503
+ let lastNode;
504
+ let lastDepth = 0;
505
+ for (let depth = 0; depth < path.length; depth++) {
506
+ const node = current.find((n) => n.item.key === path[depth]);
507
+ if (!node) return false;
508
+ lastNode = node;
509
+ lastDepth = depth;
510
+ if (node.children && node.children.length > 0) {
511
+ columns.push(node.children);
512
+ current = node.children;
513
+ } else break;
514
+ }
515
+ this._columns = columns;
516
+ if (lastNode) this.tickled.setTickled(this.#buildTickledId(lastNode, lastDepth));
517
+ this.requestUpdate();
518
+ this.#scrollNavigatedItemIntoView();
519
+ return true;
520
+ }
521
+ /** Replace the selection with `nodes` (search "select" mode). */
522
+ selectMany(nodes) {
523
+ const arr = this.selectionFilter === `none` ? [] : [...nodes];
524
+ const prev = new Set(this._selectedNodes);
525
+ this._selectedNodes = new Set(arr);
526
+ this._selectedKeys = new Set(arr.map((n) => n.item.key));
527
+ this.#lastSelectedNode = arr.at(-1);
528
+ this.#dispatchSelectEvent(prev);
529
+ this.requestUpdate();
530
+ }
450
531
  getNodes() {
451
- if (!this.root?.children) return [];
532
+ const display = this.#displayRoot;
533
+ if (!display?.children) return [];
452
534
  const result = [];
453
535
  const visit = (node, depth) => {
454
536
  result.push({
@@ -457,7 +539,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
457
539
  });
458
540
  if (node.children) for (const child of node.children) visit(child, depth + 1);
459
541
  };
460
- for (const child of this.root.children) visit(child, 0);
542
+ for (const child of display.children) visit(child, 0);
461
543
  return result;
462
544
  }
463
545
  #findNodePath(nodes, predicate, prefix = []) {
@@ -470,6 +552,124 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
470
552
  }
471
553
  }
472
554
  }
555
+ /**
556
+ * Re-render the columns for the current `#displayRoot`, clearing selection.
557
+ * Runs on every `viewRoot` change.
558
+ */
559
+ #applyViewRoot() {
560
+ this.#abortPending();
561
+ const prev = new Set(this._selectedNodes);
562
+ this._selectedByDepth.clear();
563
+ this._selectedKeys = /* @__PURE__ */ new Set();
564
+ this._selectedNodes.clear();
565
+ this.#lastSelectedNode = void 0;
566
+ this.#anchor = void 0;
567
+ this.tickled.clearTickled();
568
+ const display = this.#displayRoot;
569
+ if (display?.children !== void 0) {
570
+ this._columns = display.children ? [display.children] : [];
571
+ this._loadingDepth = void 0;
572
+ } else if (display) {
573
+ this._columns = [];
574
+ this.#loadUnloadedChildren(display, -1, false);
575
+ } else {
576
+ this._columns = [];
577
+ this._loadingDepth = void 0;
578
+ }
579
+ if (prev.size > 0) this.#dispatchSelectEvent(prev);
580
+ }
581
+ /**
582
+ * Rebase the view to show `target`'s children in column 0 (Finder column-view
583
+ * behavior on double-click), keeping the full tree in `root`.
584
+ *
585
+ * - Pass a `MillerNode` (used as-is) or a key (looked up in the full tree).
586
+ * - Pass `undefined` to restore the full-tree view.
587
+ * - Rebasing to the true root is equivalent to restoring the full view.
588
+ * - Leaves are refused (returns `false`).
589
+ *
590
+ * Selection is cleared; all depths, headers, and key paths are relative to
591
+ * the new view root. Fires a `rebase` event when the view actually changes.
592
+ * Returns `false` when nothing changed (unknown key, leaf, or same view).
593
+ */
594
+ rebaseTo(target) {
595
+ if (target === void 0) return this.#clearViewRoot();
596
+ const node = typeof target === `string` ? this.#findNodeByKey(target) : target;
597
+ if (!node || node.item.isLeaf === true) return false;
598
+ if (node === this.viewRoot || node.item.key === this.viewRoot?.item.key) return false;
599
+ if (node === this.root || !this.viewRoot && node.item.key === this.root?.item.key) return this.#clearViewRoot();
600
+ this.viewRoot = node;
601
+ this.#dispatchRebaseEvent();
602
+ return true;
603
+ }
604
+ /**
605
+ * Rebase one level up from the current view root (no-op at the full tree).
606
+ * Fires a `rebase` event when the view actually changes.
607
+ */
608
+ rebaseUp() {
609
+ if (!this.viewRoot) return false;
610
+ const parent = this.#findParentOf(this.viewRoot);
611
+ if (!parent) return false;
612
+ if (parent === this.root) return this.#clearViewRoot();
613
+ this.viewRoot = parent;
614
+ this.#dispatchRebaseEvent();
615
+ return true;
616
+ }
617
+ #clearViewRoot() {
618
+ if (this.viewRoot === void 0) return false;
619
+ this.viewRoot = void 0;
620
+ this.#dispatchRebaseEvent();
621
+ return true;
622
+ }
623
+ #dispatchRebaseEvent() {
624
+ const viewRoot = this.viewRoot;
625
+ const detail = {
626
+ viewRoot,
627
+ path: viewRoot ? this.#pathToRoot(viewRoot) : []
628
+ };
629
+ this.dispatchEvent(new CustomEvent(`rebase`, {
630
+ detail,
631
+ bubbles: true,
632
+ composed: true
633
+ }));
634
+ }
635
+ #findNodeByKey(key) {
636
+ if (!this.root) return void 0;
637
+ if (this.root.item.key === key) return this.root;
638
+ return this.#findNodeIn(this.root.children ?? [], key);
639
+ }
640
+ #findNodeIn(nodes, key) {
641
+ for (const node of nodes) {
642
+ if (node.item.key === key) return node;
643
+ if (node.children) {
644
+ const found = this.#findNodeIn(node.children, key);
645
+ if (found) return found;
646
+ }
647
+ }
648
+ }
649
+ /** Find the parent of `target` (by reference, falling back to key) in the full tree. */
650
+ #findParentOf(target) {
651
+ if (!this.root || target === this.root) return void 0;
652
+ return this.#findParentIn(this.root, target);
653
+ }
654
+ #findParentIn(node, target) {
655
+ if (!node.children) return void 0;
656
+ for (const child of node.children) {
657
+ if (child === target || child.item.key === target.item.key) return node;
658
+ const found = this.#findParentIn(child, target);
659
+ if (found) return found;
660
+ }
661
+ }
662
+ /** Ancestry chain from the true root down to and including `target`. */
663
+ #pathToRoot(target) {
664
+ const chain = [target];
665
+ let current = this.#findParentOf(target);
666
+ while (current) {
667
+ chain.unshift(current);
668
+ if (current === this.root) break;
669
+ current = this.#findParentOf(current);
670
+ }
671
+ return chain;
672
+ }
473
673
  renderColumnContent(nodes, depth) {
474
674
  console.warn(`ixfx-miller-base: renderColumnContent not overridden. depth=${depth}, nodes=${nodes.length}`);
475
675
  return nothing;
@@ -596,7 +796,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
596
796
  */
597
797
  #buildPath(depth) {
598
798
  const path = [];
599
- if (this.root) path.push(this.root);
799
+ if (this.#displayRoot) path.push(this.#displayRoot);
600
800
  for (let d = 0; d < depth; d++) {
601
801
  const sel = this._selectedByDepth.get(d);
602
802
  if (sel) path.push(sel);
@@ -699,57 +899,8 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
699
899
  this.requestUpdate();
700
900
  return;
701
901
  }
702
- if (node.children === void 0) {
703
- this._columns = this._columns.slice(0, depth + 1);
704
- this._loadingDepth = depth + 1;
705
- this.#pendingNode = node;
706
- const controller = new AbortController();
707
- this.#pendingController = controller;
708
- this.dispatchEvent(new CustomEvent(`expand`, {
709
- detail: {
710
- node,
711
- depth
712
- },
713
- bubbles: true,
714
- composed: true
715
- }));
716
- const applyChildren = (children) => {
717
- if (controller.signal.aborted) return;
718
- node.children = children;
719
- this.#pendingNode = void 0;
720
- this.#pendingController = void 0;
721
- this._loadingDepth = void 0;
722
- const cols = this._columns.slice(0, depth + 1);
723
- if (children.length > 0) cols.push(children);
724
- this._columns = cols;
725
- };
726
- const handleError = (err) => {
727
- if (err.name !== `AbortError`) console.error(err);
728
- node.children = [];
729
- this.#pendingNode = void 0;
730
- this.#pendingController = void 0;
731
- this._loadingDepth = void 0;
732
- this.requestUpdate();
733
- };
734
- if (this.model || this.loadChildren) {
735
- if (this.#expandTimer !== void 0) clearTimeout(this.#expandTimer);
736
- this.#expandTimer = setTimeout(() => {
737
- this.#expandTimer = void 0;
738
- if (this.#pendingNode !== node) return;
739
- (this.model ? this.model.getChildren(node, depth, controller.signal) : collectDataResult(this.loadChildren({
740
- node,
741
- depth
742
- }, controller.signal), controller.signal)).then((children) => applyChildren([...children])).catch(handleError);
743
- }, EXPAND_DEBOUNCE_MS);
744
- } else {
745
- this._loadingDepth = void 0;
746
- this.#pendingNode = void 0;
747
- this.#pendingController = void 0;
748
- const newColumns = this._columns.slice(0, depth + 1);
749
- newColumns.push([]);
750
- this._columns = newColumns;
751
- }
752
- } else {
902
+ if (node.children === void 0) this.#loadUnloadedChildren(node, depth, true);
903
+ else {
753
904
  const newColumns = this._columns.slice(0, depth + 1);
754
905
  newColumns.push(node.children);
755
906
  this._columns = newColumns;
@@ -763,6 +914,68 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
763
914
  }));
764
915
  }
765
916
  }
917
+ /**
918
+ * Fetch children for a node whose `children === undefined` and show them as
919
+ * the column after `parentDepth` (with the loading placeholder meanwhile).
920
+ * Shared by branch expansion (`parentDepth` = the branch's column) and view
921
+ * rebasing (`parentDepth` = -1, so the children become column 0).
922
+ *
923
+ * The `depth` reported to `loadChildren`/`model.getChildren` is the node's
924
+ * own depth: its column for expansions, -1 for a view root (it sits above
925
+ * column 0, so loaders computing child depth as `depth + 1` stay correct).
926
+ */
927
+ #loadUnloadedChildren(node, parentDepth, fireExpandEvent) {
928
+ const nodeDepth = parentDepth + 1;
929
+ this._columns = this._columns.slice(0, nodeDepth);
930
+ this._loadingDepth = nodeDepth;
931
+ this.#pendingNode = node;
932
+ const controller = new AbortController();
933
+ this.#pendingController = controller;
934
+ if (fireExpandEvent) this.dispatchEvent(new CustomEvent(`expand`, {
935
+ detail: {
936
+ node,
937
+ depth: parentDepth
938
+ },
939
+ bubbles: true,
940
+ composed: true
941
+ }));
942
+ const applyChildren = (children) => {
943
+ if (controller.signal.aborted) return;
944
+ node.children = children;
945
+ this.#pendingNode = void 0;
946
+ this.#pendingController = void 0;
947
+ this._loadingDepth = void 0;
948
+ const cols = this._columns.slice(0, nodeDepth);
949
+ if (children.length > 0) cols.push(children);
950
+ this._columns = cols;
951
+ };
952
+ const handleError = (err) => {
953
+ if (err.name !== `AbortError`) console.error(err);
954
+ node.children = [];
955
+ this.#pendingNode = void 0;
956
+ this.#pendingController = void 0;
957
+ this._loadingDepth = void 0;
958
+ this.requestUpdate();
959
+ };
960
+ if (this.model || this.loadChildren) {
961
+ if (this.#expandTimer !== void 0) clearTimeout(this.#expandTimer);
962
+ this.#expandTimer = setTimeout(() => {
963
+ this.#expandTimer = void 0;
964
+ if (this.#pendingNode !== node) return;
965
+ (this.model ? this.model.getChildren(node, nodeDepth - 1, controller.signal) : collectDataResult(this.loadChildren({
966
+ node,
967
+ depth: nodeDepth - 1
968
+ }, controller.signal), controller.signal)).then((children) => applyChildren([...children])).catch(handleError);
969
+ }, EXPAND_DEBOUNCE_MS);
970
+ } else {
971
+ this._loadingDepth = void 0;
972
+ this.#pendingNode = void 0;
973
+ this.#pendingController = void 0;
974
+ const newColumns = this._columns.slice(0, nodeDepth);
975
+ newColumns.push([]);
976
+ this._columns = newColumns;
977
+ }
978
+ }
766
979
  #onCheckboxClick(node) {
767
980
  const currentlySelected = this._selectedKeys.has(node.item.key);
768
981
  if (!!node.children?.length ? this.#allDescendantsSelected(node) : currentlySelected) this.#applyCheckedToggle(node, false);
@@ -832,7 +1045,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
832
1045
  #onResizeMove;
833
1046
  #onResizeEnd;
834
1047
  #getColumnHeader(depth) {
835
- if (depth === 0) return this.root?.item.label ?? ``;
1048
+ if (depth === 0) return this.#displayRoot?.item.label ?? ``;
836
1049
  const explicit = this._selectedByDepth.get(depth - 1);
837
1050
  if (explicit) return explicit.item.label;
838
1051
  const prevCol = this._columns[depth - 1];
@@ -843,9 +1056,11 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
843
1056
  }
844
1057
  return ``;
845
1058
  }
846
- #columnStyle(depth) {
1059
+ #columnStyle(depth, isLast) {
847
1060
  const w = this.#columnWidths.get(depth);
848
- return w !== void 0 ? `width:${w}px;flex-shrink:0` : `width:max-content`;
1061
+ if (w !== void 0) return `width:${w}px;flex-grow:0;flex-shrink:0;flex-basis:auto;max-width:none`;
1062
+ if (this.stretchLastColumn && isLast) return `width:max-content;flex-grow:1;flex-shrink:0;flex-basis:auto;min-width:0`;
1063
+ return `width:max-content;flex-grow:0;flex-shrink:0;flex-basis:auto`;
849
1064
  }
850
1065
  #isFocused(node, depth) {
851
1066
  return this.tickled.isTickled(this.#buildTickledId(node, depth));
@@ -858,8 +1073,9 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
858
1073
  }
859
1074
  #renderLoadingColumn() {
860
1075
  const depth = this._loadingDepth;
1076
+ const stretched = this.stretchLastColumn && this.#columnWidths.get(depth) === void 0;
861
1077
  return html`
862
- <div class="column loading-column" style=${this.#columnStyle(depth)}>
1078
+ <div class="column loading-column ${stretched ? `stretch-last` : ``}" style=${this.#columnStyle(depth, true)}>
863
1079
  ${this.headers ? html`
864
1080
  <div class="column-header">${this.#getColumnHeader(depth)}</div>
865
1081
  ` : nothing}
@@ -874,8 +1090,10 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
874
1090
  <div id="container" @keydown=${this.#onKeyDown} tabindex="0">
875
1091
  ${this._columns.map((nodes, depth) => {
876
1092
  const visibleNodes = this.filterPredicate ? nodes.filter((n) => this.filterPredicate(n, depth)) : nodes;
1093
+ const isLast = depth === this._columns.length - 1 && this._loadingDepth === void 0;
1094
+ const stretched = this.stretchLastColumn && isLast && this.#columnWidths.get(depth) === void 0;
877
1095
  return html`
878
- <div class="column" style=${this.#columnStyle(depth)}>
1096
+ <div class="column ${stretched ? `stretch-last` : ``}" style=${this.#columnStyle(depth, isLast)}>
879
1097
  ${this.headers ? html`
880
1098
  <div class="column-header"
881
1099
  @click=${() => this.#onHeaderClick(depth)}
@@ -894,6 +1112,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
894
1112
  })}
895
1113
  ${this._loadingDepth !== void 0 ? this.#renderLoadingColumn() : nothing}
896
1114
  </div>
1115
+ ${this.search.renderField()}
897
1116
  `;
898
1117
  }
899
1118
  _getFocusedIndex(depth) {
@@ -929,6 +1148,7 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
929
1148
  this.styles = [
930
1149
  themeFallbacks,
931
1150
  tickledStyles,
1151
+ listSearchStyles,
932
1152
  css`
933
1153
  :host {
934
1154
  --column-bg: var(--surface-3);
@@ -963,6 +1183,20 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
963
1183
  background: var(--column-bg);
964
1184
  border-right: 1px solid var(--column-separator);
965
1185
  flex-shrink: 0;
1186
+ /* Content-sized columns never grow wider than 80% of the available
1187
+ width — pathological labels truncate with ellipsis instead of
1188
+ pushing every other column out of view */
1189
+ max-width: 80%;
1190
+ /* Smoothly animate the last column stretching to fill remaining
1191
+ space and shrinking back to content width when it stops being last */
1192
+ transition: flex-grow var(--transition, 0.2s ease-out);
1193
+ }
1194
+
1195
+ .column.stretch-last {
1196
+ min-width: 0;
1197
+ /* Stretching fills remaining space by definition, so it is exempt
1198
+ from the 80% auto-size cap */
1199
+ max-width: none;
966
1200
  }
967
1201
 
968
1202
  .column-header {
@@ -1046,6 +1280,12 @@ let MillerBaseElement = class MillerBaseElement extends LitElement {
1046
1280
  __decorate([property({ type: Object })], MillerBaseElement.prototype, "root", void 0);
1047
1281
  __decorate([property({ attribute: false })], MillerBaseElement.prototype, "model", void 0);
1048
1282
  __decorate([property({ type: Boolean })], MillerBaseElement.prototype, "headers", void 0);
1283
+ __decorate([property({
1284
+ type: Boolean,
1285
+ attribute: `stretch-last-column`,
1286
+ reflect: true
1287
+ })], MillerBaseElement.prototype, "stretchLastColumn", void 0);
1288
+ __decorate([property({ attribute: false })], MillerBaseElement.prototype, "viewRoot", void 0);
1049
1289
  __decorate([property({ type: String })], MillerBaseElement.prototype, "interactionMode", void 0);
1050
1290
  __decorate([property({
1051
1291
  reflect: true,
@@ -1057,6 +1297,11 @@ __decorate([property({
1057
1297
  })], MillerBaseElement.prototype, "exclusivity", void 0);
1058
1298
  __decorate([property({ type: Object })], MillerBaseElement.prototype, "loadChildren", void 0);
1059
1299
  __decorate([property({ attribute: false })], MillerBaseElement.prototype, "filterPredicate", void 0);
1300
+ __decorate([property({
1301
+ type: String,
1302
+ attribute: `search-modes`,
1303
+ reflect: true
1304
+ })], MillerBaseElement.prototype, "searchModes", void 0);
1060
1305
  __decorate([state()], MillerBaseElement.prototype, "_columns", void 0);
1061
1306
  __decorate([state()], MillerBaseElement.prototype, "_loadingDepth", void 0);
1062
1307
  __decorate([state()], MillerBaseElement.prototype, "_selectedNodes", void 0);
@@ -1294,14 +1539,28 @@ var MillerTreeController = class {
1294
1539
  * Any ancestor `MillerNode`s with unloaded children are populated
1295
1540
  * synchronously before calling `element.navigateTo()`.
1296
1541
  *
1542
+ * Key paths are relative to the element's current view root: when the view
1543
+ * is rebased, only matches at or below the view root can be reached.
1544
+ *
1297
1545
  * Returns `false` if no matching node is found.
1298
1546
  */
1299
1547
  navigateToValue(predicate) {
1300
1548
  const treePath = this.#findTreePath(this.#root, predicate);
1301
1549
  if (!treePath) return false;
1302
- let currentColumn = this.#element.root?.children ?? [];
1550
+ const base = this.#element.viewRoot ?? this.#element.root;
1551
+ if (!base) return false;
1552
+ let startIndex = 1;
1553
+ if (this.#element.viewRoot) {
1554
+ const mappedBase = this.#nodeToTree.get(base);
1555
+ if (!mappedBase) return false;
1556
+ const baseIndex = treePath.indexOf(mappedBase);
1557
+ if (baseIndex < 0) return false;
1558
+ startIndex = baseIndex + 1;
1559
+ if (base.children === void 0) base.children = this.#childrenOf(mappedBase);
1560
+ }
1561
+ let currentColumn = base.children ?? [];
1303
1562
  const keys = [];
1304
- for (let i = 1; i < treePath.length; i++) {
1563
+ for (let i = startIndex; i < treePath.length; i++) {
1305
1564
  const treeNode = treePath[i];
1306
1565
  const key = this.#options.valueToItem(treeNode.getValue(), treeNode).key;
1307
1566
  keys.push(key);