@ixfx/components 0.13.5 → 0.13.6

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.
@@ -1027,6 +1027,19 @@ function treeScrollTopToReveal(params, index, scrollTop, topInset = 0) {
1027
1027
  if (bottom > scrollTop + params.viewportHeight) return Math.max(0, bottom - params.viewportHeight);
1028
1028
  return scrollTop;
1029
1029
  }
1030
+ /**
1031
+ * `scrollTop` that centres row `index` in the viewport, but only when the row is
1032
+ * not already fully visible (below `topInset` px of overlay). Otherwise returns
1033
+ * `scrollTop` unchanged.
1034
+ */
1035
+ function treeScrollTopToCenterIfHidden(params, index, scrollTop, topInset = 0) {
1036
+ if (index < 0 || index >= params.count) return scrollTop;
1037
+ const top = treeRowTop(params, index);
1038
+ const bottom = top + params.rowHeight;
1039
+ if (top >= scrollTop + topInset && bottom <= scrollTop + params.viewportHeight) return scrollTop;
1040
+ const centred = top + params.rowHeight / 2 - (params.viewportHeight + topInset) / 2;
1041
+ return Math.max(0, centred);
1042
+ }
1030
1043
  /** Rows that fit one viewport (PageUp / PageDown step), at least 1. */
1031
1044
  function treePageSize(params) {
1032
1045
  return Math.max(1, Math.floor(params.viewportHeight / treeStride(params)) - 1);
@@ -1166,6 +1179,8 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
1166
1179
  this.#stickyDirty = true;
1167
1180
  this.#stickyRaf = 0;
1168
1181
  this.#stickyScrollBound = false;
1182
+ this.#selectOrigin = `user`;
1183
+ this.#selectSilent = false;
1169
1184
  this.#revealToken = 0;
1170
1185
  this.#onKeyDown = (event) => {
1171
1186
  if (this.search.isOpen) return;
@@ -1467,12 +1482,12 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
1467
1482
  * container's `scrollTop` and `_scrollTop` together, so the very next render
1468
1483
  * already contains the target row — no frame where it is missing.
1469
1484
  */
1470
- #revealIndex(index) {
1485
+ #revealIndex(index, mode = `edge`) {
1471
1486
  const container = this.#container();
1472
1487
  if (!container) return;
1473
1488
  const params = this.#layoutParams(this.#getFlat().rows.length);
1474
1489
  const inset = this.renderRoot.querySelector(`.sticky-headers`)?.getBoundingClientRect().height ?? 0;
1475
- const next = treeScrollTopToReveal(params, index, container.scrollTop, inset);
1490
+ const next = (mode === `center-if-hidden` ? treeScrollTopToCenterIfHidden : treeScrollTopToReveal)(params, index, container.scrollTop, inset);
1476
1491
  if (next === container.scrollTop && next === this._scrollTop) return;
1477
1492
  container.scrollTop = next;
1478
1493
  this._scrollTop = next;
@@ -2371,8 +2386,20 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
2371
2386
  this.#dispatchSelect(prev);
2372
2387
  this.requestUpdate();
2373
2388
  }
2374
- navigateTo(keys) {
2389
+ navigateTo(keys, options = {}) {
2375
2390
  if (!this.root?.children) return false;
2391
+ const prevOrigin = this.#selectOrigin;
2392
+ const prevSilent = this.#selectSilent;
2393
+ this.#selectOrigin = `programmatic`;
2394
+ this.#selectSilent = options.silent === true;
2395
+ try {
2396
+ return this.#navigateTo(keys, options.reveal ?? `edge`);
2397
+ } finally {
2398
+ this.#selectOrigin = prevOrigin;
2399
+ this.#selectSilent = prevSilent;
2400
+ }
2401
+ }
2402
+ #navigateTo(keys, reveal) {
2376
2403
  this.#abortPending();
2377
2404
  this.#clearSelectionInternal();
2378
2405
  this.tickled.clearTickled();
@@ -2393,7 +2420,7 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
2393
2420
  }
2394
2421
  this.#stickyDirty = true;
2395
2422
  this.requestUpdate();
2396
- this.#scrollKeyIntoView(keys.at(-1));
2423
+ this.#scrollKeyIntoView(keys.at(-1), reveal);
2397
2424
  return true;
2398
2425
  }
2399
2426
  navigateToNode(predicate) {
@@ -2449,11 +2476,15 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
2449
2476
  this._selectedNodes.clear();
2450
2477
  this.selectedNode = void 0;
2451
2478
  }
2479
+ #selectOrigin;
2480
+ #selectSilent;
2452
2481
  #dispatchSelect(previous) {
2453
2482
  this._selectionVersion++;
2483
+ if (this.#selectSilent) return;
2454
2484
  const detail = {
2455
2485
  selected: new Set(this._selectedNodes),
2456
- previous
2486
+ previous,
2487
+ origin: this.#selectOrigin
2457
2488
  };
2458
2489
  this.dispatchEvent(new CustomEvent(`select`, {
2459
2490
  detail,
@@ -2788,7 +2819,15 @@ let TreeBaseElement = class TreeBaseElement extends LitElement {
2788
2819
  get _pendingNode() {
2789
2820
  return this.#pendingNode;
2790
2821
  }
2791
- #scrollKeyIntoView(key) {
2822
+ #scrollKeyIntoView(key, mode = `edge`) {
2823
+ if (mode === `center-if-hidden`) {
2824
+ this.updateComplete.then(() => {
2825
+ this.#measureMetrics();
2826
+ const idx = this.#getFlat().indexByKey.get(key);
2827
+ if (idx !== void 0) this.#revealIndex(idx, mode);
2828
+ });
2829
+ return;
2830
+ }
2792
2831
  if (this.#virtualActive()) {
2793
2832
  this.updateComplete.then(() => {
2794
2833
  const idx = this.#getFlat().indexByKey.get(key);
@@ -3553,6 +3592,9 @@ var TreeController = class {
3553
3592
  #options;
3554
3593
  #nodeToTree = /* @__PURE__ */ new WeakMap();
3555
3594
  #identityToTreeNode = /* @__PURE__ */ new Map();
3595
+ #keyToTreeNode = /* @__PURE__ */ new Map();
3596
+ /** `valueToItem` results per source node identity; replaced wholesale by `sync()`. */
3597
+ #itemCache = /* @__PURE__ */ new WeakMap();
3556
3598
  #connectSignal;
3557
3599
  constructor(root, element, options) {
3558
3600
  this.#root = root;
@@ -3563,13 +3605,13 @@ var TreeController = class {
3563
3605
  this.disconnect();
3564
3606
  this.#connectSignal = new AbortController();
3565
3607
  const { signal } = this.#connectSignal;
3566
- const rootValue = this.#root.getValue();
3567
- const rootItem = this.#options.valueToItem(rootValue, this.#root);
3568
- const rootChildren = this.#childrenOf(this.#root);
3569
- this.#element.root = {
3570
- item: rootItem,
3571
- children: rootChildren
3572
- };
3608
+ this.#itemCache = /* @__PURE__ */ new WeakMap();
3609
+ this.#identityToTreeNode.clear();
3610
+ this.#keyToTreeNode.clear();
3611
+ const rootNode = { item: this.#itemOf(this.#root) };
3612
+ this.#nodeToTree.set(rootNode, this.#root);
3613
+ rootNode.children = this.#childrenOf(this.#root);
3614
+ this.#element.root = rootNode;
3573
3615
  this.#element.addEventListener(`expand`, (e) => {
3574
3616
  const { node } = e.detail;
3575
3617
  const treeNode = this.#nodeToTree.get(node);
@@ -3583,45 +3625,101 @@ var TreeController = class {
3583
3625
  this.#connectSignal?.abort();
3584
3626
  this.#connectSignal = void 0;
3585
3627
  }
3586
- navigateToValue(predicate) {
3587
- const treePath = this.#findTreePath(this.#root, predicate);
3588
- if (!treePath) return false;
3628
+ /**
3629
+ * Re-derive children for every already-materialised node from the source tree
3630
+ * and patch them in place. Existing `TreeNode` objects are reused by key, so
3631
+ * expansion and selection survive; only added and removed nodes change. Calls
3632
+ * `invalidateStructure()` once.
3633
+ */
3634
+ sync() {
3635
+ const root = this.#element.root;
3636
+ if (!root) return;
3637
+ this.#itemCache = /* @__PURE__ */ new WeakMap();
3638
+ this.#identityToTreeNode.clear();
3639
+ this.#keyToTreeNode.clear();
3640
+ this.#nodeToTree.set(root, this.#root);
3641
+ root.item = this.#itemOf(this.#root);
3642
+ root.children = this.#syncChildren(this.#root, root.children ?? []);
3643
+ this.#element.invalidateStructure();
3644
+ }
3645
+ /** The source node whose item key is `key`, if it has been materialised. */
3646
+ lookupByKey(key) {
3647
+ return this.#keyToTreeNode.get(key);
3648
+ }
3649
+ /** Navigate using item keys (root excluded), without searching the source tree. */
3650
+ navigateToKeyPath(keys, options) {
3651
+ if (keys.length === 0) return false;
3589
3652
  let currentColumn = this.#element.root?.children ?? [];
3590
- const keys = [];
3591
- for (let i = 1; i < treePath.length; i++) {
3592
- const treeNode = treePath[i];
3593
- const key = this.#options.valueToItem(treeNode.getValue(), treeNode).key;
3594
- keys.push(key);
3595
- if (i < treePath.length - 1) {
3596
- const node = currentColumn.find((n) => n.item.key === key);
3597
- if (!node) return false;
3598
- if (node.children === void 0) {
3599
- const mapped = this.#nodeToTree.get(node);
3600
- if (mapped) {
3601
- node.children = this.#childrenOf(mapped);
3602
- this.#element.invalidateStructure();
3603
- }
3653
+ for (let i = 0; i < keys.length - 1; i++) {
3654
+ const node = currentColumn.find((n) => n.item.key === keys[i]);
3655
+ if (!node) return false;
3656
+ if (node.children === void 0) {
3657
+ const mapped = this.#nodeToTree.get(node);
3658
+ if (mapped) {
3659
+ node.children = this.#childrenOf(mapped);
3660
+ this.#element.invalidateStructure();
3604
3661
  }
3605
- currentColumn = node.children ?? [];
3606
3662
  }
3663
+ currentColumn = node.children ?? [];
3664
+ }
3665
+ return this.#element.navigateTo(keys, options);
3666
+ }
3667
+ /** Navigate to the source node with this identity, walking up via `getParent()`. */
3668
+ navigateToIdentity(identity, options) {
3669
+ let node = this.#identityToTreeNode.get(identity);
3670
+ if (!node) return false;
3671
+ const keys = [];
3672
+ const rootId = this.#root.getIdentity();
3673
+ while (node && node.getIdentity() !== rootId) {
3674
+ keys.unshift(this.#itemOf(node).key);
3675
+ node = node.getParent();
3607
3676
  }
3608
- return this.#element.navigateTo(keys);
3677
+ return node !== void 0 && this.navigateToKeyPath(keys, options);
3678
+ }
3679
+ navigateToValue(predicate, options) {
3680
+ const treePath = this.#findTreePath(this.#root, predicate);
3681
+ if (!treePath) return false;
3682
+ return this.navigateToKeyPath(treePath.slice(1).map((n) => this.#itemOf(n).key), options);
3683
+ }
3684
+ #itemOf(treeNode, isLeaf) {
3685
+ const id = treeNode.getIdentity();
3686
+ let userItem = this.#itemCache.get(id);
3687
+ if (!userItem) {
3688
+ userItem = this.#options.valueToItem(treeNode.getValue(), treeNode);
3689
+ this.#itemCache.set(id, userItem);
3690
+ }
3691
+ return isLeaf === void 0 ? userItem : {
3692
+ ...userItem,
3693
+ isLeaf: userItem.isLeaf ?? isLeaf
3694
+ };
3609
3695
  }
3610
3696
  #childrenOf(treeNode) {
3611
3697
  return Array.from(treeNode.children(), (child) => this.#toTreeNode(child));
3612
3698
  }
3699
+ #register(node, treeNode) {
3700
+ this.#nodeToTree.set(node, treeNode);
3701
+ this.#identityToTreeNode.set(treeNode.getIdentity(), treeNode);
3702
+ this.#keyToTreeNode.set(node.item.key, treeNode);
3703
+ }
3613
3704
  #toTreeNode(treeNode) {
3614
- const value = treeNode.getValue();
3615
3705
  const childList = [...treeNode.children()];
3616
- const userItem = this.#options.valueToItem(value, treeNode);
3617
- const isLeaf = userItem.isLeaf ?? childList.length === 0;
3618
- const treeNodeMap = { item: {
3619
- ...userItem,
3620
- isLeaf
3621
- } };
3622
- this.#nodeToTree.set(treeNodeMap, treeNode);
3623
- this.#identityToTreeNode.set(treeNode.getIdentity(), treeNode);
3624
- return treeNodeMap;
3706
+ for (const child of childList) this.#identityToTreeNode.set(child.getIdentity(), child);
3707
+ const node = { item: this.#itemOf(treeNode, childList.length === 0) };
3708
+ this.#register(node, treeNode);
3709
+ return node;
3710
+ }
3711
+ /** Patch `existing` to match the source children of `treeNode`; recurse into materialised nodes. */
3712
+ #syncChildren(treeNode, existing) {
3713
+ const byKey = new Map(existing.map((n) => [n.item.key, n]));
3714
+ return Array.from(treeNode.children(), (child) => {
3715
+ const fresh = this.#toTreeNode(child);
3716
+ const old = byKey.get(fresh.item.key);
3717
+ if (!old) return fresh;
3718
+ old.item = fresh.item;
3719
+ this.#register(old, child);
3720
+ if (old.children !== void 0) old.children = this.#syncChildren(child, old.children);
3721
+ return old;
3722
+ });
3625
3723
  }
3626
3724
  #findTreePath(node, predicate, ancestors = []) {
3627
3725
  const path = [...ancestors, node];
@@ -222,6 +222,11 @@ type TreeSelectDetail = {
222
222
  readonly selected: ReadonlySet<TreeNode>;
223
223
  /** The previous selection set (empty set if there was no prior selection). */
224
224
  readonly previous: ReadonlySet<TreeNode>;
225
+ /**
226
+ * What caused the change: `programmatic` for `navigateTo` and friends, `user`
227
+ * otherwise. Absent on components that do not distinguish the two.
228
+ */
229
+ readonly origin?: `user` | `programmatic`;
225
230
  };
226
231
  /**
227
232
  * Fired when an item enters cursor focus — either the pointer hovers over it, or
package/dist/tree.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as TreeDataModelOptions, D as TreeTickleDetail, E as TreeSelectEvent, O as TreeTickleEvent, T as TreeSelectDetail, _ as TreeDropTarget, b as TreeItemClickDetail, c as TreeDragContext, d as TreeDragStartEvent, g as TreeDropPosition, h as TreeDropEvent, i as TreeCollapseEvent, k as TreeDataModel, l as TreeDragEndEvent, m as TreeDropDetail, n as TreeActivateEvent, o as TreeComponentEventMap, p as TreeDropContext, r as TreeCollapseDetail, s as TreeDndMode, t as TreeActivateDetail, u as TreeDragOverEvent, v as TreeExpandDetail, x as TreeItemClickEvent, y as TreeExpandEvent } from "./tree-component-Cj8V3uar.js";
1
+ import { A as TreeDataModelOptions, D as TreeTickleDetail, E as TreeSelectEvent, O as TreeTickleEvent, T as TreeSelectDetail, _ as TreeDropTarget, b as TreeItemClickDetail, c as TreeDragContext, d as TreeDragStartEvent, g as TreeDropPosition, h as TreeDropEvent, i as TreeCollapseEvent, k as TreeDataModel, l as TreeDragEndEvent, m as TreeDropDetail, n as TreeActivateEvent, o as TreeComponentEventMap, p as TreeDropContext, r as TreeCollapseDetail, s as TreeDndMode, t as TreeActivateDetail, u as TreeDragOverEvent, v as TreeExpandDetail, x as TreeItemClickEvent, y as TreeExpandEvent } from "./tree-component-Bx40hxMz.js";
2
2
  import { a as TreeItem, n as TreeExclusivity, o as TreeNode, r as TreeFilterPredicate, s as TreeSelectionFilter, t as LoadChildrenCallback } from "./types-DQ7KOUIj.js";
3
- import { a as TreeDropVisibleRow, c as TreeListElement, d as TreeBaseElementEventMap, f as TreeVirtualize, i as TreeDropRowInfo, l as TreeCheckboxState, n as TreeDropControllerConfig, o as TreeController, p as TreeConfig, r as TreeDropResolvedDrag, s as TreeControllerOptions, t as TreeDropController, u as TreeBaseElement } from "./index-CCC0G1nO.js";
4
- export { type LoadChildrenCallback, type TreeActivateDetail, type TreeActivateEvent, TreeBaseElement, TreeBaseElementEventMap, type TreeCheckboxState, type TreeCollapseDetail, type TreeCollapseEvent, type TreeComponentEventMap, TreeConfig, TreeController, TreeControllerOptions, TreeDataModel, type TreeDataModelOptions, type TreeDndMode, type TreeDragContext, type TreeDragEndEvent, type TreeDragOverEvent, type TreeDragStartEvent, type TreeDropContext, TreeDropController, TreeDropControllerConfig, type TreeDropDetail, type TreeDropEvent, type TreeDropPosition, TreeDropResolvedDrag, TreeDropRowInfo, type TreeDropTarget, TreeDropVisibleRow, type TreeExclusivity, type TreeExpandDetail, type TreeExpandEvent, type TreeFilterPredicate, type TreeItem, type TreeItemClickDetail, type TreeItemClickEvent, TreeListElement, type TreeNode, type TreeSelectDetail, type TreeSelectEvent, type TreeSelectionFilter, type TreeTickleDetail, type TreeTickleEvent, TreeVirtualize };
3
+ import { a as TreeDropVisibleRow, c as TreeListElement, d as TreeBaseElementEventMap, f as TreeNavigateOptions, h as TreeConfig, i as TreeDropRowInfo, l as TreeCheckboxState, m as TreeVirtualize, n as TreeDropControllerConfig, o as TreeController, p as TreeRevealMode, r as TreeDropResolvedDrag, s as TreeControllerOptions, t as TreeDropController, u as TreeBaseElement } from "./index-AifRs3k9.js";
4
+ export { type LoadChildrenCallback, type TreeActivateDetail, type TreeActivateEvent, TreeBaseElement, TreeBaseElementEventMap, type TreeCheckboxState, type TreeCollapseDetail, type TreeCollapseEvent, type TreeComponentEventMap, TreeConfig, TreeController, TreeControllerOptions, TreeDataModel, type TreeDataModelOptions, type TreeDndMode, type TreeDragContext, type TreeDragEndEvent, type TreeDragOverEvent, type TreeDragStartEvent, type TreeDropContext, TreeDropController, TreeDropControllerConfig, type TreeDropDetail, type TreeDropEvent, type TreeDropPosition, TreeDropResolvedDrag, TreeDropRowInfo, type TreeDropTarget, TreeDropVisibleRow, type TreeExclusivity, type TreeExpandDetail, type TreeExpandEvent, type TreeFilterPredicate, type TreeItem, type TreeItemClickDetail, type TreeItemClickEvent, TreeListElement, TreeNavigateOptions, type TreeNode, TreeRevealMode, type TreeSelectDetail, type TreeSelectEvent, type TreeSelectionFilter, type TreeTickleDetail, type TreeTickleEvent, TreeVirtualize };
package/dist/tree.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as TreeDataModel, i as TreeDropController, n as TreeListElement, r as TreeBaseElement, t as TreeController } from "./tree-Df00opxv.js";
1
+ import { a as TreeDataModel, i as TreeDropController, n as TreeListElement, r as TreeBaseElement, t as TreeController } from "./tree-BjrfaX7d.js";
2
2
  export { TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeListElement };
@@ -1,6 +1,6 @@
1
- import { F as ListDragPreviewSourceResolver, P as ListDragPreviewRenderer, R as ListNativeDragStartEvent } from "./tree-component-Cj8V3uar.js";
1
+ import { F as ListDragPreviewSourceResolver, P as ListDragPreviewRenderer, R as ListNativeDragStartEvent } from "./tree-component-Bx40hxMz.js";
2
2
  import { n as ListViewportChangeEvent } from "./list-viewport-types-BRZsLcvp.js";
3
- import { S as ListSearchController } from "./index-BYIRgv49.js";
3
+ import { S as ListSearchController } from "./index-CzFRWPzh.js";
4
4
  import { i as ListInteractionMode, l as ListSelectionMode, r as ListElementEventMap } from "./list-selection-types-PjKdhF-a.js";
5
5
  import "./editable-label-rzXkjf1H.js";
6
6
  import { LitElement, PropertyValues } from "lit";
@@ -1,5 +1,5 @@
1
- import { I as ListDragSource, L as ListNativeDragStartDetail, M as ListDragPreview, N as ListDragPreviewContext, P as ListDragPreviewRenderer, R as ListNativeDragStartEvent, j as ListDragEntry } from "./tree-component-Cj8V3uar.js";
1
+ import { I as ListDragSource, L as ListNativeDragStartDetail, M as ListDragPreview, N as ListDragPreviewContext, P as ListDragPreviewRenderer, R as ListNativeDragStartEvent, j as ListDragEntry } from "./tree-component-Bx40hxMz.js";
2
2
  import { n as ListViewportChangeEvent, t as ListViewportChangeDetail } from "./list-viewport-types-BRZsLcvp.js";
3
3
  import { a as ListItemClickDetail, c as ListSelectEvent, d as ListTickleEvent, i as ListInteractionMode, l as ListSelectionMode, n as ListActivateEvent, o as ListItemClickEvent, r as ListElementEventMap, s as ListSelectDetail, t as ListActivateDetail, u as ListTickleDetail } from "./list-selection-types-PjKdhF-a.js";
4
- import { a as ListContextMenuDetail, c as ListDragLeaveEvent, d as ListDropEvent, f as ListEditCancelDetail, h as ListEditCommitEvent, i as VerticalListElementEventMap, l as ListDragOverEvent, m as ListEditCommitDetail, n as CustomCheckboxOptions, o as ListContextMenuEvent, p as ListEditCancelEvent, r as VerticalListElement, s as ListDragLeaveDetail, t as AddItemOptions, u as ListDropDetail } from "./vertical-list-m7GJtXwv.js";
4
+ import { a as ListContextMenuDetail, c as ListDragLeaveEvent, d as ListDropEvent, f as ListEditCancelDetail, h as ListEditCommitEvent, i as VerticalListElementEventMap, l as ListDragOverEvent, m as ListEditCommitDetail, n as CustomCheckboxOptions, o as ListContextMenuEvent, p as ListEditCancelEvent, r as VerticalListElement, s as ListDragLeaveDetail, t as AddItemOptions, u as ListDropDetail } from "./vertical-list-BohPAS04.js";
5
5
  export { AddItemOptions, CustomCheckboxOptions, type ListActivateDetail, type ListActivateEvent, type ListContextMenuDetail, type ListContextMenuEvent, type ListDragEntry, type ListDragLeaveDetail, type ListDragLeaveEvent, type ListDragOverEvent, type ListDragPreview, type ListDragPreviewContext, type ListDragPreviewRenderer, type ListDragSource, type ListDropDetail, type ListDropEvent, type ListEditCancelDetail, type ListEditCancelEvent, type ListEditCommitDetail, type ListEditCommitEvent, type ListElementEventMap, type ListInteractionMode, type ListItemClickDetail, type ListItemClickEvent, type ListNativeDragStartDetail, type ListNativeDragStartEvent, type ListSelectDetail, type ListSelectEvent, type ListSelectionMode, type ListTickleDetail, type ListTickleEvent, type ListViewportChangeDetail, type ListViewportChangeEvent, VerticalListElement, VerticalListElementEventMap };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
- "version": "0.13.5",
4
- "generated": "2026-10-04T12:58:50.843Z",
3
+ "version": "0.13.6",
4
+ "generated": "2026-10-05T20:21:38.161Z",
5
5
  "components": [
6
6
  {
7
7
  "name": "ac-text",
package/docs-user/tree.md CHANGED
@@ -46,6 +46,8 @@ type TreeNode = {
46
46
 
47
47
  `TreeNode` is **immutable** — structural changes always produce a new root. Never mutate a node in place; use `TreeDataModel` convenience methods or replace `root` directly.
48
48
 
49
+ **Incremental updates.** `TreeController.sync()` re-derives children of already-materialised nodes from the source tree and patches them in place (nodes are reused by key, so expansion and selection survive). `navigateToKeyPath(keys)`, `navigateToIdentity(id)` and `lookupByKey(key)` resolve targets without searching the source tree.
50
+
49
51
  **Escape hatch for in-place edits.** The component itself fills in `node.children` when lazy-loading, and `TreeController` does the same. If you must edit `node.children` or `node.item` (e.g. an icon) in place, call `el.invalidateStructure()` afterwards. It drops the cached whole-tree indexes (parent map, icon depths, filter matches, checkbox states) and re-renders. Visible rows are re-derived on every update, so ordinary expand / collapse never needs it.
50
52
 
51
53
  ---
@@ -248,7 +250,7 @@ they do without windowing and cover rows that are not rendered:
248
250
  is never missing.
249
251
  - **Incremental search**: `navigate`, `select` and `filter` modes search the whole tree.
250
252
  Match highlights are repainted as the window moves.
251
- - **`navigateTo` / `revealKey`**: expand ancestors and scroll to the row.
253
+ - **`navigateTo` / `revealKey`**: expand ancestors and scroll to the row. `navigateTo(keys, { reveal: 'center-if-hidden' })` centres the row only when it is off screen (arithmetic, no DOM measuring); `{ silent: true }` suppresses the `select` event. Otherwise `select` detail carries `origin: 'user' | 'programmatic'`.
252
254
  - **Drag and drop**: drop targets are computed from the layout, so auto-scroll while dragging works.
253
255
  - **Sticky headers**: found by arithmetic, with no per-row measuring.
254
256
 
@@ -817,7 +819,7 @@ interface TreeComponent extends EventTarget {
817
819
  select(node: TreeNode): void;
818
820
  deselect(node: TreeNode): void;
819
821
  clearSelection(): void;
820
- navigateTo(keys: readonly string[]): boolean;
822
+ navigateTo(keys: readonly string[], options?: { reveal?: 'edge' | 'center-if-hidden'; silent?: boolean }): boolean;
821
823
  navigateToNode(predicate: (node: TreeNode) => boolean): boolean;
822
824
  getNodes(): ReadonlyArray<{ node: TreeNode; depth: number }>;
823
825
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "type": "module",
4
- "version": "0.13.5",
4
+ "version": "0.13.6",
5
5
  "packageManager": "pnpm@10.0.0",
6
6
  "description": "Web components for ixfx — Lit-based UI toolkit",
7
7
  "author": "Clint Heyer",