@keenmate/svelte-treeview 5.0.0-rc10 → 5.0.0-rc12

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 (39) hide show
  1. package/CHANGELOG.md +953 -889
  2. package/README.md +362 -1103
  3. package/ai/INDEX.txt +349 -349
  4. package/ai/advanced-patterns.txt +1 -1
  5. package/ai/drag-drop.txt +397 -397
  6. package/ai/performance.txt +349 -349
  7. package/ai/tree-editing.txt +1 -1
  8. package/component-variables.manifest.json +143 -145
  9. package/dist/components/ContextMenuDivider.svelte +2 -2
  10. package/dist/components/ContextMenuItem.svelte +58 -10
  11. package/dist/components/ContextMenuLevel.svelte +142 -0
  12. package/dist/components/ContextMenuLevel.svelte.d.ts +32 -0
  13. package/dist/components/Node.svelte +495 -482
  14. package/dist/components/Node.svelte.d.ts +1 -1
  15. package/dist/components/Tree.svelte +1370 -1300
  16. package/dist/components/Tree.svelte.d.ts +86 -72
  17. package/dist/constants.generated.d.ts +1 -1
  18. package/dist/constants.generated.js +1 -1
  19. package/dist/core/TreeController.svelte.d.ts +94 -61
  20. package/dist/core/TreeController.svelte.js +560 -158
  21. package/dist/index.d.ts +1 -1
  22. package/dist/ltree/ltree.svelte.js +73 -4
  23. package/dist/ltree/types.d.ts +17 -0
  24. package/dist/styles/animations.css +17 -17
  25. package/dist/styles/base.css +14 -13
  26. package/dist/styles/checkbox.css +49 -36
  27. package/dist/styles/context-menu.css +40 -57
  28. package/dist/styles/dark-mode.css +29 -124
  29. package/dist/styles/debug.css +15 -15
  30. package/dist/styles/drag-drop.css +170 -170
  31. package/dist/styles/drop-zones.css +85 -85
  32. package/dist/styles/loading.css +12 -12
  33. package/dist/styles/main.css +44 -44
  34. package/dist/styles/node.css +24 -21
  35. package/dist/styles/states.css +59 -28
  36. package/dist/styles/toggle-icons.css +29 -29
  37. package/dist/styles/variables.css +121 -119
  38. package/dist/styles.css +423 -475
  39. package/package.json +4 -1
package/CHANGELOG.md CHANGED
@@ -1,889 +1,953 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ## [5.0.0-rc10] - 2026-06-10
11
-
12
- ### Changed
13
- - **CSS custom properties rescoped from `:root` to `.ltree-container`**: All `--ltree-*` declarations now live on the tree's root element instead of the document root. This mirrors the `:host`-scoped pattern from `@keenmate/web-multiselect` / `web-daterangepicker` and is the only way `--base-*` theming actually works at subtree scope. Previously, a wrapper around the tree that set `--base-accent-color: red` had no effect because `--ltree-primary`'s `var(--base-accent-color, ...)` substitution was frozen at `:root`. With the new scope, the substitution recomputes on `.ltree-container` (a descendant of the wrapper), and `--base-*` set on any ancestor flows through correctly. Multiple trees on the same page can now be themed differently via subtree wrappers.
14
-
15
- **Consumer migration**: setting `--ltree-*` on a wrapper element (e.g., `.my-theme { --ltree-primary: red }`) no longer cascades into the tree, because `.ltree-container` has its own direct rule for every `--ltree-*` token. To theme a subtree, prefer `--base-accent-color` (and the rest of the `--base-*` taxonomy) on the wrapper every primary-derived tint follows via the existing `color-mix()` chains. To override an `--ltree-*` directly, target `.ltree-container`: `.my-wrap .ltree-container { --ltree-primary: red }`.
16
-
17
- ### Added
18
- - **`--ltree-bg` CSS variable**: The tree's `.ltree-container` now paints its own default background (`var(--base-main-bg, light-dark(#ffffff, #1a1a1a))`) so consumers don't need to wrap the tree in a colored container for a visible surface. The dark-mode CSS flips `--ltree-bg` alongside the other surface tokens. Set `--ltree-bg: transparent` to restore the pre-rc10 layered behavior.
19
- - **Built-in dark mode with belt-and-suspenders coverage**: New `dark-mode.css` partial in an `@layer overrides;` cascade catches all four signals OS preference via `@media (prefers-color-scheme: dark)`, framework theme classes (`[data-theme="dark"]`, `[data-bs-theme="dark"]`, `.dark`), per-instance opt-in via the new `theme` prop on `<Tree>`, and symmetric `light` selectors so a single tree can be forced back to light on a dark page. Every `--ltree-*` color variable's fallback now uses `light-dark(<light>, <dark>)` so consumers who declare `html { color-scheme: light dark }` get OS-aware behavior automatically with zero JavaScript.
20
- - **`theme` prop on `<Tree>`**: `'dark' | 'light' | null | undefined`. Forwards to the root `.ltree-container` as `data-theme`, where the stylesheet's per-instance selectors (`.ltree-container[data-theme="dark"]`) take over. Leave `undefined` to inherit from the page (OS, framework class, etc.).
21
- - **`--ltree-elevated-bg`, `--tree-ghost-shadow` CSS variables**: `elevated-bg` reads through the canonical `--base-elevated-bg` chain for surfaces above the main background (currently feeds the context-menu chain); `tree-ghost-shadow` replaces a hardcoded `rgba(0,0,0,0.3)` literal in the touch drag ghost so consumers can recolor or remove the shadow.
22
- - **`getIsDropAllowedCallback` prop + `getNodeIsDropAllowed(node)` method**: Callback variant for the existing `isDropAllowedMember` data-field prop, matching the pattern rc09 introduced for `getIsExpandedCallback` / `getIsSelectableCallback` / `getIsSelectedCallback`. Seeded at `insertArray` time. Precedence: callback > member > default. Additionally, `getIsDraggableCallback` is now actually applied during the seed walk (the prop existed in rc09 but inert at insert-time only consumed lazily in some paths). Precedence: callback > member > default for both.
23
-
24
- ### Changed
25
- - **CSS file layout aligned with the Bliss web-component guidelines**: All `_<partial>.css` renamed to `<partial>.css` (the underscore was a legacy SASS convention and these are plain CSS modules). `main.css` now declares `@layer variables, component, overrides;` and wraps every `@import` in `layer(...)` — consumers' unlayered overrides automatically beat every rule in the library, no `!important` needed. New canonical Tier-2 stubs (`controls.css`, `floating.css`, `animations.css`) added even where empty, so the file set is identical across the KeenMate component suite. `@keyframes bounce` and `@keyframes ltree-spin` moved into `animations.css`.
26
- - **Loading overlay background adapts to theme**: `--ltree-loading-bg` no longer hardcodes `rgba(255, 255, 255, 0.8)`; it now composes 80% of `--base-main-bg` against the page so the semi-transparent overlay reads correctly on both light and dark surfaces.
27
- - **`component-variables.manifest.json` extended**: `base-elevated-bg` added to the `baseVariables` list; `ltree-elevated-bg` and `tree-ghost-shadow` added to `componentVariables`. The manifest now stays in sync with the new variable surface.
28
-
29
- ### Fixed
30
- - **Virtual scroll stuck at bottom after filter shrinks the tree**: When the user scrolled down and then typed a search filter, `visibleFlatNodes` collapsed and `vsTotalHeight` shrank below the previous scroll position. The browser silently clamped the container's actual `scrollTop`, but the controller's `vsScrollTop` state kept its stale large value — so `vsStartIndex` fell out of range, `flatNodesToRender` was empty, and the scrollbar appeared frozen near the bottom with no content rendered. A new `$effect` now reacts to `vsTotalHeight` and clamps both `vsScrollTop` and the container's `scrollTop` to the new max whenever content shrinks below the current scroll position.
31
- - **Double-click expand silently broken in `clickBehavior='select'` mode**: The browser's native `dblclick` event never fired because the first click triggered focus → `_setFocusedNode` bumped `node._rev` → the flat-mode `{#each}` destroyed and recreated the row, so the second click landed on a different DOM element and the browser refused to synthesize a `dblclick`. The controller now detects double-clicks manually (`_lastSelectClickPath` / `_lastSelectClickTime`, 400 ms threshold) so a rapid second click on the same node toggles its expand state regardless of re-render. Node-level `_onNodeDblClicked` removed (it could double-toggle if the browser ever did fire `dblclick`).
32
-
33
- ## [5.0.0-rc09] - 2026-06-07
34
-
35
- ### Added
36
- - **`selectionMode: 'single' | 'multi'` prop** (default `'single'`): Decides how highlight cardinality and modifier keys behave. In `'single'`, plain click sets highlight to one node and Ctrl/Shift+click degrade to plain click; Shift+Arrow and Enter are no-ops. In `'multi'`, Ctrl+click toggles, Shift+click extends a range from the focused node, Shift+Arrow extends one step, and Enter toggles highlight on the focused node. Matches the [selection-highlight-model.md](selection-highlight-model.md) decision record.
37
- - **Implicit highlight selection mirroring when `showCheckboxes` is false**: Every change to `highlightedPaths` is mirrored into `selectedPaths`, and `onSelectionChange` fires alongside `onHighlightChange`. When checkboxes are visible the two sets stay decoupledhighlight is the cursor / range cursor, checkbox state is the form selection. No new toggle prop; the rule follows `showCheckboxes` directly. Programmatic API (`highlightNode`, `highlightNodes`, `clearHighlight`) mirrors too and respects `{ silent: true }`.
38
- - **`clickTogglesCheckbox` prop**: Boolean (default `false`). When `true` AND `showCheckboxes` is on AND the node is selectable, a plain click on the node label runs the checkbox-toggle path instead of focusing/highlighting `focusedNode` and `highlightedPaths` stay untouched. Expand-on-click still fires when `clickBehavior` is `'expand'` or `'expand-and-focus'`. Modified clicks (Ctrl/Shift) fall through to the normal multi-highlight path so range/toggle selection still works.
39
- - **`getIsExpandedCallback`, `getIsSelectableCallback`, `getIsSelectedCallback` props**: Callback variants for the existing `isExpandedMember` / `isSelectableMember` / `isSelectedMember` data-field props, matching the same pattern as `getIsDraggableCallback` and `getIsCollapsibleCallback`. All three are seed-only invoked once per node during `insertArray` — so subsequent user mutations (checkbox clicks, expand button) aren't overridden by the callback. Precedence: callback > member > default. The post-insert `selectedPaths` seeding walk now also triggers when `getIsSelectedCallback` is set, not just when `isSelectedMember` is set.
40
- - **Lucide SVG toggle icons**: All built-in `ltree-icon-*` classes (default chevron, alt filled-triangle, plus/minus, arrow, leaf) now render Lucide SVGs via `mask-image` + `background-color: currentColor` instead of UTF-8 glyph characters (`▶ + •`). Icons inherit text color automatically, render cleanly at any size, and don't depend on font fallback. Zero added DOM nodes — still a single `::before` pseudo-element per toggle.
41
- - **`--ltree-toggle-icon-size` / `--ltree-toggle-icon-width` / `--ltree-toggle-icon-color` CSS variables**: Runtime overrides for the toggle column's icon size (default `16px`, was `12px`), column width (`20px`), and color (`#6c757d`). The SCSS variables (`$tree-toggle-icon-*`) still drive the build-time defaults; the CSS vars are bridged via `var(--…, #{$…})` so users can scale icons live without recompiling SCSS.
42
- - **Toggle icon live demo on `/examples/theming`**: A new card with radio controls for the four built-in icon sets and the `toggleIconMode` (`rotate` / `swap`) prop, wired to a live `<Tree>`. A code snippet under the controls reflects the current selection so users can copy-paste the props they see.
43
- - **Full CSS-variable theming surface (`--ltree-*` with `--base-*` chain)**: Every styling value is exposed as a CSS custom property at `:root`. The resolution chain is end-user override `--base-*` token (shared with other `@keenmate/*` web components) hardcoded default. Roughly fifty new variables covering typography, node layout, toggle icon, checkbox, selection / highlight / drag states, drop zones (pastel + glow modes), context menu, debug info, scroll highlight, and loading overlay. Pattern matches `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`.
44
-
45
- ### Changed
46
- - **`!isSelectable` now also gates highlight (and therefore the mirrored selection in no-checkbox mode), not just the checkbox render**: Click handlers and Shift+arrow range expansion skip non-selectable nodes when building the highlight set. Focus and arrow navigation are NOT gated the focused row can still land on `!isSelectable` nodes so consumers can show external detail panels (sidebars, breadcrumbs, etc.).
47
- - **Right-click no longer moves focus or highlight**: `_onNodeRightClicked` only opens the context menu at the right-clicked node. Consumers reading the focused/highlighted set inside `getContextMenuItemsCallback` should now use the `node` argument that the callback receives, since right-click no longer rewrites `focusedNode` / `highlightedPaths`. The selected-nodes argument continues to reflect whatever multi-highlight the user had before opening the menu.
48
- - **Drag and drop now moves the whole highlight set when the dragged node is in `highlightedPaths`** (same-tree, `operation: 'move'`, `autoHandleMove: true`): the algorithm picks the **top-level selected** subsethighlighted paths whose nearest highlighted ancestor is NOT in the set and moves each one's full subtree. Selected descendants of a top-level node are absorbed (they ride along inside their ancestor's subtree, not separately extracted). Cross-tree multi-drag still moves the single dragged node (the cross-tree payload only carries one node).
49
- - **Multi-drag now chains subsequent moves 'after' the previously moved subtree** (was: subsequent moves landed as children of `dropNode` regardless of the requested position). Dropping selection {A, B, C} 'after D' now produces `[D, A, B, C]` as siblings (previously: `[D]` at root with A/B/C as children of D). 'before D' produces `[A, B, C, D]`. 'child of D' produces D with children `[A, B, C]`. Source order is preserved within the moved set.
50
- - **Dragging a non-highlighted node now replaces the highlight with that single node before the drag begins**: Matches the Windows Explorer / macOS Finder convention where mousedown on an unselected item selects it. Previously, the prior highlight stayed visible (the `click` event never fired because the user dragged instead of releasing) while the drag silently carried only the single grabbed node making it impossible to tell what would actually move. The replacement runs in `_onNodeDragStart` and fires `onHighlightChange` / mirrors to `selectedPaths` like a plain click. Skipped if the dragged node is already in the highlight set (multi-drag) or is not selectable.
51
- - **Keyboard: Enter toggles focused highlight in multi mode (was: always expand/collapse)**. In single mode Enter is now a no-op (falls through to the host). Space falls back to expand/collapse when no checkboxes are shown, but toggles the focused node's checkbox when `showCheckboxes` is on.
52
- - **Ctrl/Shift+click on a node no longer toggles expand/collapse**: The modifier-click gesture is reserved for highlight management (toggle / range). Previously, in the default `clickBehavior='expand-and-focus'` mode, modified clicks both updated the highlight set AND expanded/collapsed the row, which made it impossible to multi-select folders without their contents flickering open. Plain click still expands as before. Matches OS file explorer conventions.
53
- - **Stylesheet ported from SCSS to pure CSS**: `src/lib/styles/main.scss` (1095 lines, one file) split into eleven partials under `src/lib/styles/` (`_variables.css`, `_base.css`, `_node.css`, `_toggle-icons.css`, `_checkbox.css`, `_states.css`, `_drag-drop.css`, `_drop-zones.css`, `_context-menu.css`, `_debug.css`, `_loading.css`) plus a `main.css` entry point that chains them via CSS `@import`. SCSS `@mixin` constructs replaced with shared base-class boilerplate. Build now uses `lightningcss-cli` (one devDep) to bundle the imports into `dist/styles.css`; `sass` removed from devDependencies. Pattern matches the file layout in `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`.
54
- - **`--ltree-rem` base sizing unit**: New CSS variable (default `10px`) drives every dimension via `calc(N * var(--ltree-rem))` font sizes, paddings, margins, widths, radii, spinner size, etc. Set `--ltree-rem` once to scale the whole component proportionally (e.g., `--ltree-rem: 12px` for 20% larger). Set it to `1rem` to scale with document font-size (Pure Admin pattern). Default rendering is visually identical to the previous absolute-px output. Hairline borders (`1.5px` checkbox, `3px` glow) and animation timings stay absolute. Matches the `--ms-rem` / `--drp-rem` pattern in the sibling packages.
55
- - **`component-variables.manifest.json` published at the package root**: Mirrors the schema used by `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`. Lists every `--base-*` token the component reads (with `required: true/false` flags) and every `--ltree-*` variable the component publishes (categorized: `color`, `sizing`, `spacing`, `border`, `surface`, `typography`, `state`, `drop-zone`, `context-menu`, `icon`, `animation`). Intended for IDE auto-completion, Storybook integration, and theme-designer tooling. Exported via the `./component-variables.manifest.json` package entry.
56
- - **Hover background follows `--ltree-primary` by default**: `--ltree-node-hover-bg` default changed from solid `#f8f9fa` to `color-mix(in srgb, var(--ltree-primary) 8%, transparent)`. Themes that set only `--ltree-primary` now get a matching hover tint automatically; the explicit `--base-hover-bg` token override still wins over the derived default. Visually similar to the previous gray at default primary `#0d6efd`.
57
- - **`--ltree-checkbox-focus-ring` split into tunable parts**: `--ltree-checkbox-focus-ring-width` (default `2px`) and `--ltree-checkbox-focus-ring-color` (default `color-mix(--ltree-primary 25%)`) — the composed `--ltree-checkbox-focus-ring` shorthand still works and now references both. Set just one part to widen the ring or recolor it without redeclaring the full box-shadow.
58
-
59
- ### Breaking
60
- - **Default `selectionMode = 'single'` changes click semantics for existing multi-select users**: Previously Ctrl/Shift+click always built a multi-highlight regardless of any prop. Now the default is single-select to keep the old behaviour, opt in with `selectionMode='multi'`. Plain click + Arrow nav are unchanged.
61
- - **`selectedPaths` becomes populated in no-checkbox trees** because highlight is mirrored into selection. Consumers reading `selectedPaths` from no-checkbox `<Tree>` instances will now see values that previously stayed empty. To keep the old empty-`selectedPaths` behaviour, render checkboxes (and ignore them with CSS) or keep your own derived "real selection" set.
62
- - **`lastHighlightedPath` (private), `isHighlightAnchor` (public on `LTreeNode`), `.ltree-highlight-anchor` (CSS class), `--ltree-highlight-anchor-width` / `--ltree-highlight-anchor-color` (CSS vars), and the matching `component-variables.manifest.json` entries removed**: The focused node now serves as the anchor for Shift+range operations. Internal Shift+Arrow uses a hidden `_shiftCursor` fieldconsumers never see it.
63
- - **`./styles.scss` package export removed.** Consumers using `import '@keenmate/svelte-treeview/styles.scss'` must switch to `import '@keenmate/svelte-treeview/styles.css'` (the canonical export, unchanged). For per-section theming, individual partials are now available under `./styles/_<section>.css`.
64
- - **`--ltree-primary-rgb` / `--ltree-success-rgb` / `--ltree-danger-rgb` removed** (along with `--base-accent-color-rgb` / `--base-success-color-rgb` / `--base-danger-color-rgb` from the `--base-*` chain). Tinted variants are now derived from the parent color via `color-mix(in srgb, var(--ltree-x) N%, transparent)` so setting `--ltree-primary: red` automatically tints the multi-select background, dragover background, drop placeholder, focus ring, scroll highlight, debug panel, and danger context-menu hover without needing a companion `-rgb` variable. Matches the `@keenmate/web-multiselect` pattern. Minimum browser baseline: Chrome 111 / Safari 16.4 / Firefox 113 (all from March-May 2023).
65
- - **Indent scaling semantics**: `--ltree-node-indent-per-level` was previously `0.5rem` (document-relative followed `html { font-size }`). It is now `calc(0.8 * var(--ltree-rem))` — same visual default (8 px), but scales with `--ltree-rem` instead of document `rem`. To restore the old document-relative behavior, set `--ltree-rem: 1rem`.
66
- - **SCSS variable overrides (`$tree-*`, `$drop-*`, `$primary-*`) no longer supported.** Migrate to CSS custom properties at runtime: `--ltree-node-font-size: 16px` instead of `@use '@keenmate/svelte-treeview/styles.scss' with ($tree-node-font-size: 16px)`. The full variable surface is documented at `/examples/theming`.
67
-
68
- ### Fixed
69
- - **Stale `dragOverNodeClass` highlights piling up across rows during drag**: Each `Node` tracked its own local `isDraggedOver` flag, toggled by the row's own `ondragover` / `ondragleave`. HTML5 `dragleave` is unreliable when crossing between sibling rows fast (it can fire with cursor coordinates still inside the leaving row's rect, or skip altogether when `dragenter` on the next row beats it) so the dashed-green dragover highlight got stuck on rows the cursor had already left, leaking one stale highlight per missed leave. The class is now applied via direct `classList.add/remove` on the previous-and-new DOM nodes from a controller `$effect` that watches the centralized `hoveredNodeForDrop`, matching the existing touch-drag pattern. This guarantees exactly one highlighted row at a time and the cost stays O(1) per node-crossing regardless of tree size no per-Node prop propagation. The dead per-node `isDraggedOver` state was removed.
70
- - **`ltree-selected-brackets` invisible when checkboxes are enabled**: The adjacent-sibling rule that hides the `❯ ❮` pseudo-elements (because the checkbox already signals selection) left the class with zero visual effect — highlighted nodes looked identical to unhighlighted ones. The suppressed selector now also applies `font-weight: bold` and `color: var(--ltree-primary)` as a fallback so the highlight is always visible.
71
- - **Independent checkbox mode auto-checking parents**: In `checkboxMode="independent"`, the post-toggle ancestor walk still ran and propagated `isSelected = true` up to parents when all descendants happened to be selectedmaking parent checkboxes auto-check themselves despite the "independent" promise. The walk is now gated to cascade mode only; independent mode leaves parents fully alone.
72
- - **`Node.svelte` stale icon/highlight classes after `tree.update()`**: `expandIconClass`, `collapseIconClass`, `leafIconClass`, `highlightedNodeClass`, `focusedNodeClass`, `dragOverNodeClass`, and `allowCopy` were destructured from the shared `NodeConfig` `$state` proxy as primitive snapshots, so once Node mounted they never updated. Replaced with `$derived(config.x)` so runtime updates (including the new icon-set radios in the theming demo) actually propagate.
73
-
74
- ## [5.0.0-rc08] - 2026-05-27
75
-
76
- ### Added
77
- - **`{ silent: true }` option on highlight/selection methods**: `highlightNode`, `highlightNodes`, `clearHighlight`, `deselectAll` (and deprecated `selectNode`/`selectNodes`) now accept `{ silent: true }` to update state without firing `onNodeClick` / `onHighlightChange` / `onSelectionChange`. Intended for URL-restore flows (deep links loading form data from query params) where firing the change callback would re-trigger form loaders and clobber the data the URL just supplied. Silent mode also skips focusing the tree container so it doesn't steal focus from whatever the user is interacting with.
78
- - **Array variants on expand/collapse methods**: `expandNodes`, `collapseNodes`, `expandAll`, and `collapseAll` now accept `string | string[]`. Single emit per call regardless of array length.
79
- - **`{ exclusive: true }` option on `expandNodes` and `expandAll`**: Opens the target path(s) and collapses anything currently expanded that isn't on the union-of-spines (and, for `expandAll`, not under a target subtree). Equivalent to `collapseAll() + expandNodes(path)` but in a single pass with one emit downstream listeners (transition animations, URL sync, virtualized renderers) don't see the intermediate fully-collapsed state. Non-collapsible nodes (`isCollapsible === false`) are never touched.
80
- - **`{ noEmit: true }` option on all four expand/collapse methods**: Skips the change emit, enabling batching multiple operations and emitting once at the end via `tree.refresh()`.
81
- - **`/examples/silent-highlight` demo page**: URL-restore scenario with loud vs. silent toggle showing how form data is preserved in silent mode.
82
- - **`/examples/expand-collapse` demo page**: Demonstrates array variants and exclusive focus mode.
83
-
84
- ## [5.0.0-rc07] - 2026-05-23
85
-
86
- ### Added
87
- - **`isSelectedMember` prop**: Data field name that seeds `node.isSelected` at `insertArray` time. The controller walks the tree after insert and pre-populates the bindable `selectedPaths` Set with every path where the field is truthy — independent of `isSelectableMember`, so non-selectable nodes can still ship checked.
88
- - **`isSelectableMember` prop now publicly wired**: Previously declared on the core types but not exposed through `Tree.svelte`'s prop/update interface. Now end-to-end usable. Controls whether a node renders a checkbox and carries the `ltree-clickable` class.
89
-
90
- ### Fixed
91
- - **Filter race with async indexing**: A `bind:searchText` change that landed while the FlexSearch index was still being built (via `requestIdleCallback` batches) saw an empty index, hid the entire tree, and never recovered — no mechanism re-applied the filter once indexing completed. `filterNodes` now remembers the active query and the indexer's `onComplete` callback re-runs it, so the visible filter catches up automatically regardless of `indexerBatchSize`, dataset size, or how fast the user types.
92
-
93
- ## [5.0.0-rc06] - 2026-03-31
94
-
95
- ### Added
96
- - **`showCheckboxes` prop**: Renders a checkbox before each selectable node with custom styling and indeterminate support.
97
- - **`checkboxMode` prop** (`'independent'` | `'cascade'`): Controls whether checking a parent cascades to all descendants. Indeterminate state shown when partial.
98
- - **`beforeCheckboxToggleCallback` interceptor**: Cancel or override checkbox toggles.
99
- - **Three-level selection model**: Separated into `focusedNode` (single node, click/arrows), `highlightedPaths` (multi-select, Ctrl/Shift+click), and `selectedPaths` (checkbox data state). Highlight and checkbox state are independent — build a highlight selection, then check/uncheck all highlighted nodes with one checkbox click.
100
- - **`onHighlightChange` event**: Fires when highlighted paths change (Ctrl+click, Shift+click, etc.).
101
- - **Bulk checkbox via highlight**: When multiple nodes are highlighted and a checkbox in the highlight is clicked, all highlighted nodes toggle together.
102
- - **Shift+Arrow/Home/End keyboard highlight**: Shift+ArrowDown/Up extends highlight range by one sibling, Shift+Home/End extends to first/last visible node.
103
- - **PageUp/PageDown navigation**: Jumps 10 visible nodes forward/back. Shift+PageUp/PageDown extends highlight by 10 nodes.
104
- - **`ltree-selected-highlight` CSS class**: Explorer-style blue background highlight. Customizable via `--ltree-highlight-bg` and `--ltree-highlight-color`.
105
- - **Interaction example page** (`/examples/interaction`): Interactive demos for click behavior, checkboxes, multi-select, and keyboard navigation. All settings persisted to localStorage.
106
-
107
- ### Fixed
108
- - **Keyboard navigation not working after node click**: Clicking a node now auto-focuses the tree container, so arrow keys work immediately without having to click the container separately.
109
- - **Svelte proxy equality warning on checkbox toggle**: `_setFocusedNode` now compares by path instead of object identity to avoid `state_proxy_equality_mismatch`.
110
-
111
- ### Breaking
112
- - **`selectedNode` `focusedNode`**: Renamed prop and bindable. The single focused node (last clicked / arrow-keyed to).
113
- - **`selectedPaths` repurposed**: Now represents checkbox-only data state. For click/highlight multi-select, use `highlightedPaths`.
114
- - **`highlightedPaths` (new)**: Replaces old `selectedPaths` for Ctrl+click / Shift+click UI highlight.
115
- - **`selectedNodeClass` `highlightedNodeClass`**: CSS class applied to highlighted nodes.
116
- - **`focusedNodeClass` (new)**: CSS class applied to the single focused node.
117
- - **`onSelectionChange` repurposed**: Now fires on checkbox selection changes only. Use `onHighlightChange` for highlight changes.
118
- - **`selectNode()` / `selectNodes()` deprecated**: Use `highlightNode()` / `highlightNodes()` instead.
119
-
120
- ## [5.0.0-rc05] - 2026-03-26
121
-
122
- ### Added
123
- - **`clickBehavior` prop** (`'select'` | `'expand'` | `'expand-and-focus'`, default `'expand-and-focus'`): Controls what happens on node click. Replaces the boolean `shouldToggleOnNodeClick` prop. Matches canvas package's `ClickBehavior` type.
124
-
125
- ### Breaking
126
- - **`shouldToggleOnNodeClick` removed**: Replace `shouldToggleOnNodeClick={true}` with `clickBehavior="expand-and-focus"` (default) and `shouldToggleOnNodeClick={false}` with `clickBehavior="select"`.
127
-
128
- ## [5.0.0-rc04] - 2026-03-12
129
-
130
- ### Added
131
- - **`TreeNavigation<T>` interface**: Pluggable keyboard navigation per renderer. Each renderer (HTML, Canvas) provides its own spatial implementation. Users can override individual methods via `TreeNavigationOverrides<T>`.
132
- - **Bulk subtree operations**: `insertBranch(parentPath, nodes)`, `replaceBranch(path, nodes)`, `deleteBranch(path)` on TreeController — perform subtree-level add/replace/remove with a single tree emission.
133
- - **Clipboard API on TreeController**: `copyNodes(paths?)`, `cutNodes(paths?)`, `pasteNodes(targetPath, position, options?)`, `cancelCut()`, `hasClipboard()`, `getClipboardOperation()`. Cut nodes are dimmed via `cutPaths` set. Supports `autoHandlePaste` (default) and manual mode for server-driven workflows.
134
- - **Branch-operations example** (`/examples/branch-operations`): Server-simulated cut/paste workflow demonstrating `deleteBranch` + `insertBranch` with clipboard integration.
135
-
136
- ### Fixed
137
- - **`.ltree-container` missing `outline: none`**: Container div now suppresses browser focus outline.
138
-
139
- ## [5.0.0-rc03] - 2026-03-08
140
-
141
- ### Added
142
- - **Multi-select**: Ctrl+click (Cmd on Mac) toggles individual nodes in/out of selection. Shift+click selects a range from the last-clicked anchor to the current node. Plain click clears selection and selects one node.
143
- - **`selectedPaths` bindable prop**: `Set<string>` of all selected node paths. Two-way binding for external control.
144
- - **`rangeSelectionMode` prop**: `'visual'` (default) selects only visible/expanded nodes between anchor and target; `'logical'` selects all nodes in depth-first tree order including collapsed children.
145
- - **`onSelectionChanged` event**: `(paths: Set<string>, nodes: LTreeNode<T>[]) => void` fires when selection changes.
146
- - **`contextMenuCallback` 3rd parameter**: Now receives `selectedNodes?: LTreeNode<T>[]` enables selection-aware context menus.
147
- - **Public multi-select API on TreeController**: `selectNode(path, mode)`, `selectNodes(paths)`, `deselectAll()`, `getSelectedNodes()`, `isNodeSelected(path)`.
148
- - **`SelectionModifiers` type export**: `{ ctrl: boolean; shift: boolean }` for modifier-aware click handling.
149
- - **`.ltree-multi-selected` CSS class**: Styling for nodes in a multi-selection.
150
- - **Unified Context Menu types** (`ContextMenuItem`, `ContextMenuDivider`, `ContextMenuEntry`): Shared type system across svelte-treeview and canvas-tree. Breaking change from old API: `title` → `label`, `callback` → `onclick`, `isDivider` flag replaced by separate `ContextMenuDivider` type with `divider: true` discriminator.
151
- - **Named dividers**: `{ divider: true, label: 'Section' }` renders as `──── Section ────`
152
- - **Keyboard shortcuts**: `shortcut` field renders right-aligned hint and activates on keypress when menu is open
153
- - **Submenus**: `children: ContextMenuEntry[]` opens nested menu on hover
154
- - **Visibility control**: `isVisible: false` hides items in callback approach (snippet approach uses `{#if}`)
155
- - **Flexible styling**: `className` replaces dedicated `danger` booleanuse `className="danger"` or any custom class
156
- - **Async onclick**: `onclick` supports `Promise<void>` return with try/catch error handling
157
- - **`ContextMenuItemC` / `ContextMenuDividerC` Svelte components**: Declarative context menu building inside the `contextMenu` snippet. Supports nested children slot for submenus.
158
- - **Context menu keyboard shortcuts**: When context menu is open, pressing a shortcut key (e.g. `C`, `Shift+N`, `F2`) triggers the matching item's `onclick`. Supports modifier keys (Ctrl, Shift, Alt). Escape closes the menu.
159
- - **`accordionExpand` prop**: Per-parent accordion behavior — expanding a node automatically collapses its siblings (children of the same parent). Opt-in via `accordionExpand={true}`. Respects `isCollapsibleMember`/`getIsCollapsibleCallback` — non-collapsible siblings are not force-collapsed. Programmatic methods (`expandAll`, `expandNodes`) remain unconstrained.
160
- - **`toggleIconMode` prop** (`'rotate'` | `'swap'`, default `'rotate'`): Controls how expand/collapse icons behave. `'rotate'` mode (new default) always uses `expandIconClass` (▶) and rotates it 90° via CSS when expanded — smooth animated transition. `'swap'` mode switches between `expandIconClass` (▶) and `collapseIconClass` (▼) classes (previous behavior). Ported from `@keenmate/web-treeview`.
161
-
162
- ### Fixed
163
- - **Expand/collapse icon not updating in flat rendering mode**: `toggleExpanded()` now bumps `node._rev` so the flat-mode `{#each}` key changes and Svelte re-renders the node with the correct icon state.
164
- - **`.expanded` CSS transform was a no-op**: `.ltree-toggle-icon.expanded` had `rotate(0deg)` (no effect). Fixed to `rotate(90deg)` for the new `rotate` toggle icon mode.
165
-
166
- ## [5.0.0-rc02] - 2026-03-05
167
-
168
- ### Architecture (Breaking)
169
- - **Core/Renderer split**: Tree logic (`TreeController`) fully separated from rendering. `Tree.svelte` is now a thin wrapper delegating to `TreeController`. New `TreeProvider` component enables custom renderers (Canvas, WebGL, SVG) on the same core.
170
- - **Drop position naming**: `'above'`/`'below'` renamed to `'before'`/`'after'` throughout (`DropPosition` type, CSS classes, events). `'child'` unchanged.
171
- - **Canvas rendering extracted**: Canvas-based rendering (`CanvasTree`, layouts, themes) moved to separate package `@keenmate/svelte-treeview-canvas`.
172
-
173
- ### Added
174
- - **Render mode switch for examples**: Shared `RenderModeSwitch` component across all example pages. Three-button segmented control (Recursive / Progressive / Virtual) with localStorage persistence. All example `<Tree>` instances receive mode props via `{...getTreeProps()}`.
175
- - **Virtual scroll mode** (`virtualScroll`, `virtualRowHeight`, `virtualOverscan`, `virtualContainerHeight`): Only renders visible nodes + overscan rows in a fixed-height scrollable container. Enables smooth scrolling through trees with 50,000+ nodes while maintaining ~50 DOM nodes. rAF-throttled scroll handler, auto-measures row height if not explicitly set.
176
- - **Virtual scroll `scrollToPath`**: Index-based scrolling that centers the target node in the viewport, waits for rAF-throttled scroll + re-render, applies highlight with retry logic.
177
- - **Search navigation UX**: Filter/search mode toggle on the search example page. Filter mode hides non-matching nodes, search mode keeps tree visible and navigates to highlighted results. Includes result counter, prev/next chevron buttons, Enter/Shift+Enter keyboard navigation (round-robin), Escape to clear.
178
- - **CSS zone auto-expand**: Floating drop zones auto-expand when positions are hidden (via `allowedDropPositions`). Uses `:not(:has())` CSS rules for "around", "above", "below" layouts.
179
- - **Unified flat rendering indentation**: Both flat and recursive modes use `--tree-node-indent-per-level` CSS variable. Added `flatGap` logic for parent-to-first-child gap matching.
180
- - **`overscroll-behavior: contain`** on `.ltree-virtual-scroll` container.
181
- - **`isCollapsibleMember` / `getIsCollapsibleCallback`**: Per-node collapsibility control.
182
- - **`getIsDraggableCallback`**: Dynamic per-node draggability.
183
- - **`applyChanges()` batch method**: Apply multiple tree edits in a single operation.
184
- - **`_rev` change tracking** on nodes for efficient keyed rendering.
185
-
186
- ### Fixed
187
- - **Floating drop zones clipped by scrollable containers**: Moved floating drop zone rendering from `Node.svelte` (absolute positioning inside each node) to `Tree.svelte` (fixed positioning at tree level with `z-index: 10000`). Zones now escape `overflow: hidden` containers. Handler methods (`isFloatingPositionAllowed`, `handleFloatingZoneDragOver`, `handleFloatingZoneDragLeave`, `handleFloatingZoneDrop`) added to `TreeController`.
188
- - **Glow and floating drop zones showing simultaneously**: In flat rendering mode, `dropZoneMode` destructured from `NodeConfig` context was a stale snapshot (nodes aren't recreated). Changed to `$derived(config.dropZoneMode)` so it reads through the reactive proxy on each evaluation.
189
- - **"After" drop position placing node at end instead of between siblings**: `moveNode` and `addNode` converted root nodes' `parentPath` from `''` to `null` via `|| null`, while `insertArray` used `getParentPath()` which returns `''`. This mismatch caused `sortCallback` to skip the `sortOrder` comparison (`'' !== null` → entered parentPath branch → returned 0), preserving insertion order instead of respecting sort order.
190
- - **Empty tree drop placeholder ignoring `dragDropMode`**: `handleEmptyTreeDragOver/Drop/TouchEnd` now check `dragDropMode !== 'none'` before activating the drop placeholder.
191
- - **`treeId` not synced on prop changes**: Added `$effect` in `Tree.svelte` to sync `treeId` prop to controller after mount.
192
- - **`dropZoneStart` not used in glow mode**: `calculateGlowPosition` now uses `dropZoneStart` to compute the child zone threshold instead of hardcoded `width/2`.
193
- - **Sort order mismatch between recursive and flat modes**: `visibleFlatNodes` applied `sortCallback` during traversal while recursive mode relied on insertion-order `Object.values()`. Removed runtime sort from flat traversal to match recursive mode — both now use insertion-time sorting from `insertArray()`.
194
- - **Critical `$state()` vs `$state.raw()` performance regression**: TreeController's `data` property deep-proxied user data, causing **5,500x slowdown** with 8,000+ nodes. Changed `data`, `selectedNode`, `insertResult`, `contextMenuNode`, `hoveredNodeForDrop`, `touchDragState`, `flatRenderedIds`, `flatRenderQueue` to `$state.raw()`.
195
-
196
- ### Changed
197
- - **Custom Layout Example Explicit Drop Zones**: Replaced invisible spatial detection with visible drop zone pills (Before / After / Child) on dendrograms.
198
-
199
- ## [4.7.2] - 2026-02-17
200
-
201
- ### Fixed
202
- - **`bodyClass` prop not working** ([#24](https://github.com/keenmate/svelte-treeview/issues/24)): `class:bodyClass` was toggling a literal CSS class named `"bodyClass"` instead of applying the user's custom class value. Changed to `class={bodyClass}`.
203
-
204
- ## [4.7.1] - 2026-02-17
205
-
206
- ### Changed
207
- - **Drag and Drop Disabled by Default**: `dragDropMode` now defaults to `'none'` instead of `'both'`
208
- - Most trees are read-only, so this is a safer default
209
- - To enable drag and drop, explicitly set `dragDropMode="both"` (or `"self"` / `"cross"`)
210
-
211
- ### Fixed
212
- - Removed development `console.log` statements from `Tree.svelte` and `ltree.svelte.ts`
213
-
214
- ## [4.7.0] - 2026-02-11
215
-
216
- ### Added
217
- - **Per-Node Drop Position Restrictions**: New feature to control which drop positions are allowed per node
218
- - `allowedDropPositionsMember` - Property name mapping for static data (e.g., from server)
219
- - `getAllowedDropPositionsCallback` - Callback for dynamic position logic based on node type
220
- - `DropPosition` type exported: `'above' | 'below' | 'child'`
221
- - Example use cases:
222
- - Trash folder: `['child']` only (can drop INTO, not above/below)
223
- - Files: `['above', 'below']` only (can't drop INTO a file)
224
- - Folders: `undefined` or `[]` (all positions allowed - default)
225
- - Works with both glow mode (snaps to nearest allowed position) and floating mode (only shows allowed zones)
226
- - Backwards compatible: `undefined`/empty array = all positions allowed
227
-
228
- ### Example Usage
229
- ```typescript
230
- // Callback approach (dynamic logic)
231
- function getAllowedDropPositionsCallback(node: LTreeNode<T>): DropPosition[] | null {
232
- if (node.data?.type === 'file') return ['above', 'below'];
233
- if (node.data?.type === 'trash') return ['child'];
234
- return undefined; // all positions allowed
235
- }
236
-
237
- // Member approach (server data)
238
- const data = [
239
- { path: '1', name: 'Trash', allowedDropPositions: ['child'] },
240
- { path: '2', name: 'Document.pdf', allowedDropPositions: ['above', 'below'] },
241
- { path: '3', name: 'Projects' }, // all positions (default)
242
- ];
243
- ```
244
-
245
- ## [4.6.0] - 2026-02-11
246
-
247
- ### Added
248
- - **Flat Rendering Mode**: New `useFlatRendering` prop (default: `true`) for significantly faster rendering
249
- - Renders all visible nodes in a single `{#each}` loop instead of recursive components
250
- - Initial render ~12x faster (300ms → 25ms for 5500 nodes)
251
- - Progressive rendering batches initial load to prevent UI freeze
252
- - Documentation: `docs/FLAT_MODE_PERFORMANCE.md`
253
- - **Context-Based Node Configuration**: Moved stable callbacks and config to Svelte context
254
- - `NodeCallbacks<T>` interface for all event handlers (click, drag, drop, touch)
255
- - `NodeConfig` interface for stable configuration (icons, classes, drop zone settings)
256
- - Eliminates inline arrow function re-renders (5500 nodes no longer re-evaluate on array changes)
257
- - Exported types: `NodeCallbacks`, `NodeConfig` from package index
258
- - **Async beforeDropCallback**: `beforeDropCallback` now supports async/Promise return values
259
- - Enables showing confirmation dialogs before completing a drop
260
- - Can await user input to decide whether to cancel, proceed, or modify the drop
261
- - Example: `async (dropNode, draggedNode, position) => { return await showDialog(); }`
262
-
263
- ### Enhanced
264
- - **Exponential Batch Sizing**: Progressive rendering now uses exponential batching (20 → 40 → 80 → 160...)
265
- - First batch (20 nodes) renders instantly for immediate visual feedback
266
- - Batch size doubles each frame up to `maxBatchSize` (default 500)
267
- - New props: `initialBatchSize` (default 20), `maxBatchSize` (default 500)
268
- - Replaces fixed `renderBatchSize` prop
269
- - Expand/collapse on large trees (>1000 nodes): Immediate add to minimize diffs
270
- - **Tree Editor Example**: Enhanced with async drop validation dialog
271
- - Demonstrates async `beforeDropCallback` with native confirm dialog
272
- - Shows warning when drop position is modified
273
-
274
- ### Fixed
275
- - **Scroll Highlight Stacking**: Fixed multiple nodes staying highlighted when rapidly clicking prev/next
276
- - Previous highlight now immediately cleared when navigating to new node
277
- - Timeout properly cancelled to prevent stale highlight removal
278
- - **Flat Mode Node Updates After Move**: Fixed nodes not re-rendering after `moveNode()` in flat mode
279
- - Keyed each now uses `node.id + path + hasChildren` to detect moved nodes
280
- - Moved nodes now correctly update their indentation level
281
- - Parent nodes correctly update toggle icon when children are moved away
282
-
283
- ### Changed
284
- - **Default Rendering Mode**: `useFlatRendering` now defaults to `true` (was `false`)
285
- - **Default Progressive Render**: `progressiveRender` now defaults to `true` (was `false`)
286
-
287
- ## [4.5.0] - 2026-02-09
288
-
289
- ### Added
290
- - **Drop Zone Layout Configuration**: New props to customize drop zone appearance and positioning
291
- - `dropZoneLayout` - Controls zone arrangement with 5 layout options:
292
- - `'around'` (default) - Above zone on top, Below/Child zones on bottom
293
- - `'above'` - All 3 zones in a horizontal row above the node
294
- - `'below'` - All 3 zones in a horizontal row below the node
295
- - `'wave'` - Zones stacked vertically (above/child/below) with fixed width
296
- - `'wave2'` - Diagonal wave pattern with Above/Below offset 7% to the left
297
- - `dropZoneStart` - Number (0-100) controlling where zones start horizontally (default: 33%)
298
- - `dropZoneMaxWidth` - Max width in pixels for wave layouts (default: 120px)
299
- - **New TypeScript Type**: `DropZoneLayout` type exported from `types.ts`
300
- - **Mobile Touch Drag and Drop**: Full touch support for drag and drop on mobile devices
301
- - Long-press (300ms) to initiate drag - distinguishes from tap and scroll
302
- - Visual ghost element follows finger during drag showing the dragged node
303
- - Drop target highlighting using existing `dragOverNodeClass` prop
304
- - Haptic feedback via `navigator.vibrate()` when drag starts (on supported devices)
305
- - Automatic cancellation if finger moves >10px before long-press completes (allows normal scrolling)
306
- - Works alongside existing desktop HTML5 drag and drop - same `onNodeDrop` callback for both
307
- - **Drop Placeholder for Empty Trees**: When dragging nodes to an empty tree, a drop zone placeholder appears
308
- - Shows visual drop target in empty trees during drag operations
309
- - Works with both desktop (HTML5 DnD) and touch drag
310
- - Customizable via `dropPlaceholder` snippet prop for custom content
311
- - `onNodeDrop` callback receives `null` as `dropNode` for root-level drops into empty trees
312
- - **Drop Position Indicators**: Visual indicators showing exactly where dropped items will be placed
313
- - Three drop positions per node: `'above'` (sibling before), `'child'` (as child), `'below'` (sibling after)
314
- - Absolutely positioned indicators on right half of node to prevent layout shifts
315
- - Position calculated from mouse Y: top 25% = above, middle 50% = child, bottom 25% = below
316
- - New CSS classes: `.ltree-drop-indicators`, `.ltree-drop-above`, `.ltree-drop-child`, `.ltree-drop-below`
317
- - **Root Drop Zone**: Drop zone that appears at bottom of non-empty trees during drag
318
- - Allows dropping items as root-level nodes in trees that already have content
319
- - New CSS class: `.ltree-root-drop-zone`
320
- - **Drag Drop Mode Control**: New `dragDropMode` prop to control allowed drag operations
321
- - `'none'` - Drag and drop disabled
322
- - `'self'` - Only within same tree
323
- - `'cross'` - Only between different trees
324
- - `'both'` - Both self and cross-tree (default)
325
- - **Sibling Order Support**: New `orderMember` prop for explicit sibling ordering
326
- - Specifies which field in user data contains the sort order value
327
- - Used by default sort to order siblings within the same parent
328
- - Required for proper above/below positioning in drag-drop tree editors
329
- - Example: `orderMember="sortOrder"` with data like `{ path: '1.1', name: 'A', sortOrder: 10 }`
330
- - **Tree Editor Helper Methods**: New methods for building tree editors
331
- - `getChildren(parentPath)` - Get direct children of a node
332
- - `getSiblings(path)` - Get all siblings of a node (including itself)
333
- - `getNodeByPath(path)` - Get a node by its path
334
- - `refreshSiblings(parentPath)` - Re-sort children of a parent using orderMember
335
- - `refreshNode(path)` - Trigger re-render for a specific node
336
- - **Tree Editor Mutation Methods**: New methods for modifying tree structure
337
- - `addNode(parentPath, data, pathSegment?)` - Add a new node to the tree
338
- - `moveNode(sourcePath, targetPath, position)` - Move a node with full subtree to a new location
339
- - Supports 'above', 'below', and 'child' positions
340
- - Automatically updates paths of all descendants
341
- - Calculates order values when orderMember is set
342
- - `removeNode(path, includeDescendants?)` - Remove a node from the tree
343
- - **Example Pages**: New `/examples` route with interactive demos (same look-and-feel as web-multiselect)
344
- - Landing page with feature cards linking to 7 example sections
345
- - Basic Examples: tree rendering, expand level control, scroll to path, programmatic expand/collapse
346
- - Drag & Drop: two-tree drag demo, touch drag instructions, drop placeholder customization
347
- - Context Menu: callback-based menus, dynamic items, icons, disabled states, dividers
348
- - Search & Filter: live filtering with `searchText`, `searchNodes()` query method
349
- - Theming: CSS variable reference, theme examples (default, purple, dark, green)
350
- - Data Structures: path-based hierarchy, custom separators, insert result validation
351
- - Tree Editor: add/move/remove nodes with drag-drop and orderMember support
352
- - **Drop Indicator Arrows**: Visual arrow indicators for glow-mode drop zones
353
- - Arrows positioned at 66% of row width, centered vertically
354
- - Uses Lucide SVG icons as data URIs: `arrow-big-up`, `arrow-big-down`, `arrow-big-right-dash`
355
- - Child arrow rotated 45° for diagonal pointing effect
356
- - Fully customizable via SCSS variables
357
- - **Ctrl+Drag Copy Operation**: Full support for copying nodes via Ctrl+drag
358
- - `allowCopy` prop enables Ctrl+drag to copy instead of move
359
- - `autoHandleCopy` prop (default: true) controls whether Tree auto-handles same-tree copies
360
- - `true`: Tree creates copy with generated ID (`{id}_copy_{timestamp}`) - good for batch/offline mode
361
- - `false`: User callback handles copy (for DB/API integration) - good for online/live mode
362
- - Copy operations respect drop position (above/below/child) just like moves
363
- - Visual feedback: `.ltree-drop-copy` class applied during copy operations
364
- - **Position Support for copyNodeWithDescendants**: Enhanced copy method now supports sibling positioning
365
- - New optional parameters: `siblingPath` and `position` ('above' | 'below')
366
- - Copies can be placed at specific positions relative to siblings
367
- - Uses same `orderMember` logic as `moveNode` for consistent ordering
368
- - **Logging Infrastructure**: Categorized logging using vendored loglevel library
369
- - Six log categories: `LTREE:INIT`, `LTREE:DATA`, `LTREE:RENDER`, `LTREE:INDEX`, `LTREE:DRAG`, `LTREE:UI`
370
- - Color-coded console output with timestamps for easy debugging
371
- - Disabled by default (silent mode) for production
372
- - Exported utilities: `enableLogging()`, `disableLogging()`, `setLogLevel()`, `setCategoryLevel()`
373
- - UI logging: node clicks, expand/collapse, selection changes, context menu
374
- - Drag logging: drag start/end, drop operations, touch drag events
375
- - Render logging: progressive rendering frame stats
376
- - Index logging: async search indexing progress
377
- - New `/dev/logging` demo page for testing log levels and categories
378
- - **Performance Logging**: Dedicated performance measurement utilities
379
- - Measures key operations: `insertArray` (conversion/sort/insert phases), `filterNodes`, `expandAll`, `collapseAll`
380
- - Output includes duration, item count, per-item time, and items/sec throughput
381
- - Summary view showing breakdown by phase with percentages
382
- - Configurable threshold to only log operations slower than X ms
383
- - Exported utilities: `enablePerfLogging()`, `disablePerfLogging()`, `setPerfThreshold(ms)`
384
- - Browser console access: `window.components['svelte-treeview'].perf.enable()`
385
- - Purple color-coded output for easy identification
386
- - **Global Runtime API**: `window.components['svelte-treeview']` for browser console access
387
- - `config` - Package info (name, version, author, license, repository, homepage) read from package.json at build time
388
- - `version()` - Returns current version string
389
- - `logging.enableLogging()` / `logging.disableLogging()` - Toggle all logging
390
- - `logging.setLogLevel(level)` - Set level for all categories
391
- - `logging.setCategoryLevel(category, level)` - Set level for specific category
392
- - `logging.getCategories()` - List available log categories
393
- - **Container-Scoped Scrolling**: New `containerScroll` option for `scrollToPath()`
394
- - `scrollToPath(path, { containerScroll: true })` scrolls only within the nearest scrollable ancestor
395
- - Prevents page-level scrolling when tree is inside a scrollable container
396
- - Automatically finds the scrollable parent element (overflow: auto/scroll)
397
- - Useful for search result navigation without disrupting page position
398
-
399
- ### Enhanced
400
- - **Drop Zone Styling**: Improved visual feedback during drag operations
401
- - Semi-transparent zones (0.25 opacity) that become solid (0.85) when hovered
402
- - Modern pastel color palette: sage green for Above, peach/coral for Below, lavender for Child
403
- - Interactive controls in `/examples/drag-drop` to test all layout configurations
404
- - **Drop Zone SCSS Variables**: Full customization of drop zone appearance via SCSS variables
405
- - `$drop-zone-border-radius` - Border radius for all zones (default: 0)
406
- - Per-zone variables for backgrounds, colors, and shadows in both inactive and active states:
407
- - Above: `$drop-zone-above-bg`, `$drop-zone-above-color`, `$drop-zone-above-active-bg`, `$drop-zone-above-active-color`, `$drop-zone-above-active-shadow`
408
- - Below: `$drop-zone-below-bg`, `$drop-zone-below-color`, `$drop-zone-below-active-bg`, `$drop-zone-below-active-color`, `$drop-zone-below-active-shadow`
409
- - Child: `$drop-zone-child-bg`, `$drop-zone-child-color`, `$drop-zone-child-active-bg`, `$drop-zone-child-active-color`, `$drop-zone-child-active-shadow`
410
- - **Drop Zone Positioning**: Moved drop zones from inside `.ltree-node-content` to `.ltree-node-row` level
411
- - Eliminates padding-related gaps that made zones hard to reach
412
- - More predictable positioning relative to the full row width
413
- - **Wave2 Layout Overlap**: Added 10% overlap for Above/Below zones in wave2 layout
414
- - Ensures first node's Above zone and last node's Below zone are always reachable
415
- - Child zone shrunk to 80% height to accommodate overlap without zone collision
416
- - **dropZoneStart Flexibility**: Now accepts both number (percentage) and string (any CSS value)
417
- - Number: treated as percentage (e.g., `33` `33%`)
418
- - String: used as-is (e.g., `"33%"`, `"50px"`, `"3rem"`)
419
- - **Touch UX**: Added CSS properties to prevent text selection during touch drag
420
- - `-webkit-user-select: none` and `-webkit-touch-callout: none` on node content
421
- - **Ghost Element Styling**: New `.ltree-touch-ghost` CSS class with customizable CSS variables
422
- - `--tree-ghost-bg`: Background color (default: rgba(59, 130, 246, 0.9))
423
- - `--tree-ghost-color`: Text color (default: white)
424
- - **Drop Placeholder Styling**: New `.ltree-drop-placeholder` and `.ltree-drop-placeholder-content` CSS classes
425
- - **Drop Indicator Arrow SCSS Variables**: Full customization of arrow indicators via SCSS variables
426
- - `$drop-arrow-above`, `$drop-arrow-below`, `$drop-arrow-child` - SVG data URIs for each direction
427
- - `$drop-arrow-size` - Arrow size (default: 24px)
428
- - `$drop-arrow-position` - Horizontal position within row (default: 66%)
429
- - `$drop-arrow-above-rotation`, `$drop-arrow-below-rotation`, `$drop-arrow-child-rotation` - Rotation angles
430
- - **Search Example Page Redesign**: Merged filter and search cards into unified search experience
431
- - Single search input with live filtering and result navigation
432
- - Prev/Next buttons to traverse search results with wrap-around
433
- - Result counter showing "X of Y" position indicator
434
- - Keyboard navigation: Enter = next result, Shift+Enter = previous
435
- - Clickable result list with active item highlighting
436
- - Auto-scroll to first result when searching
437
-
438
- ### Fixed
439
- - **Empty Tree Drop Placeholder**: Fixed drop placeholder not appearing when dragging to empty trees
440
- - Added missing `ondragenter` handler to empty state divs
441
- - Added `min-height: 60px` to `.ltree-empty-state` to ensure drop target is always reachable
442
- - **Drag-Drop Demo ID/Path Mismatch**: Fixed bug where second node drop to first node didn't work on first try
443
- - Root cause: `nextId++` post-increment caused id and path to use different values
444
- - Fixed by extracting `const itemId = nextId++` before using in object properties
445
- - **Tree Data Reset**: Fixed `insertArray` not clearing existing tree data when called with new/empty data
446
- - Previously, setting `data = []` would not clear the tree - existing nodes remained visible
447
- - Now `insertArray` properly resets root children, nodeCount, and maxLevel before inserting
448
- - **Example Pages Data Insertion**: Added `isSorted={true}` to all example page Tree components
449
- - Prevents "Could not find parent node" errors caused by `sortCallback` sorting data before insertion
450
- - When `sortCallback` alphabetizes data, children could be inserted before parents (e.g., "AuthService" before "Services")
451
- - `isSorted={true}` tells the tree to skip pre-sorting and only use `sortCallback` for display ordering
452
- - **Search Example Async Index**: Added note explaining that search index is built asynchronously
453
- - Added Enter key support for better UX when retrying searches
454
- - Users are now informed to wait a moment if no results appear immediately after page load
455
- - **Search Example Reactivity**: Made "Search Nodes (Query)" input reactive
456
- - Added `$effect` to automatically trigger search when input changes
457
- - **Glow Mode Border Radius**: Fixed border-radius appearing on glow drop indicators during drag
458
- - Changed `$tree-node-content-border-radius` default from `4px` to `0`
459
- - **Search Example Documentation**: Fixed incorrect prop name in search configuration table
460
- - Changed `searchValueCallback` `getSearchValueCallback` (correct prop name)
461
- - Fixed callback signature from `(item: T) => string` to `(node: LTreeNode<T>) => string`
462
- - **LTreeNode Type Export**: Added `LTreeNode` re-export from `types.ts`
463
- - Fixes import errors when using `import type { LTreeNode } from '$lib/ltree/types'`
464
- - **Search Example Endless Loop**: Fixed `$effect` causing infinite loop on search
465
- - Used Svelte's `untrack()` to prevent reactive state updates from re-triggering the effect
466
- - Effect now only reacts to `searchText` changes, not internal state mutations
467
- - **Search Example Container Scroll**: Fixed prev/next navigation scrolling the entire page
468
- - Now uses `scrollToPath(path, { containerScroll: true })` for container-scoped scrolling
469
- - **Critical Performance Bug in insertArray**: Fixed O() algorithm causing 85+ second load times
470
- - Progressive render check was iterating all remaining nodes for every node at expandLevel
471
- - With 17,000 nodes this caused ~145 million iterations instead of ~34,000
472
- - Fix: Pre-compute last expandLevel index once, then use simple index comparison
473
- - Result: Load time reduced from 85+ seconds to under 1 second
474
- - **Global API Constants**: Fixed `__PACKAGE_NAME__ is not defined` error when using library in other projects
475
- - Vite `define` constants only work during dev, not when library is built with `svelte-package`
476
- - Added `scripts/generate-constants.js` to bake package.json values into `constants.generated.ts`
477
-
478
- ### Important - Svelte 5 Performance
479
-
480
- **Use `$state.raw()` for large datasets passed to Tree component**
481
-
482
- When passing large arrays (1000+ items) to the Tree component, use `$state.raw()` instead of `$state()` to avoid severe performance degradation:
483
-
484
- ```typescript
485
- // SLOW - Svelte deeply proxies all 8000+ objects, causing 5000x slowdown
486
- let treeNodes = $state<TreeNode[]>([])
487
- treeNodes = response.data // Each item becomes a Proxy
488
-
489
- // FAST - Array is reactive but items remain plain objects
490
- let treeNodes = $state.raw<TreeNode[]>([])
491
- treeNodes = response.data // Items stay as plain objects
492
- ```
493
-
494
- **Why this matters:**
495
- - `$state()` creates deep proxies - every nested object becomes a Proxy
496
- - Tree's `insertArray()` accesses multiple properties on each data item
497
- - With 8000 items × ~10 property accesses = 80,000 proxy operations
498
- - Proxy overhead: ~2.2ms per item vs ~0.0004ms for plain objects (5,500x slower)
499
-
500
- **Symptoms of this issue:**
501
- - Tree takes 15-90+ seconds to render with thousands of items
502
- - Console shows `[Violation] 'message' handler took XXXXms`
503
- - Same data loads instantly in isolated test environment
504
-
505
- **The fix does NOT affect reactivity** - changes to `treeNodes` array itself still trigger updates. Only the individual items inside lose deep reactivity, which Tree doesn't need.
506
- - Build now runs `npm run generate-constants` before `svelte-package`
507
- - **Glow Mode Border Radius**: Added `!important` to `border-radius: 0` on glow classes to ensure override
508
- - Removed `border-radius: 4px !important` from `.ltree-dragover-glow` class
509
- - Removed `border-radius` from `.ltree-node-content` transition to prevent animation glitch
510
- - **Glow Mode Drop Validation**: Fixed glow showing on invalid drop targets
511
- - When `dragDropMode='cross'` prevents drops, glow no longer appears on hover
512
- - Cleared `hoveredNodeForDrop` state when drop validation fails
513
- - Debug logging for drop rejection now gated behind `shouldDisplayDebugInformation`
514
- - **Glow Mode Border Radius Transition**: Fixed double border artifact on node hover during drag
515
- - Added `border-radius` to transition property for smooth animation
516
- - Eliminates visual conflict between base rounded corners and glow's straight corners
517
- - **Ctrl+Drag dropEffect Timing**: Fixed copy operation failing with `dropEffect: none`
518
- - Root cause: Node's `ondragover` read stale `dropOperation` prop before Tree updated it
519
- - Fix: Node now reads `event.ctrlKey` directly to calculate dropEffect immediately
520
- - Also added dropEffect confirmation in `ondrop` handlers for spec compliance
521
- - **Missing allowCopy Prop in Recursive Node**: Fixed `allowCopy` not being passed to child nodes
522
- - Caused Ctrl+drag to fail on non-root nodes since they defaulted to `allowCopy=false`
523
- - Added `{allowCopy}` to recursive Node component call
524
-
525
- ### Changed
526
- - **BREAKING: onNodeDrop Signature**: Callback signature updated to include drop position
527
- - Before: `onNodeDrop?: (dropNode, draggedNode, event) => void`
528
- - After: `onNodeDrop?: (dropNode, draggedNode, position, event) => void`
529
- - `position` is `'above'`, `'below'`, or `'child'` indicating where item should be placed
530
- - `dropNode` can be `null` when dropping into empty tree or root drop zone
531
-
532
- ## [4.4.0] - 2025-10-02
533
-
534
- ### Added
535
- - **External Update Method**: New `update()` method for programmatic prop updates from vanilla JavaScript
536
- - Allows external code to update component props without Svelte reactivity
537
- - Accepts partial object with any Tree props (excluding snippets/templates)
538
- - Useful for HTML/JavaScript integration and dynamic configuration
539
- - Example: `tree.update({ searchText: 'query', expandLevel: 3, data: newData })`
540
-
541
- ### Fixed
542
- - **Search Functionality**: Fixed search filtering in context-menu dev page
543
- - Added missing `searchValueMember="name"` prop to enable proper search indexing
544
- - Search now correctly filters nodes by name instead of filtering everything out
545
-
546
- ### Changed
547
- - **Code Cleanup**: Renamed internal "trie" references to "tree" for consistency
548
- - Updated variable names in Tree.svelte, Node.svelte, and ltree-demo.ts
549
- - Removed "trie" from package.json keywords
550
- - Improved code readability and naming consistency throughout codebase
551
-
552
- ## [4.3.1] - 2025-09-25
553
-
554
- ### Enhanced
555
- - **Async Callback Support**: Context menu callbacks now fully support async operations
556
- - Updated `callback: () => void | Promise<void>` signature in `ContextMenuItem` interface
557
- - Added automatic error handling for async callbacks with try/catch wrapper
558
- - Menu item clicks properly await async operations before completing
559
- - Errors in async callbacks are logged to console for debugging
560
- - **Robust Error Handling**: Async callback failures don't break menu functionality
561
- - Failed async operations are caught and logged automatically
562
- - Developers can implement custom error handling within their callbacks
563
- - Menu stays open on errors, allowing users to retry actions
564
- - **Enhanced Dev Examples**: Added comprehensive async callback demonstrations
565
- - Copy action with simulated network delay
566
- - New folder creation with error simulation (20% failure rate)
567
- - Database backup with long-running operation simulation
568
- - Shows patterns for success/failure handling and conditional menu closing
569
-
570
- ### Documentation
571
- - **Async Patterns**: Examples showing proper async callback implementation
572
- - **Error Handling**: Best practices for managing async operation failures
573
- - **Menu Control**: Demonstrated conditional closing based on operation success/failure
574
-
575
- ## [4.3.0] - 2025-09-25
576
-
577
- ### Added
578
- - **Enhanced Context Menu Control**: Context menu callback now receives `closeMenuCallback` parameter for programmatic menu control
579
- - `contextMenuCallback?: (node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[]`
580
- - Developers can now control when/if context menu closes after menu item actions
581
- - Enables conditional closing patterns (e.g., don't close on cancel, only on success)
582
- - Public `closeContextMenu()` method exported for external control
583
- - **Context Menu Item Styling**: New `className?: string` property in `ContextMenuItem` interface
584
- - Apply custom CSS classes to individual menu items for styling
585
- - Supports multiple classes (space-separated strings)
586
- - Example: `className: 'text-danger fw-bold'` for destructive actions
587
- - **Enhanced Dev Examples**: Updated context menu examples to demonstrate new features
588
- - Conditional menu closing patterns for different action types
589
- - CSS class styling demonstrations with Bootstrap classes
590
- - Improved UX patterns showing when to close vs keep menu open
591
-
592
- ### Enhanced
593
- - **Flexible Menu Behavior**: Context menu now supports various interaction patterns
594
- - Immediate close after action completion
595
- - Conditional close based on user confirmation
596
- - Persistent menu for multi-step operations
597
- - Custom styling per menu item type
598
-
599
- ## [4.2.1] - 2025-09-24
600
-
601
- ### Enhanced
602
- - **Debug Context Menu Positioning**: Improved debug context menu to position relative to tree element instead of viewport
603
- - Debug menu now appears 200px right and 100px down from each tree's top-left corner
604
- - Supports multiple trees on same page with individual positioning
605
- - Enhanced debug logging to include tree ID and calculated position coordinates
606
- - Better for CSS development when tree is not at top-left of viewport
607
- - **Debug Context Menu Robustness**: Enhanced debug mode to work with single-node trees
608
- - Uses second node when available, falls back to first node for single-node trees
609
- - More flexible node selection for debug menu display
610
- - Improved reliability for development scenarios
611
-
612
- ### Fixed
613
- - **Debug Mode State Management**: Fixed context menu interference between debug mode and normal right-click menus
614
- - Added `isDebugMenuActive` state tracking to prevent debug logic from hiding user-triggered menus
615
- - Normal right-click context menus now work properly when debug mode is disabled
616
- - Proper cleanup of debug state when switching between modes
617
- - **Debug Mode Requirements**: Relaxed debug context menu requirements to support edge cases
618
- - Changed minimum tree length requirement from `> 1` to `> 0` for better compatibility
619
- - Debug mode now works with any non-empty tree structure
620
-
621
- ## [4.2.0] - 2025-09-24
622
-
623
- ### Added
624
- - **Context Menu System**: Comprehensive context menu functionality with two implementation approaches
625
- - **Callback-based Context Menus**: New `contextMenuCallback` prop that accepts a function `(node: LTreeNode<T>) => ContextMenuItem[]`
626
- - **ContextMenuItem Interface**: New interface with `icon`, `title`, `isDisabled`, `callback`, and `isDivider` properties
627
- - **Position Offset Configuration**: New `contextMenuXOffset` (default: 8px) and `contextMenuYOffset` (default: 0px) props for cursor clearance
628
- - **Debug Mode**: New `shouldDisplayContextMenuInDebugMode` prop for persistent context menu display at fixed position (200px, 100px)
629
- - **Snippet-based Support**: Maintains backward compatibility with existing `{#snippet contextMenu(node, closeMenu)}` approach
630
- - **Enhanced Context Menu UX**:
631
- - Auto-close on scroll events (mouse wheel, scrollbar, touch, programmatic)
632
- - Auto-close on outside clicks
633
- - Support for disabled menu items with visual feedback
634
- - Support for menu dividers for visual organization
635
- - Rich icon support for menu items
636
- - **Development Tools**: New `/dev/context-menu` page with comprehensive examples
637
- - Basic file system context menu example with conditional actions
638
- - Advanced server management example with status-based and type-specific menus
639
- - Real-time offset configuration testing
640
- - Interactive demonstration of all context menu features
641
- - Debug context menu mode with `shouldDisplayContextMenuInDebugMode` for easy styling development
642
- - Navigation link added to main layout for easy access
643
-
644
- ### Enhanced
645
- - **CSS Styling**: Added comprehensive context menu styles in `main.scss`
646
- - `.ltree-context-menu`, `.ltree-context-menu-item`, `.ltree-context-menu-icon`, `.ltree-context-menu-divider` classes
647
- - Support for disabled states with `.ltree-context-menu-item-disabled`
648
- - Flexible layout with proper hover effects and visual hierarchy
649
- - **Type Safety**: Full TypeScript support for all context menu features
650
- - **Documentation**: Comprehensive README and CLAUDE.md updates covering both implementation approaches
651
-
652
- ### Fixed
653
- - **Context Menu Scroll Behavior**: Fixed issue where context menu remained visible when scrolling
654
- - Added scroll event listeners with capture phase to catch all scroll events
655
- - Added wheel event listeners for mouse wheel scrolling
656
- - Context menu now properly closes on any scroll interaction
657
-
658
- ## [4.1.1] - 2025-09-23
659
-
660
- ### Fixed
661
- - **TreePathSeparator Default Value**: Fixed `treePathSeparator` parameter to properly default to '.' when not provided to Tree.svelte
662
- - Previously, when `treePathSeparator` was undefined, the reactive effect would override the ltree's internal default
663
- - Now defaults to '.' in the parameter destructuring, ensuring consistent behavior
664
-
665
- ## [4.1.0] - 2025-09-23
666
-
667
- ### Fixed
668
- - **Critical Sorting Bug**: Fixed default sort method to sort by level first, ensuring proper hierarchical tree construction
669
- - Previously sorted by parent path first, causing level 3 nodes to be inserted before level 2 nodes
670
- - Now sorts by level (depth) first, then parent path, then display value
671
- - Eliminates "Could not find parent node" errors when nodes are inserted out of level order
672
- - **Progressive Rendering**: Fixed progressive rendering feature to work correctly with proper level-based sorting
673
- - Progressive rendering now displays levels 1-2 immediately while deeper levels continue processing
674
- - Improves perceived performance for large datasets by showing initial tree structure quickly
675
- - **TreePathSeparator Reactivity**: Fixed Tree component to properly update internal separator when `treePathSeparator` prop changes
676
- - Added reactive effect to update ltree's separator property when prop changes
677
- - Prevents race conditions where data is processed with wrong separator
678
- - Fixes filesystem demo and other custom separator use cases
679
- - **Sort Functions in Examples**: Updated all demo sort functions to calculate level from path depth during sorting
680
- - Home page, dev page, and filesystem examples now use path-based level calculation
681
- - Ensures consistent level-first sorting across all demos and examples
682
- - Prevents insertion failures in example applications
683
-
684
- ### Enhanced
685
- - **Test Coverage**: Added comprehensive test suite for sorting functionality
686
- - Tests verify level-first sorting behavior with various hierarchical data structures
687
- - Validates progressive rendering scenarios and sort correctness
688
- - Uses Vitest framework for fast, reliable testing
689
-
690
- ### Changed
691
- - **Default Sort Algorithm**: Updated `_defaultSort` method to prioritize level over parent path for hierarchical correctness
692
- - **Example Sort Functions**: All demo applications now use level-first sorting for consistent behavior
693
-
694
- ## [4.0.1] - 2025-01-23
695
-
696
- ### Fixed
697
- - **treePathSeparator Propagation**: Fixed helper functions (`getParentPath`, `getRelativePath`, `getPathSegments`) to properly use the configured `treePathSeparator` instead of hardcoded "." separator
698
- - All path manipulation functions now respect the custom separator setting
699
- - Ensures consistent path handling throughout the tree operations when using custom separators like "/"
700
- - Fixed `getRelativePath` to use `pathSeparator.length` instead of assuming single character
701
- - Fixed `getLevel` to properly count segments with multi-character separators
702
-
703
- ### Added
704
- - **Test Suite**: Added comprehensive test coverage for ltree helper functions
705
- - 24 test cases covering single-character, multi-character, and edge case separators
706
- - Vitest testing framework integration with `npm run test` and `make test` commands
707
- - Tests validate proper handling of separators like `"::"`, `"->>"`, `"<|>"` and complex edge cases
708
-
709
- ## [4.0.0] - 2025-01-09
710
-
711
- ### Added
712
- - **Complete Showcase Site Redesign**: Comprehensive overhaul of the documentation and demo site
713
- - **API Reference Page**: Complete tabbed reference with properties, methods, events, and templates tables
714
- - **Professional Navigation**: Fixed-top navbar with burger menu, GitHub link, and responsive sidebar
715
- - **Ocean Color Scheme**: Beautiful blue-themed design using Coolors.co palette (#00171F, #003459, #007EA7, #00A7E1, #FFFFFF)
716
- - **Responsive Layout**: Mobile-first design with collapsible sidebar and backdrop overlay
717
- - **Enhanced Examples**: Four comprehensive code examples with descriptions in tabbed interface
718
- - **Docker Production Setup**: Complete containerization for static site deployment
719
- - **Multi-stage Dockerfile**: Optimized build with Node.js builder and nginx production stage
720
- - **Static Site Generation**: SvelteKit configuration for pre-rendered HTML pages
721
- - **Make Commands**: Docker build, run, and management commands with custom registry support
722
- - **Nginx Configuration**: Optimized serving with gzip, caching, and SPA routing support
723
-
724
- ### Changed
725
- - **Layout Architecture**: Moved from nested Bootstrap containers to clean, consistent structure
726
- - **Fixed Navigation**: Top navbar with brand, burger menu, and GitHub link
727
- - **Sidebar Design**: Fixed-width (280px) sidebar with consistent icon spacing
728
- - **Footer Integration**: Professional footer with KeenMate branding
729
- - **SvelteKit Configuration**: Updated for optimal static generation
730
- - **Static Adapter**: Switched from adapter-auto to adapter-static for reliable builds
731
- - **Prerendering**: Enabled SSR and prerender for all showcase pages
732
- - **Build Output**: Optimized for nginx serving with proper fallback handling
733
- - **Page Structure Consistency**: Standardized header structure across all showcase pages
734
- - **Removed Redundant Containers**: Eliminated nested container-fluid wrappers
735
- - **Clean Headers**: Direct h1 and description elements without Bootstrap grid overhead
736
-
737
- ### Enhanced
738
- - **Visual Design**: Professional styling throughout the showcase site
739
- - **Fixed Icon Alignment**: Consistent 1.5rem width for sidebar navigation icons
740
- - **Gradient Backgrounds**: Sophisticated color gradients across navbar, sidebar, and footer
741
- - **Interactive Elements**: Hover effects, focus states, and smooth transitions
742
- - **Typography**: Clear hierarchy with proper contrast and accessibility
743
- - **User Experience**: Improved navigation and usability
744
- - **Always-Visible Burger Menu**: Toggle sidebar on any screen size for flexible layout
745
- - **Responsive Behavior**: Automatic sidebar hiding on mobile with backdrop close
746
- - **Tab Navigation**: Full-width code examples with clean tab interface
747
- - **Mobile Optimization**: Touch-friendly interactions and responsive text sizing
748
-
749
- ### Fixed
750
- - **Container Structure**: Resolved double-container issues causing layout inconsistencies
751
- - **Sidebar Toggle**: Fixed burger menu functionality to work across all screen sizes
752
- - **Static Generation**: Proper SvelteKit configuration for nginx-compatible static builds
753
- - **Icon Spacing**: Consistent navigation icon width preventing text misalignment
754
-
755
- ### Documentation
756
- - **API Reference**: Complete tables for all component properties, methods, events, and templates
757
- - **Usage Examples**: Real-world code examples including organization tree configuration
758
- - **Docker Documentation**: Make commands and containerization setup
759
- - **Responsive Design**: Mobile-first approach with professional styling
760
-
761
- ## [4.0.0-rc.08] - 2025-01-08
762
-
763
- ### Added
764
- - **searchNodes() Method**: New public method `searchNodes(searchText)` that returns an array of matching nodes without filtering the tree display
765
- - Programmatically search nodes using the internal search index
766
- - Returns `LTreeNode<T>[]` array of matching nodes
767
- - Useful for building custom search interfaces, suggestions, and result summaries
768
- - **Configurable Path Separators**: New `treePathSeparator` property allows custom hierarchical path separators
769
- - Default remains `"."` for backward compatibility (e.g., "1.2.3")
770
- - Support for custom separators like `"/"` for file system style paths (e.g., "1/src/components")
771
- - All path operations throughout the component respect the custom separator
772
- - **Data Structure Showcase Page**: New comprehensive `/data-structure` showcase page with four detailed sections:
773
- - **LTree Path Structure**: Understanding path-based hierarchical data model
774
- - **Optimized Data Structure**: Precomputed values for better performance
775
- - **Custom Path Separators**: Live demo with file system style paths using "/" separator
776
- - **External Search & Data Management**: Managing search outside the tree component
777
- - **Invalid Data Structures**: Common mistakes and unsupported patterns
778
- - **Enhanced Search Showcase**: Added new `searchNodes()` method demonstration section to `/search` page
779
- - Interactive search interface showing difference between `searchNodes()` and `filterNodes()`
780
- - Live examples with result display and usage patterns
781
- - **Insert Result Information**: New `insertResult` bindable property provides detailed information about data insertion
782
- - `InsertArrayResult<T>` interface with successful count and failed nodes array
783
- - Each failed node includes original data, processed node, and error message
784
- - Useful for data validation, debugging, and handling incomplete datasets
785
- - **Drag-over Visual Feedback**: New `dragOverNodeClass` property for highlighting nodes during drag operations
786
- - Two built-in classes: `ltree-dragover-highlight` (dashed border) and `ltree-dragover-glow` (shadow effect)
787
- - Automatic state management with proper drag event handling
788
- - Provides clear visual feedback for drop targets during drag-and-drop operations
789
-
790
- ### Changed
791
- - **Documentation Updates**: Updated README.md, CLAUDE.md, and showcase pages with new features
792
- - Added `searchNodes` to public methods documentation
793
- - Added `treePathSeparator` to Tree Configuration properties table
794
- - Updated architecture description to reflect configurable separators
795
- - Fixed path requirements documentation to clarify separator flexibility
796
- - **Navigation Enhancement**: Added "Data Structure" page to sidebar navigation with 🗂️ icon
797
-
798
- ### Enhanced
799
- - **Type System**: Updated `Ltree<T>` interface to include `searchNodes` method signature
800
- - **Internal Architecture**: Enhanced `createLTree` function to accept configurable `treePathSeparator` parameter
801
- - **Component Integration**: Updated `Tree.svelte` component to pass through `treePathSeparator` property
802
-
803
- ### Fixed
804
- - **Node Indentation**: Fixed `Node.svelte` indent style to use consistent per-level indentation instead of cumulative indentation
805
- - Previously: Each level had exponentially increasing indent (level * indent-per-level)
806
- - Now: Each level uses fixed CSS variable `--tree-node-indent-per-level` allowing proper CSS-based indentation control
807
- - **Search Index Accuracy**: Fixed `insertArray` to only add successfully inserted nodes to `flatTreeNodes` array
808
- - Prevents search index from returning incorrect node indices for nodes that failed to insert
809
- - Failed nodes are no longer included in search operations, ensuring search results match visible tree structure
810
- - **Error Message Clarity**: Improved `insertTreeNode` error messages to include the failing node's path
811
- - Error format: `"Node: {path} - Could not find parent node: {parentPath}"`
812
- - Makes debugging hierarchical data issues much clearer
813
-
814
- ### Documentation
815
- - **Comprehensive Examples**: Added working code examples for both basic and advanced use cases
816
- - **Path Separator Flexibility**: Clarified that paths don't need to be dot-separated, can use any consistent separator
817
- - **External Data Management**: Detailed examples of filtering data outside the tree component
818
- - **Performance Optimization**: Guidelines for when to use precomputed values vs automatic calculations
819
-
820
- ## [4.0.0-rc.07] - 2025-01-06
821
-
822
- ### Added
823
- - **Customizable Scroll Highlight**: New `scrollHighlightClass` property allows users to define custom CSS classes for scroll highlight effects
824
- - **Built-in Highlight Options**: Added pre-built highlight classes:
825
- - `ltree-scroll-highlight` - Background glow with blue color (default)
826
- - `ltree-scroll-highlight-arrow` - Red arrow indicator positioned to the right of the node
827
- - **Scroll Highlight Timeout Control**: New `scrollHighlightTimeout` property (default: 4000ms) controls duration of highlight effect
828
- - **Enhanced scrollToPath Method**: Improved scroll highlighting with proper element targeting and CSS class management
829
- - **Debug Logging Control for Indexer**: Added `shouldDisplayDebugInformation` property to Indexer class for consistent debug logging control
830
-
831
- ### Changed
832
- - **Removed CSS Animation Dependencies**: Scroll highlighting now uses pure CSS classes instead of CSS animations for better timeout control
833
- - **Improved Element Targeting**: `scrollToPath` now targets `.ltree-node-content` specifically for more precise highlighting
834
- - **Enhanced Documentation**: Updated README with comprehensive examples for highlight customization
835
- - **Consistent Debug Logging**: All indexer console.log messages now respect the `shouldDisplayDebugInformation` flag for unified logging control
836
-
837
- ### Fixed
838
- - **Scroll Highlight Duration**: Fixed issue where CSS animations overrode JavaScript timeout values
839
- - **Element Selection**: Improved DOM element selection for scroll highlighting functionality
840
- - **LTree Path Traversal**: Fixed `expandNodes` and `collapseNodes` methods by correctly prefixing path segments with 'x' prefix to match internal tree structure storage
841
-
842
- ## [4.0.0-rc.05] - 2025-09-05
843
-
844
- ### Added
845
- - **Optimized Async Search Indexing**: Improved indexing implementation that processes entire queue at once during idle time instead of small batches
846
- - **Enhanced Indexing Performance**: Increased batch size from 100 to 1000 nodes and streamlined queue processing
847
- - **Better Debug Logging**: Added conditional debug logging for indexing operations when `shouldDisplayDebugInformation` is enabled
848
-
849
- ### Changed
850
- - **Indexing Architecture**: Refactored async indexing to process all queued nodes in a single idle callback rather than batched processing
851
- - **Queue Management**: Simplified indexing queue processing with more efficient completion handling
852
- - **TypeScript Support**: Added `Tuple<T, U>` type import for enhanced type safety
853
-
854
- ### Performance
855
- - **Faster Indexing**: Single-pass indexing of entire queue reduces overhead and callback scheduling
856
- - **Reduced Idle Callbacks**: Less frequent but more efficient use of `requestIdleCallback`
857
- - **Improved Memory Usage**: More efficient queue management with immediate processing
858
-
859
- ## [4.0.0] - 2025-09-01
860
-
861
- ### Added
862
- - **Asynchronous Search Indexing**: Search indexing now uses `requestIdleCallback` for non-blocking performance
863
- - **Statistics Tracking**: New `statistics` getter provides real-time data:
864
- - `nodeCount`: Total number of nodes in the tree
865
- - `maxLevel`: Maximum depth level of the tree
866
- - `filteredNodeCount`: Number of nodes currently visible when filtering
867
- - `isIndexing`: Boolean indicating if search indexing is in progress
868
- - `pendingIndexCount`: Number of nodes pending indexing
869
- - **Expand Level Control**: New `expandLevel` property (default: 2) automatically expands nodes up to specified depth
870
- - **Drag & Drop Properties**: Added `isDraggableMember` and `isDropAllowedMember` for fine-grained drag & drop control
871
- - **Debug Information Panel**: Enhanced debug display with collapsible interface showing tree statistics and indexing progress
872
-
873
- ### Changed
874
- - **Breaking**: Renamed internal references from "Trie" to "LTree" for consistency
875
- - **Breaking**: `trieId` property renamed to `treeId`
876
- - **Search Performance**: Tree now renders immediately while search indexing happens asynchronously
877
- - **Debug Styling**: Updated debug panel styling to use `em` units with reduced padding
878
-
879
- ### Performance
880
- - **Non-blocking UI**: Tree renders immediately while search indexing occurs during browser idle time
881
- - **Improved Large Dataset Handling**: Async indexing prevents UI freezing with large data sets
882
- - **Batch Processing**: Search indexing processes nodes in batches during idle periods
883
- - **Graceful Degradation**: Falls back to `setTimeout` on browsers without `requestIdleCallback` support
884
-
885
- ### Documentation
886
- - Added comprehensive documentation for async search indexing
887
- - Added warning about search indexing requirements
888
- - Enhanced API documentation with new properties and statistics
889
- - Updated performance section highlighting async capabilities
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [5.0.0-rc12] - 2026-06-28 [PUBLISHED]
9
+
10
+ Showcase / docs polish (a context-menu doc note and a theming-playground CSS fix surfaced by manual review of `/examples/theming` and `/examples/tree-editor`), plus one library correctness fix: keyboard copy/cut/paste threw on default `$state` data.
11
+
12
+ ### Fixed
13
+ - **Context menu: three fixes surfaced by driving the library menu from the Windows Explorer demo's right pane**. (1) **Re-opening on a different node no longer leaves the menu stuck** — the Floating-UI positioning `$effect` in `Tree.svelte` only depended on `contextMenuVisible`, so a second right-click on another node (visible stays `true`) didn't re-run it; `autoUpdate` stayed anchored to the first node's coordinates and only recomputes on scroll/resize, so the menu sat at the old spot until you left-clicked to close it (`visible false`) and right-clicked again. The effect now also tracks `contextMenuX` / `contextMenuY` / `contextMenuNode`, so it tears down and re-anchors when the menu jumps nodes. (2) **Clicking a menu item now auto-closes the menu** `ContextMenuLevel` (the `getContextMenuItemsCallback` render path) ran the item's `onclick` but never dismissed the menu, relying on each consumer to call the provided `close()` in every handler (the test/demo callbacks did so 15×). Leaf-item activation (click + Enter/Space) now closes the menu in a `finally`, so `close()` is optional calling it anyway is harmless. Items that act incrementally (a toggle, a multi-step action) can opt out with the new `shouldCloseOnClick: false` and keep the menu open, dismissing it themselves via the captured `close` when finished (see Added). The snippet-based `ContextMenuItem` path is unchanged (its contract still hands `close` to the consumer). (3) **Dropped a `binding_property_non_reactive` warning** `ContextMenuLevel` held item element refs in a plain array while `bind:this={itemEls[i]}` wrote into it; it's now `$state`, which also lets the submenu-positioning effect re-run once the parent item attaches. Regression coverage: `e2e/context-menu.spec.ts` gains a reposition-on-second-right-click case; `e2e/windows-explorer.spec.ts` asserts a menu item with no manual `close()` still dismisses the menu.
14
+ - **A copied node now stays on the clipboard across multiple pastes — `pasteNodes` only clears the clipboard on cut**: `pasteNodes` called `clearClipboard()` unconditionally after every paste, so a `copy` was single-use — paste once and the clipboard was empty, contradicting every file manager / IDE (Finder, Explorer, VS Code) where a copy can be pasted repeatedly. Now the clipboard is cleared only when `clip.operation === 'cut'` (a move is one-shot — its source nodes were just removed); a `copy` persists so it can be pasted again (each paste re-runs `beforePasteCallback`, so successive pastes into the same parent get `Copy 1`, `Copy 2`, … via the demo's collision-naming, which strips any prior ` Copy N` first so repeats don't compound into `Copy 1 Copy 1`). Applied to both the auto-handled path and the `shouldAutoHandlePaste=false` forwarding path; cut-dimming (`cutPaths`) is still cleared on every paste. This brings svelte-treeview into parity with `@keenmate/web-treeview`, whose `pasteNodes` already clears the clipboard only inside its `if (operation === 'cut')` block — svelte-treeview was the diverging side. Regression coverage in `e2e/clipboard.spec.ts`: a copy pasted twice adds two nodes, and a cut's second paste is a no-op (clipboard emptied by the move).
15
+ - **Keyboard copy/cut/paste (`copyNodes` / `cutNodes`) no longer throw on `$state` data — `structuredClone` can't clone a Svelte reactive proxy**: `TreeController._collectClipboardEntry` snapshotted each node's data with `structuredClone(node.data)`. For any consumer passing the default deeply-reactive `$state` array (the common case), `node.data` is a `Proxy`, and `structuredClone` throws `DataCloneError: ... could not be cloned` so the copy silently failed and a subsequent paste found an empty clipboard. Swapped both clone sites (the root entry and the recursive descendant walk) to `$state.snapshot(...)`, which deproxies reactive state into a plain deep clone and is a no-op-style passthrough for already-plain data, so it's correct whether the consumer uses `$state`, `$state.raw`, or unproxied arrays. Surfaced while wiring Ctrl/Cmd+C/X/V into `/examples/tree-editor` (which uses plain `$state`). Regression coverage: new `e2e/clipboard.spec.ts` against a new `/test/clipboard` fixture that deliberately uses plain `$state` data — copy→paste duplicates a node, cut→paste moves it (count unchanged), cut dims + Escape un-dims, and Ctrl+click multi-select copies several nodes at once.
16
+ - **Brand themes in the `/examples/theming` "Dark Mode Playground" no longer lose their surface in per-instance light mode — Glass in particular stopped rendering white-on-white**: the demo's generic per-instance surface rules (`:global(.playground-wrapper:has(.stv__container[data-theme='light'])) { background: #f9fafb }` and the matching dark variant) carried specificity `(0,3,0)`, which outranks every brand theme's *base* rule `:global(.playground-wrapper.brand-X)` at `(0,2,0)`. In **inherit** and **dark** modes nothing collided — there's no light `:has()` match, and each brand owns a higher-specificity `.brand-X:has(...dark)` rule that wins — but in **light** mode the brand themes have no light-variant rule, so the generic gray override won and reverted the wrapper to plain `#f9fafb`. Most brand themes degraded quietly (their `--base-*` tokens still themed the tree, dark text stayed readable), but **Glass** forces `--base-text-color-1: #ffffff`, so picking `theme="light"` left white labels on a now-white surface — the node text vanished, only the emoji icons showed. Fix scopes the four generic `:has()` surface-flip rules (two wrapper, two tree) to `.brand-default` only: the unstyled default theme is the sole case that needs the generic flip, and every real brand theme already covers all three modes via its base rule (light) and `.brand-X:has(...dark)` variant (dark). Regression coverage: new `e2e/theming-brand.spec.ts` (6 cases) asserts each gradient brand (material / neon / soft / glass / forest) keeps a `linear-gradient` wrapper surface across inherit / dark / light, with a dedicated check that Glass light mode renders its `rgb(102, 126, 234)` purple — the existing `e2e/theming.spec.ts` only exercised dark-mode *signal precedence* via a synthetic Debug theme and never touched the real brand themes, which is why this slipped through.
17
+
18
+ ### Added
19
+ - **`ContextMenuItem.shouldCloseOnClick?: boolean` (default `true`) — per-item opt-out of the new auto-close**: the callback menu now auto-closes after a leaf item is activated (see Fixed), which is the right default for one-shot commands (Open, Delete, Copy path). But some items act *incrementally* — a toggle, a counter, a multi-step action — and want the menu to stay open so the user can click again or read updated state. Setting `shouldCloseOnClick: false` on a `ContextMenuItem` suppresses the auto-close for that entry only; the handler then owns dismissal and calls the `close` callback it captured from `getContextMenuItemsCallback(node, close, …)` (or the snippet's `close` prop) when it's done. Named per the codebase's `should*` boolean-prop convention (sibling fields `isDisabled` / `isVisible`). Implemented in `ContextMenuLevel.svelte` by gating both the click and Enter/Space `finally` close on `item.shouldCloseOnClick !== false`. The `/test/context-menu` fixture gains a "Bump (stays open)" item that increments a counter without closing; `e2e/context-menu.spec.ts` asserts the menu survives repeated clicks of it and that a normal item still dismisses afterward.
20
+ - **Data-driven per-node class hooks `nodeClass?: (node) => string` and `nodeContentClass?: (node) => string`**: previously the only per-row class hooks were *state* classes (`highlightedNodeClass` / `focusedNodeClass` / `dragOverNodeClass`) — there was no way to tag a row from its own data (e.g. `is-folder` / `is-file`, a status colour, a grid-participation class) without reaching into `[data-tree-path=""]` selectors. These two callbacks run per node and return class(es) applied to `.stv__node` (`nodeClass`) and `.stv__node-content` (`nodeContentClass`); they recompute on the node's `_rev` bump like the rest of the render. Plumbed through `NodeConfig` (stable context reference, no prop-drilling) so they don't defeat flat-mode diffing. Wired end-to-end on `<Tree>` (prop → `$effect` sync → `update()` passthrough) and on the `createTreeController` props. New `/test/node-class` fixture + `e2e/node-class.spec.ts` (3 cases) assert the classes land on the right elements and are stylable from app CSS. Motivated by the Explorer demo's right pane needing folder/file row styling.
21
+ - **Public `onNodeDoubleClick?: (node) => void` `<Tree>` event — a real double-click notification, reliable in flat rendering where the native `dblclick` is not**: there was previously no way for a consumer to react to a node double-click. The controller already did manual double-click *detection* (tracking last path + timestamp on the controller, 400ms window) but only internally and only for `clickBehavior="select"`, where a double toggles expand/collapse — and it deliberately avoids the browser's native `dblclick` because the first click bumps `node._rev`, the flat-mode `{#each}` destroys and recreates the row, and the second click lands on a fresh element so the browser refuses to synthesize a `dblclick`. That detection is now generalized to **every** `clickBehavior` and fires the new `onNodeDoubleClick` event; `select` mode additionally keeps its built-in expand/collapse-on-double. On a detected double the second click is consumed (early return) so the gesture reads as a single open rather than a re-toggle — a double therefore fires `onNodeClick` once (the first click) plus `onNodeDoubleClick` once. Detection is gated to genuine UI clicks (a new internal `uiClick` flag on the click path), so programmatic `highlightNode` / `selectNode` calls can never be mistaken for a double-click. Wired end-to-end through `<Tree>` (prop → `$effect` sync → `update()` passthrough) and dogfooded in the Windows Explorer demo's nav tree (double-click a folder opens it in the right pane). New `/test/double-click` fixture + `e2e/double-click.spec.ts` (4 cases) cover firing in both `select` and `expand-and-focus` modes, the single-click negative, and the two-different-nodes negative.
22
+ - **Windows File Explorer demo at `/examples/custom-layout`** (`WindowsExplorer.svelte`): a near-complete dual-pane Explorer clone where **both panes are `<Tree>` instances** with custom `nodeTemplate` renderers, showcasing how far the renderer composes. The left nav pane is a hierarchical `<Tree>` fed a folders-only slice (`leafIconClass=""` so childless folders show no marker, like Explorer); the right pane is a **flat `<Tree>`** rendering the details list (Name / Date modified / Type / Size) as a 4-column grid — the columns line up because each row's `.stv__node-content` is a CSS grid sharing the sticky header's track template inside one scroll area (toggle icons hidden, indent zeroed via `--stv-node-indent-per-level: 0`). The right pane gets **selection** (`selectionMode="multi"` + `bind:highlightedPaths`), **keyboard navigation**, the **right-click context menu** (`getContextMenuItemsCallback` → Open / Copy path / Rename / Delete / New folder / Select all), **double-click-to-open** (`onNodeDoubleClick`), and **per-row folder/file classes** (`nodeClass`) all from the library replacing the earlier hand-rolled list, custom selection set, debounced click handler and bespoke floating menu. The two panes stay in sync through the tree's public API — `expandNodes()` + `focusNode()` — driven from `onNodeClick` / `onNodeDoubleClick`. The dataset models a real `C:\Windows` install (~30 folders, multi-level subfolders + files). Also: per-column sorting (folders-first); **recursive search** across the current folder's subtree with each hit's location shown and **wildcard globs** (`*.dll`, `img?.jpg`); and working New folder / Rename / Delete that mutate the data and restore nav-tree expansion via `getExpandedPaths()` / `setExpandedPaths()`. Rendered as the first card on the existing Custom Layout page (the iOS-Files and Notepad++ demos remain below). Smoke-covered by `e2e/windows-explorer.spec.ts` (12 cases).
23
+ - **Public `onCopy` / `onCut` / `onPaste` `<Tree>` events — post-operation clipboard notifications, symmetric with the `beforeCopy/Cut/Paste` interceptors**: these belong to the library's **Events** family (`on*`), distinct from the `before*Callback` interceptor and `get*Callback` provider families. The controller already fired an `onPaste` handler, but it was only reachable through `createTreeController` props — the `<Tree>` component never forwarded it, and there were no copy/cut equivalents at all. Added `onCopy?: (paths: string[]) => void` and `onCut?: (paths: string[]) => void` (fired after `copyNodes` / `cutNodes` succeed, with the final paths post-interceptor) on the controller, and forwarded all three (`onCopy`, `onCut`, `onPaste`) as `<Tree>` props with the usual `$effect` sync + `update()` passthrough. This completes the clipboard surface: `before*Callback` to rewrite/block, `on*` to react. The "Copy N"-on-collision rename is done in `beforePasteCallback` (mutating the clipboard entries' data before insert), not in `onPaste` — the `on*` events fire after the tree has already changed. Documented in `docs/usage.md` (event table + a new clipboard-interceptor table).
24
+ - **`/examples/tree-editor` gains multi-select + keyboard copy/cut/paste (Ctrl/Cmd + C / X / V)**: the editor now sets `selectionMode="multi"` (Ctrl/Cmd+click and Shift+click extend `highlightedPaths`) and wires the clipboard through the documented `onTreeKeydown(event, controller)` hook. The library already implemented the operations (`copyNodes` / `cutNodes` / `pasteNodes` / `cancelCut`, a shared cross-tree clipboard, and `Escape`→cancel-cut) but intentionally leaves the key *bindings* to the consumer, because paste needs an app-specific `transformData` (the demo assigns fresh ids so pasted copies don't collide) and a target path (the focused node, or root). Copy/cut act on the highlight set when present, else the focused node; paste lands as a child of the focused node. Cut nodes dim until pasted (the demo renders this from a local set in its `nodeTemplate` — the controller exposes `cutPaths` but doesn't paint dimming itself), and `mod` is `event.ctrlKey || event.metaKey` so it works with Cmd on macOS. The demo also wires the new `onCopy` / `onCut` / `onPaste` events (for its activity log) and a `beforePasteCallback` that (a) appends `"Copy 1"` / `"Copy 2"` / … to a pasted copy whose name already exists under the target — stripping any prior ` Copy N` first so repeated pastes number sequentially instead of compounding (only the root is renamed; descendants keep their names; a move keeps the name), and (b) redirects a paste that lands on the copied node itself — Ctrl/Cmd+C then Ctrl/Cmd+V without moving focus — into that node's parent, so the copy drops in as a sibling ("duplicate in the same folder"). Because `beforePasteCallback` runs before the paste-into-self guard, the redirect also sidesteps that guard cleanly.
25
+
26
+ ### Changed
27
+ - **Context Menu Examples page leads with a "Positioning is handled by Floating UI" note**: a short explainer (moved to sit directly below the page header, above the first example card) documenting that the root menu and submenus are placed by `@floating-ui/dom` via `computePosition` + `offset` / `flip` / `shift` / `autoUpdate`, that submenus flip left when there's no room on the right, and that `contextMenuXOffset` / `contextMenuYOffset` feed the root menu's cursor offset.
28
+ - **`/examples/tree-editor` polish**: single click now selects without expanding and double-click toggles expand (`clickBehavior="select"`, was the default `'expand-and-focus'`), and the code-example import was corrected from a default import (`import Tree from …`) to the named `import { Tree } from '@keenmate/svelte-treeview'` — `Tree` is a named export, so the default form resolved to `undefined`.
29
+
30
+ ## [5.0.0-rc11] - 2026-06-25 [PUBLISHED]
31
+
32
+ Consolidated release folding the work previously staged under the unpublished rc12 and rc13 headings (npm's last published `rc` was rc10; the rc12/rc13 labels were never shipped, so this lands as rc11). Three themes: a `/validate-web-component` alignment sweep against the BlissFramework `web-components` guidelines (BEM/prefix `ltree`→`stv` rename, `is*/should*` boolean naming, dark-mode Strategy-B rewrite, README split), the three-level selection / highlight / focus API normalization, and a batch of drag-drop and highlight-marker correctness fixes. Within-RC references to "rc12" below denote earlier iterations of this same unpublished cycle.
33
+
34
+ ### Changed
35
+ - **Selection / highlight / focus API normalized into three symmetric verb-families**: the imperative methods had drifted into "pure chaos" — `selectNode`/`selectNodes` were `@deprecated` aliases that secretly drove the *highlight* set (not checkboxes), the highlight set cleared with `clearHighlight()` while the checkbox set cleared with the differently-named `deselectAll()`, `highlightNodes()` silently *replaced* the set while reading like "add these", there was no imperative way to set the checkbox set by path, and no imperative focus method at all. The surface is now three concerns × the same shape. **Highlight (UI multi-select — `highlightedPaths`):** `highlightNode(path, mode?, opts?)`, `highlightNodes(paths, opts?)` (now **additive**), `setHighlightedPaths(paths, opts?)` (replace), `highlightAll(opts?)`, `clearHighlight(paths?, opts?)` (path-optional). **Selection (checkbox / data state — `selectedPaths`):** `selectNode(path, opts?)` (now actually checks the box, cascades in cascade mode), `selectNodes(paths, opts?)` (additive), `setSelectedPaths(paths, opts?)` (replace), `selectAll(opts?)`, `deselectNode(path, opts?)`, `clearSelection(paths?, opts?)` (renames `deselectAll`). **Focus (single cursor — `focusedNode`):** new `focusNode(path, opts?)` / `clearFocus(opts?)`. Two shared public types replace the inline literals everywhere: `HighlightMode = 'replace' | 'toggle' | 'range'` (the existing `SelectionMode` name was already taken for `'single' | 'multi'`) and `TreeMutationOptions = { silent?: boolean }`. Breaking within the RC: `deselectAll` → `clearSelection`; `clearHighlight({silent})` → `clearHighlight(undefined, {silent})` (options moved to the 2nd arg); `selectNode`/`selectNodes` flip from highlight-aliases to real checkbox setters; `highlightNodes` flips from replace to additive (use `setHighlightedPaths` for the old behavior). Internal `navTo` (keyboard nav) repointed from the old `selectNode`-alias to `highlightNode`. Mirror change applied to `@keenmate/web-treeview` (where the same normalization also renamed its highlight-`selectAll` → `highlightAll`, repointed Ctrl+A to `highlightAll`, and gave it real checkbox `selectNode`/`selectNodes`). `svelte-check` 0 errors; full e2e green.
36
+ - **Boolean Props renamed to follow the `is*/should*/has*/can*` rule** (C-NC-3): ten flags that were ambiguous between verb and noun now read unambiguously as predicates. `showCheckboxes` `shouldShowCheckboxes`, `clickTogglesCheckbox` `shouldClickToggleCheckbox`, `accordionExpand` `isAccordionExpand`, `progressiveRender` `isProgressiveRender`, `useFlatRendering` `isFlatRenderingEnabled`, `virtualScroll` `isVirtualScrollEnabled`, `allowCopy` `isCopyAllowed`, `autoHandleCopy` `shouldAutoHandleCopy`, `autoHandleMove` `shouldAutoHandleMove`, `autoHandlePaste` `shouldAutoHandlePaste`. Six existing flags already followed the rule (`isSorted`, `isLoading`, `isRendering`, `shouldUseInternalSearchIndex`, `shouldDisplayDebugInformation`, `shouldDisplayContextMenuInDebugMode`) and are unchanged. Old names are not aliased within an RC cycle the API is unstable by definition; consumers on rc12 search-and-replace each prop site. Applied across `src/lib/`, `src/routes/`, `e2e/`, `docs/`, `ai/`, `README.md`, `CHANGELOG.md`, `CLAUDE.md` (~314 occurrences across 36 files). `svelte-check` reports 0 errors after the sweep.
37
+ - **`dark-mode.css` migrated to Strategy B (color-scheme flipping)**: the previous file (~140 lines) re-declared every `--stv-*` token inside each conditional signal block (Strategy A). The new file (~45 lines) flips `color-scheme: dark` (or `light`) on the framework-theme + per-instance selectors and lets the `light-dark(<light>, <dark>)` fallbacks already present in `variables.css` resolve the dark branch. Three wins: (a) ~5× shorter file adding a new themeable variable no longer requires touching every signal block; (b) consumer `--base-*` overrides survive — Strategy A's hardcoded dark literals (e.g. `#1a1a1a`) silently replaced themed values, Strategy B leaves the consumer chain untouched; (c) aligns with what web-treeview is migrating to. Signal precedence is unchanged: per-instance `.stv__container[data-theme]` → framework ancestor class → page `color-scheme` via `light-dark()` OS preference via `@media`.
38
+ - **`component-variables.manifest.json` prefix + variable names refreshed**: top-level `"prefix": "ltree"``"prefix": "stv"`, and every `componentVariables[].name` renamed `ltree-X` `stv-X` (the rc12 BEM rename touched the code but left the manifest out of sync theme-designer and any consumer reading the manifest saw names that no longer existed in the CSS). All five auto-script "name declared but not defined" warnings (`ltree-rem`, `ltree-primary`, `ltree-success`, `ltree-danger`, `ltree-light`) resolve cleanly after the rename. `--base-*` usage strings updated to reference `--stv-*` where they cross-link.
39
+ - **`--stv-light` dark fallback tuned from `#1a1a1a` to `#2b2b2b`**: `--stv-light` doubles as the context-menu hover surface and the elevated chip background, so a dark value identical to `--stv-bg` (`#1a1a1a`) flattened the elevated surface against the main bg in dark mode. The new value matches `--stv-elevated-bg` and keeps the surface distinguishable. Affects `variables.css:51` only.
40
+ - **`.stv__container` declares `display: block` explicitly** (C-TC-7): the host `<div>` defaults to block already, but the rule requires the declaration to be explicit so the cascade is reasoned-about, and matches web-treeview's container. Single line in `base.css`.
41
+ - **README cut from 1103 → 347 lines** (C-RS-2): three long sections — `## Advanced Usage`, `## Styling and Customization`, `## API Reference` extracted to topical docs under `docs/`. The README now opens with a new `## What is it` intro (value proposition + audience) and routes readers to deeper material via a `## Demos & docs` link block. What's-New bullets reformatted to the canonical `**lead phrase short headline** — prose body` form with a real em-dash (U+2014) between the bold lead and the prose (C-RS-16). Historical `.ltree-container` / `--ltree-*` references in the rc10 and rc09 bullets renamed to `.stv__container` / `--stv-*` with a parenthetical note that the BEM rename shipped earlier in this cycle.
42
+ - **CSS prefix rename, BEM class shape**: every CSS class and variable now follows the BlissFramework `naming-conventions.md` BEM rule (`<prefix>__element--modifier`, two underscore levels max), with the registered prefix changing from `ltree` to `stv`. Mechanical part: `--ltree-*` → `--stv-*` (every variable; consumer overrides need to swap the prefix). BEM part: every `.ltree-X` class is rewritten — elements as `.stv__X` (e.g. `.ltree-node` → `.stv__node`, `.ltree-node-content` → `.stv__node-content`, `.ltree-context-menu-item` → `.stv__context-menu-item`), state classes as modifiers (e.g. `.ltree-drag-over` → `.stv__node-content--drag-over`, `.ltree-context-menu-item-disabled` → `.stv__context-menu-item--disabled`, `.ltree-icon-expand` → `.stv__toggle-icon--expand`, `.ltree-drop-zones-around` → `.stv__drop-zones--around`). Semantic rename folded in: `.ltree-selected-{bold,border,brackets}` `.stv__node-content--highlight-{bold,border,brackets}` and `.ltree-selected-highlight` → `.stv__node-content--highlight-fill`. One non-BEM utility kept: `.stv__clickable` (was `.ltree-clickable`) — applied to both the toggle-icon span and the node-content div as a plain `cursor: pointer` marker; promoting it to a modifier of one or the other would force a duplicate class. `BlissFramework/guidelines` reservation table now lists `stv` (svelte-treeview) and `wtv` (web-treeview) instead of the legacy `ltree` exception. Internal directory names (`src/lib/ltree/`, `ltree-node.svelte.ts`, `ltree-helpers.ts`) and their imports are untouched — those refer to the LTree data structure, not the CSS prefix. The full rename was applied by `scripts/rename-css-prefix.mjs`, kept in-tree.
43
+ - **Context menu positioning ported to `@floating-ui/dom`**: The root menu and every submenu are now placed by `computePosition` + `autoUpdate` instead of raw `left/top` inline styles and CSS `:hover` show/hide. Root menu uses a virtual reference at `(contextMenuX + contextMenuXOffset, contextMenuY + contextMenuYOffset)` with `bottom-start` placement + `flip()` + `shift({padding:8})` so the menu now flips above the cursor near the viewport bottom and slides sideways instead of clipping. Submenus use `right-start` with a `left-start` fallback. `contextMenuXOffset` / `contextMenuYOffset` props keep their existing semantics — the menu's top-left still lands exactly at `(cursor + offsets)`; the `offset` middleware is set to `0` so no extra gap is introduced. Internally: the recursive context-menu snippet in `Tree.svelte` was extracted into a new `ContextMenuLevel.svelte` component, and `ContextMenuItem.svelte` (the public snippet-based component) was updated to mount its submenu on hover (150ms hide grace) instead of relying on CSS `:hover`. CSS `:hover > .ltree-context-submenu` rules and `position: absolute; left: 100%` removed from `context-menu.css`.
44
+
45
+ ### Added
46
+ - **`docs/usage.md`, `docs/theming.md`, `docs/examples.md`, `docs/accessibility.md`** at the package root: usage carries the full Props / Methods / Events / Snippets reference; theming carries the `--base-*` / `--stv-*` contract, the cascade-layer footgun warning, dark-mode signal precedence, and the BEM class reference; examples carries the drag-drop / multi-select / context-menu / search / tree-edit cookbook; accessibility carries the keyboard navigation table (derived from `Tree.svelte` `handleTreeKeydown`) and the three-level selection model. All four are linked from the README's `## Demos & docs` section.
47
+ - **`## About` and `## Built with BlissFramework` sections in the README** (C-RS-14 / C-RS-15): About credits KeenMate, frames the Pure Admin / `@keenmate/theme-designer` integration as opt-in via the `--base-*` taxonomy, and clarifies that there's no runtime dependency on Pure Admin. BlissFramework links to the live guidelines.
48
+ - **`VALIDATION-NOTES.md` at the package root**: register for accepted deviations from the BlissFramework component guidelines that the team has decided are correct outcomes for this component. Initial entries cover C-CST-4 (namespace-style Logic class split across `src/lib/core/` + `src/lib/ltree/`), C-NC-6 (six structural `*Member` props without paired `getXCallback` `id`, `path`, `parentPath`, `level`, `hasChildren`, `order` because no consumer would want a per-node computed override for "what is this node's id"), C-NC-8 / C-CSS-7 (framework-theme `.dark` / `.light` ancestor qualifiers in `dark-mode.css` are not classes the component emits they're conventions the component honors), and C-CS-5 (dark-mode fixture lives at SvelteKit routes/, not `docs/test/`, because this is a SvelteKit app). Future `/validate-web-component` runs read this file and downgrade matching flags from Fail to ⚠️ Exception.
49
+ - **`.stv__node-content--highlight-glow` built-in highlight flavor + a "Glow (soft ring)" option in `/examples/interaction`**: a fifth shipped highlight class (joining bold / border / brackets / fill) — a primary-tinted background plus a soft `box-shadow` glow ring, distinct from the hard-edged `--highlight-border` and the flat `--highlight-fill`. The interaction demo's "Highlighted Style" dropdown gains the option, and a new "Adding your own highlight & focus styles" note + code block walks through both referencing a built-in highlight class and defining a custom `:global()` focus class. The theming reference table now lists `--highlight-fill` and `--highlight-glow` (previously only bold / border / brackets were documented). A fourth "Focused Style" demo option "Ring (full outline)" (`demo-focused-ring`, page-scoped) was added alongside the existing outline / underline / bg-tint recipes.
50
+ - **Dynamic Theme Switching demo at the bottom of `/examples/theming`**: ported from `@keenmate/web-daterangepicker`'s `examples-theming.html` "Dynamic Theme Switching" section. A single demo tree with two button rows underneath seven brand buttons (Default / Material / Neon / Sharp / Soft / Forest / Glass, each rendered in the accent color of the theme it switches to so the row reads as a swatch picker) that hot-swap a class on the wrapper, and three color-scheme buttons (Inherit / Light / Dark) that drive the per-instance `theme` prop on `<Tree>`. Reuses the existing `.playground-wrapper.brand-*` CSS so all seven brand themes work with zero new theme CSS. A live code block under the buttons shows the JS equivalent for both swaps.
51
+ - **`@floating-ui/dom` runtime dependency** (`^1.7.6`): Required by the context menu positioning above. Matches the version pinned in `@keenmate/web-treeview`.
52
+
53
+ ### Fixed
54
+ - **`collapseNodes`, `collapseAll`, and `expandAll` now refresh the toggle UI of every affected node**: same class of bug as the rc12 `expandNodes({exclusive:true})` fix, but in three sibling code paths that were missed. `collapseNodes` (the path that handles dblclick-to-collapse in `clickBehavior='select'`), `collapseAll`'s `collapseRecursive`, and `expandAll`'s `setExpandedRecursive` + exclusive-mode `trim` helper all flipped `node.isExpanded` without bumping `node._rev`. The flat-mode keyed `{#each}` then reused the existing Node component, leaving `class:expanded={node.isExpanded}` on the toggle icon stale visually the chevron stayed rotated-down on collapsed parents (and rotated-right on just-expanded ones) until something else forced a re-render. Children-visibility worked correctly because `visibleFlatNodes` re-derives from `changeTracker`. Each site now bumps `_rev` after flipping `isExpanded`, guarded by an "actually changed" check so already-correct rows don't churn. The same gap existed in web-treeview's ltree — mirrored there for parity, though its DOM-diff renderer reads `data-expanded` directly and masked the user-visible symptom.
55
+ - **`get*Callback` seed-time props now see `node.data` populated**: `insertArray` and `insertBranch` evaluated `getIsExpandedCallback`, `getIsSelectableCallback`, `getIsSelectedCallback`, `getIsDraggableCallback`, and `getIsDropAllowedCallback` *before* assigning `node.data = row`. Consumer callbacks of the shape `(node) => node.data?.X !== false` therefore saw `undefined` and returned their default branch typically `true` so every per-node opt-out silently failed. Manifested in `/examples/drag-drop`: File C carrying `isDraggable: false` in its data was still draggable. Fix moves `node.data = row` above the callback block at both sites. Regression coverage: new `src/lib/ltree/seed-callbacks.test.ts` (vitest, 7 cases, runs in 4ms) exercises all five callbacks reading from `node.data?.X` in both `insertArray` and `insertBranch` — 4 of 7 tests fail against the pre-fix code.
56
+ - **`.stv__checkbox` defended against unlayered consumer resets (Bootstrap reboot)**: Bootstrap's `reboot.css` ships an unlayered `label { display: inline-block }` which, per the cascade-layers spec, beats *every* layered rule regardless of selector specificity. Result: in a Bootstrap host page the checkbox label fell back to `inline-block` and the visually-hidden absolute-positioned `<input>` shoved the `.stv__checkbox-box` out of line vertically. Fix: `.stv__checkbox { display: inline-flex !important; margin: 0 4px 0 0 !important; }` in the component layer. The `!important` here is not a hack it's the documented escape hatch for component-layer rules colliding with unlayered consumer resets (the rc10 changelog already warns about the unlayered-reset footgun; this is the first place we hit it in practice). Audit of the rest of the library found no other reboot-collision-prone elements emitted every other tag is a `<div>` or `<span>`. Inline comment in `checkbox.css` documents the policy so future `<label>` / `<button>` / `<input>` rules know to mirror it.
57
+ - **Escape now clears the highlight set (multi-select) and cancels a pending cut**: previously Escape did nothing when nodes were Ctrl/Shift-click highlighted — surprising because every analogous tree (Finder, Explorer, VS Code's explorer) treats Escape as "clear what I just picked". The new handler in `Tree.svelte handleTreeKeydown` runs in priority order: (1) if `getClipboardOperation() === 'cut'` and clipboard has content → `cancelCut()` (the cut-dimmed rows un-dim and the operation is aborted, matching the existing Ctrl+C/X/V flow), (2) else if `highlightedPaths.size > 0` → `clearHighlight()`, (3) else `handled = false` so the event falls through to whatever surrounding UI wants it. Checkboxes (`selectedPaths`) are *not* cleared by Escape they're deliberate state ("yes I want this row") and shouldn't be wiped by a stray keypress; consumers wanting that behavior call `deselectAll()` themselves. Context-menu Escape stays on its own window-level listener and never reaches this path. Mirror change applied to `@keenmate/web-treeview` (its `DomRenderer` previously routed Escape to `deselectAll()`, which only touched the checkbox set the rc06 three-level split made that wrong; the controller's `clearHighlight()` is the right call).
58
+ - **`addNode` (and therefore `copyNodeWithDescendants` / `applyChanges` `'create'`) now seeds per-node flags from the `getIs*Callback` / `*Member` props — dropped/added nodes are no longer stuck non-draggable and drop-rejecting**: `addNode` built its node with `createLTreeNode()` (defaults `isDraggable: false`, `isDropAllowed: false`) and never ran the seed resolution that `insertArray`/`insertBranch` do, so a programmatically-added node kept those defaults. The DOM `draggable` attribute (`Node.svelte`) and the drag-over / drop gates read `node.isDraggable` / `node.isDropAllowed` *directly* (not via the dynamic `getNodeIs*` resolvers), so the unseeded node rendered non-draggable and rejected drops even when the tree had a `getIsDraggableCallback` / `getIsDropAllowedCallback`. Surfaced in `/examples/drag-drop`: dropping a subtree into the (initially empty) Target tree worked once — the first drop lands on the empty root, which has no node to gate it — but every node copied in via `copyNodeWithDescendants` was then frozen (couldn't be dragged out, and drops on/around it were refused). Fix mirrors `insertArray`'s seed block in `addNode` (callback > member > default for `isExpanded` / `isSelectable` / `isSelected` / `isDraggable` / `isDropAllowed` / `isCollapsible` / `allowedDropPositions`), evaluated after `node.data` is assigned so the callbacks see populated data. Regression coverage: two new `describe` blocks in `src/lib/ltree/seed-callbacks.test.ts` (vitest) exercising `addNode` and `copyNodeWithDescendants` — both flags resolve from the callback on the new/copied nodes (fail against the pre-fix defaults). web-treeview's `addNode` already seeded the core flags (no user-visible bug there); it gained the two remaining lines (`isCollapsible` / `allowedDropPositions`) for full parity.
59
+ - **Multi-drag now respects per-node draggability — a locked node (`isDraggable=false`) in the highlight set no longer rides along**: dragging a multi-highlight set moves every top-level highlighted subtree, but the `isMultiDrag` branch in `_onNodeDrop` pulled those paths straight from `highlightedPaths` via `_getTopLevelHighlightedPaths()` without ever re-checking draggability. The single-drag path is gated at drag *start* (`startDrag` bails when `!getNodeIsDraggable(node)`), but multi-drag bypassed that gate entirely — so a pinned node that merely happened to be Ctrl/Shift-highlighted alongside draggable siblings got moved anyway. Reproduced in `/examples/drag-drop`: highlighting File A + File B + the locked "File C (pinned)" and dragging the block to the other tree carried File C along. Fix adds a `.filter()` on the top-level paths that drops any node whose `getNodeIsDraggable()` is false (locked descendants that ride *inside* a draggable ancestor's subtree are unaffected — only top-level members of the moved set are gated). Regression coverage: new "Multi-Drag with a locked node" section in `/test/drag-drop` + a case in `e2e/drag-drop.spec.ts` (highlight A/B/locked-C, drag onto D → only A/B nest, C stays at root; fails against the pre-fix code which left just `['Lock-D']` at root). Mirror change applied to `@keenmate/web-treeview` (identical `isMultiDrag` path in its `TreeController`). Note this only covers *same-tree* multi-drag, which the library auto-handles; *cross-tree* multi-drag passes only the lead node ref to `onNodeDrop`, so the consumer reconstructs the set from `highlightedPaths` and is responsible for its own draggability filter — `/examples/drag-drop`'s `handleTargetDrop` was updated to do exactly that (it was the path that still carried "File C (pinned)" into the target tree).
60
+ - **`.stv__node-content--highlighted` marker is now a FALLBACK — it no longer fights a configured `highlightedNodeClass`**: the rc12 marker was applied unconditionally (`class:stv__node-content--highlighted={node.isHighlighted}`), so its subtle default background/outline was painted *underneath* whatever `highlightedNodeClass` the consumer set. Picking "Bold" in `/examples/interaction` therefore showed bold text **on top of** the marker's tint — the marker fighting the chosen style. The fix gates the marker on the class being unset: `class:stv__node-content--highlighted={node.isHighlighted && !highlightedNodeClass}`. Now the marker only provides its default look when nothing is configured (so `selectionMode='multi'` still has visible feedback out of the box), and steps aside entirely the moment a highlight class is set — the configured class becomes the sole source of highlight styling, no `!important` overrides needed. While tracing this: `.stv__node-content--multi-selected` was found to be dead CSS (defined in `states.css`, never applied by any component) — left in place for now, flagged for a separate cleanup pass. Mirror change applied to `@keenmate/web-treeview` (its `DomRenderer` had the same unconditional add in both the create and diff-update paths).
61
+ - **`.stv__node-content--focused` is now a pure CSS hook with no default styles**: the rc12 always-on marker shipped a subtle outline (`outline: 2px solid var(--stv-focused-outline, accent 60%)`), reasoning that keyboard nav should be visible without configuration. But the demo's `/examples/interaction` page exposes a "Focused Style: None (invisible focus)" dropdown option that promised exactly that — *no* focus visual — and rc12's default contradicted the dropdown: picking "None" still painted an outline on the focused row (most visible after Shift+click range, where the last-clicked row is the only focused one). The fix honors the dropdown's promise: `.stv__node-content--focused` is now an empty rule (a pure CSS hook, same philosophy as `.stv__node-label`). Apps that want the rc12 default outline write their own rule and pass the class via `focusedNodeClass` see the `demo-focused-outline` recipe in `/examples/interaction` source. `--stv-focused-outline` is no longer referenced and is dropped as a variable. `.stv__node-content--highlighted` keeps its default subtle look (selectionMode='multi' still produces visible feedback without configuration) the rc12 promise there is sound; only the focused default went too far. Mirror change applied to `@keenmate/web-treeview` for parity.
62
+ - **Checkbox checkmark + indeterminate dash now scale with `--stv-checkbox-size`**: the previous rules hardcoded pixel positions for the checkmark (`left: 4px; top: 1px; width: 4px; height: 8px`) and the indeterminate dash (`left: 3px; width: calc(--stv-checkbox-size - 8px)` asymmetric gap), so bumping `--stv-checkbox-size` from the default `1.5rem` (15px) to e.g. `2.4rem` (24px) left the checkmark anchored to the upper-left corner and the dash sitting off-center. New rules use percentages for both indicators and `translate(-50%, -58%) rotate(45deg)` to center the rotated rectangle's optical center (the −58% instead of −50% compensates for the post-rotation visual mass shifting slightly downward). At the default 15px size the rendered checkmark / dash pixels are within ±1px of the pre-fix output, so no visual shift unless the box is resized. New `--stv-checkbox-checkmark-width` variable (default `2px`, intentionally non-scaling same "hairline" policy as `--stv-checkbox-border-width`).
63
+ - **Desktop drag-drop now honors `node.isDropAllowed`**: the `isDropAllowedMember` / `getIsDropAllowedCallback` props seed `node.isDropAllowed`, but only the touch path consulted it (`TreeController.svelte.ts` lines 3110 + 3199); the desktop `_onNodeDragOver`, `_onNodeDrop`, public `dragOver` / `drop`, and the glow-mode `_onZoneDrop` all skipped it. Net result: setting `isDropAllowed: false` muted touch drops but desktop drag-and-drop went through anyway — surprising given the prop's name. All five sites now gate on `node.isDropAllowed`. The dragover gate suppresses the hover highlight (visual feedback that the target won't accept), and the drop-side gate is the real enforcement (Node.svelte's ondragover calls `event.preventDefault()` itself before forwarding to the controller, so the drop event fires regardless of the dragover gate — the drop handlers must filter). Backwards-compat: every existing fixture and example already opts in via `getIsDropAllowedCallback={() => true}`, so no consumer of the in-tree code is affected.
64
+ - **`expandNodes(path, { exclusive: true })` now updates the toggle UI of collapsed siblings**: The `exclusive` option trims off-spine branches by setting `child.isExpanded = false`, and the expand walk sets `node.isExpanded = true`. Both mutations were missing a `_rev` bump, so the flat-mode keyed `{#each}` reused the existing Node component for the affected rows. The reused component left the toggle icon's `class:expanded` modifier stale — visually the branch looked still-expanded (chevron rotated, even though children were gone) and the just-expanded target's toggle still looked collapsed (even though its children were now visible). The children-visibility side reacted correctly because `visibleFlatNodes` re-derives from `changeTracker`. The fix bumps `_rev` on every mutation in both phases (matching the pattern `Node.svelte`'s accordion-collapse already used). Covered by `e2e/exclusive-expand.spec.ts` against `/test/exclusive-expand`. (web-treeview is unaffected: its `expandNodes` doesn't support `exclusive`, so there's no off-spine trim path to fix.)
65
+ - **`scrollToPath` reliably scrolls to targets in just-expanded deep branches**: With `isFlatRenderingEnabled` + `isProgressiveRender` (the default), `expandNodes` reveals new rows in rAF-deferred batches sized `initialBatchSize` (default 20) and doubling. Previously, `scrollToPath` queried the DOM after one `tick()` — only the immediate batch was rendered, so any target row past that batch produced a `console.warn("DOM element not found")` and the function returned `false` without scrolling or highlighting. Symptom: in long deep trees, search-result navigation appeared to do nothing for hits in collapsed branches with many siblings. Fix: the non-virtual branch of `scrollToPath` now retries the DOM lookup for up to ~6 additional frames (each `await rAF + tick`) before giving up, matching the retry pattern already used by the virtual-scroll branch's highlight step. Covered by `e2e/search-deep.spec.ts` against `/test/search-deep` (1000 leaves over 3 levels, `initialBatchSize=5`, targets at the 10th sibling position so they fall in the deferred batch). Same fix applied to `@keenmate/web-treeview` (identical progressive-render logic, identical bug).
66
+ - **Esc-cancelling a drag no longer leaves the source node "stuck" selected**: `_onNodeDragStart` schedules a rAF that replaces `highlightedPaths` with the dragged node (OS-convention selection sync so multi-drag visually shows what's moving). Previously, if the user then pressed Esc, the browser fired `dragend` with `dropEffect = 'none'` and the controller still left the dragged node highlighted, applying `.stv__node-content--highlight-bold` (bold + primary color) to the source row even though the user had cancelled. Two-part fix: (1) the rAF now snapshots the prior `highlightedPaths` before mutating, and `_onNodeDragEnd` rolls the highlight back when `dropEffect === 'none'`; (2) `_onNodeDragStart` now also attaches the `dragend` listener directly to the source element instead of relying solely on the `.stv__container` listener — the rAF's `tree.refresh()` detaches the source row from the document, so the subsequent `dragend` couldn't bubble to the container and the handler never ran. Direct-element listeners still fire after detachment. Successful drops behave as before — the new highlight stays. Covered by `e2e/drag-esc-cancel.spec.ts`. (web-treeview is unaffected: it has no OS-convention highlight sync on drag start, and already attaches `dragend` at both `bodyEl` and `document` levels.)
67
+
68
+ ### Added
69
+ - **Default label now wraps in `<span class="stv__node-label">` DOM parity with `@keenmate/web-treeview`**: when no `nodeTemplate` snippet is supplied, the display value is emitted inside a `.stv__node-label` span instead of as bare text inside `.stv__node-content`. Matches web-treeview's default render path so the two packages produce structurally identical rows. The wrapper is a CSS hook only no default styles (intentionally empty rule body). The orphaned `--stv-node-label-font-weight` and `--stv-node-label-margin-right` variables in `variables.css` are removed; both were unreferenced (the class never existed in markup before this release) so no consumer chain breaks. If you were targeting the bare text inside `.stv__node-content` with a CSS rule, switch the selector to `.stv__node-label`.
70
+
71
+ - **`/test/highlight-focus` fixture + `e2e/highlight-focus.spec.ts` (8 tests)**: closes the e2e gap that let the highlight-marker fight ship there was no test asserting on the `--highlighted` / `--focused` marker classes at all. The fixture exposes Highlight-Class and Focus-Class dropdowns over a `selectionMode='multi'`, `clickBehavior='select'` tree. Spec asserts: no `highlightedNodeClass` highlighted row gets the fallback `--highlighted` marker; setting Bold/Glow → the custom class is applied and the marker is **absent** (the anti-fight contract fails against the pre-fix unconditional marker); switching back to none restores the marker and drops the custom class; the `--focused` hook is present on the focused row even with no `focusedNodeClass`; `focusedNodeClass` lands on exactly the focused row; focus is single (moving focus moves both the hook and the custom class, exactly one `--focused` on the page); and highlight + focus classes stack independently on the same row while the marker stays away. Mirrored in `@keenmate/web-treeview` (`test/highlight-focus.html` + `e2e/highlight-focus.spec.ts`, 7 tests — no in-place class-switch test because its diff renderer only re-renders a row on `_rev` bump).
72
+ - **`/test/callbacks` fixture + `e2e/callbacks.spec.ts` (9 tests)**: closes the e2e gap that let the seed-callback bug ship. The existing `/test/drag-drop` fixture covers `allowedDropPositionsMember` (data-field form) but never used `getIsDraggableCallback`, `getAllowedDropPositionsCallback`, or `getIsDropAllowedCallback`, so none of the per-node callback opt-outs had a failing test. The new fixture wires all three callbacks reading from `node.data` and exposes drop state via `data-testid` spans. Spec asserts: `draggable="false"` on a pinned row + no `onNodeDrop` fires when dragging it; drops on a `['child']`-only target snap to child even when aimed at "before"; drops on a `['before','after']` target snap to before/after when aimed at "child"; and drops onto a row with `isDropAllowed: false` don't fire `onNodeDrop` at all. Two `isDraggable` tests and the `isDropAllowed` test all fail against the pre-fix code.
73
+
74
+ ## [5.0.0-rc10] - 2026-06-10
75
+
76
+ ### Changed
77
+ - **CSS custom properties rescoped from `:root` to `.ltree-container`**: All `--ltree-*` declarations now live on the tree's root element instead of the document root. This mirrors the `:host`-scoped pattern from `@keenmate/web-multiselect` / `web-daterangepicker` and is the only way `--base-*` theming actually works at subtree scope. Previously, a wrapper around the tree that set `--base-accent-color: red` had no effect because `--ltree-primary`'s `var(--base-accent-color, ...)` substitution was frozen at `:root`. With the new scope, the substitution recomputes on `.ltree-container` (a descendant of the wrapper), and `--base-*` set on any ancestor flows through correctly. Multiple trees on the same page can now be themed differently via subtree wrappers.
78
+
79
+ **Consumer migration**: setting `--ltree-*` on a wrapper element (e.g., `.my-theme { --ltree-primary: red }`) no longer cascades into the tree, because `.ltree-container` has its own direct rule for every `--ltree-*` token. To theme a subtree, prefer `--base-accent-color` (and the rest of the `--base-*` taxonomy) on the wrapperevery primary-derived tint follows via the existing `color-mix()` chains. To override an `--ltree-*` directly, target `.ltree-container`: `.my-wrap .ltree-container { --ltree-primary: red }`.
80
+
81
+ ### Added
82
+ - **`--ltree-bg` CSS variable**: The tree's `.ltree-container` now paints its own default background (`var(--base-main-bg, light-dark(#ffffff, #1a1a1a))`) so consumers don't need to wrap the tree in a colored container for a visible surface. The dark-mode CSS flips `--ltree-bg` alongside the other surface tokens. Set `--ltree-bg: transparent` to restore the pre-rc10 layered behavior.
83
+ - **Built-in dark mode with belt-and-suspenders coverage**: New `dark-mode.css` partial in an `@layer overrides;` cascade catches all four signals — OS preference via `@media (prefers-color-scheme: dark)`, framework theme classes (`[data-theme="dark"]`, `[data-bs-theme="dark"]`, `.dark`), per-instance opt-in via the new `theme` prop on `<Tree>`, and symmetric `light` selectors so a single tree can be forced back to light on a dark page. Every `--ltree-*` color variable's fallback now uses `light-dark(<light>, <dark>)` so consumers who declare `html { color-scheme: light dark }` get OS-aware behavior automatically with zero JavaScript.
84
+ - **`theme` prop on `<Tree>`**: `'dark' | 'light' | null | undefined`. Forwards to the root `.ltree-container` as `data-theme`, where the stylesheet's per-instance selectors (`.ltree-container[data-theme="dark"]`) take over. Leave `undefined` to inherit from the page (OS, framework class, etc.).
85
+ - **`--ltree-elevated-bg`, `--tree-ghost-shadow` CSS variables**: `elevated-bg` reads through the canonical `--base-elevated-bg` chain for surfaces above the main background (currently feeds the context-menu chain); `tree-ghost-shadow` replaces a hardcoded `rgba(0,0,0,0.3)` literal in the touch drag ghost so consumers can recolor or remove the shadow.
86
+ - **`getIsDropAllowedCallback` prop + `getNodeIsDropAllowed(node)` method**: Callback variant for the existing `isDropAllowedMember` data-field prop, matching the pattern rc09 introduced for `getIsExpandedCallback` / `getIsSelectableCallback` / `getIsSelectedCallback`. Seeded at `insertArray` time. Precedence: callback > member > default. Additionally, `getIsDraggableCallback` is now actually applied during the seed walk (the prop existed in rc09 but inert at insert-time — only consumed lazily in some paths). Precedence: callback > member > default for both.
87
+
88
+ ### Changed
89
+ - **CSS file layout aligned with the Bliss web-component guidelines**: All `_<partial>.css` renamed to `<partial>.css` (the underscore was a legacy SASS convention and these are plain CSS modules). `main.css` now declares `@layer variables, component, overrides;` and wraps every `@import` in `layer(...)` — consumers' unlayered overrides automatically beat every rule in the library, no `!important` needed. New canonical Tier-2 stubs (`controls.css`, `floating.css`, `animations.css`) added even where empty, so the file set is identical across the KeenMate component suite. `@keyframes bounce` and `@keyframes ltree-spin` moved into `animations.css`.
90
+ - **Loading overlay background adapts to theme**: `--ltree-loading-bg` no longer hardcodes `rgba(255, 255, 255, 0.8)`; it now composes 80% of `--base-main-bg` against the page so the semi-transparent overlay reads correctly on both light and dark surfaces.
91
+ - **`component-variables.manifest.json` extended**: `base-elevated-bg` added to the `baseVariables` list; `ltree-elevated-bg` and `tree-ghost-shadow` added to `componentVariables`. The manifest now stays in sync with the new variable surface.
92
+
93
+ ### Fixed
94
+ - **Virtual scroll stuck at bottom after filter shrinks the tree**: When the user scrolled down and then typed a search filter, `visibleFlatNodes` collapsed and `vsTotalHeight` shrank below the previous scroll position. The browser silently clamped the container's actual `scrollTop`, but the controller's `vsScrollTop` state kept its stale large value — so `vsStartIndex` fell out of range, `flatNodesToRender` was empty, and the scrollbar appeared frozen near the bottom with no content rendered. A new `$effect` now reacts to `vsTotalHeight` and clamps both `vsScrollTop` and the container's `scrollTop` to the new max whenever content shrinks below the current scroll position.
95
+ - **Double-click expand silently broken in `clickBehavior='select'` mode**: The browser's native `dblclick` event never fired because the first click triggered focus → `_setFocusedNode` bumped `node._rev` → the flat-mode `{#each}` destroyed and recreated the row, so the second click landed on a different DOM element and the browser refused to synthesize a `dblclick`. The controller now detects double-clicks manually (`_lastSelectClickPath` / `_lastSelectClickTime`, 400 ms threshold) so a rapid second click on the same node toggles its expand state regardless of re-render. Node-level `_onNodeDblClicked` removed (it could double-toggle if the browser ever did fire `dblclick`).
96
+
97
+ ## [5.0.0-rc09] - 2026-06-07
98
+
99
+ ### Added
100
+ - **`selectionMode: 'single' | 'multi'` prop** (default `'single'`): Decides how highlight cardinality and modifier keys behave. In `'single'`, plain click sets highlight to one node and Ctrl/Shift+click degrade to plain click; Shift+Arrow and Enter are no-ops. In `'multi'`, Ctrl+click toggles, Shift+click extends a range from the focused node, Shift+Arrow extends one step, and Enter toggles highlight on the focused node. Matches the [selection-highlight-model.md](selection-highlight-model.md) decision record.
101
+ - **Implicit highlight selection mirroring when `shouldShowCheckboxes` is false**: Every change to `highlightedPaths` is mirrored into `selectedPaths`, and `onSelectionChange` fires alongside `onHighlightChange`. When checkboxes are visible the two sets stay decoupled — highlight is the cursor / range cursor, checkbox state is the form selection. No new toggle prop; the rule follows `shouldShowCheckboxes` directly. Programmatic API (`highlightNode`, `highlightNodes`, `clearHighlight`) mirrors too and respects `{ silent: true }`.
102
+ - **`shouldClickToggleCheckbox` prop**: Boolean (default `false`). When `true` AND `shouldShowCheckboxes` is on AND the node is selectable, a plain click on the node label runs the checkbox-toggle path instead of focusing/highlighting `focusedNode` and `highlightedPaths` stay untouched. Expand-on-click still fires when `clickBehavior` is `'expand'` or `'expand-and-focus'`. Modified clicks (Ctrl/Shift) fall through to the normal multi-highlight path so range/toggle selection still works.
103
+ - **`getIsExpandedCallback`, `getIsSelectableCallback`, `getIsSelectedCallback` props**: Callback variants for the existing `isExpandedMember` / `isSelectableMember` / `isSelectedMember` data-field props, matching the same pattern as `getIsDraggableCallback` and `getIsCollapsibleCallback`. All three are seed-only — invoked once per node during `insertArray` — so subsequent user mutations (checkbox clicks, expand button) aren't overridden by the callback. Precedence: callback > member > default. The post-insert `selectedPaths` seeding walk now also triggers when `getIsSelectedCallback` is set, not just when `isSelectedMember` is set.
104
+ - **Lucide SVG toggle icons**: All built-in `ltree-icon-*` classes (default chevron, alt filled-triangle, plus/minus, arrow, leaf) now render Lucide SVGs via `mask-image` + `background-color: currentColor` instead of UTF-8 glyph characters (`▶ ▼ ⯈ ⯆ + − → ↓ •`). Icons inherit text color automatically, render cleanly at any size, and don't depend on font fallback. Zero added DOM nodes — still a single `::before` pseudo-element per toggle.
105
+ - **`--ltree-toggle-icon-size` / `--ltree-toggle-icon-width` / `--ltree-toggle-icon-color` CSS variables**: Runtime overrides for the toggle column's icon size (default `16px`, was `12px`), column width (`20px`), and color (`#6c757d`). The SCSS variables (`$tree-toggle-icon-*`) still drive the build-time defaults; the CSS vars are bridged via `var(--…, #{$…})` so users can scale icons live without recompiling SCSS.
106
+ - **Toggle icon live demo on `/examples/theming`**: A new card with radio controls for the four built-in icon sets and the `toggleIconMode` (`rotate` / `swap`) prop, wired to a live `<Tree>`. A code snippet under the controls reflects the current selection so users can copy-paste the props they see.
107
+ - **Full CSS-variable theming surface (`--ltree-*` with `--base-*` chain)**: Every styling value is exposed as a CSS custom property at `:root`. The resolution chain is end-user override → `--base-*` token (shared with other `@keenmate/*` web components) → hardcoded default. Roughly fifty new variables covering typography, node layout, toggle icon, checkbox, selection / highlight / drag states, drop zones (pastel + glow modes), context menu, debug info, scroll highlight, and loading overlay. Pattern matches `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`.
108
+
109
+ ### Changed
110
+ - **`!isSelectable` now also gates highlight (and therefore the mirrored selection in no-checkbox mode), not just the checkbox render**: Click handlers and Shift+arrow range expansion skip non-selectable nodes when building the highlight set. Focus and arrow navigation are NOT gated — the focused row can still land on `!isSelectable` nodes so consumers can show external detail panels (sidebars, breadcrumbs, etc.).
111
+ - **Right-click no longer moves focus or highlight**: `_onNodeRightClicked` only opens the context menu at the right-clicked node. Consumers reading the focused/highlighted set inside `getContextMenuItemsCallback` should now use the `node` argument that the callback receives, since right-click no longer rewrites `focusedNode` / `highlightedPaths`. The selected-nodes argument continues to reflect whatever multi-highlight the user had before opening the menu.
112
+ - **Drag and drop now moves the whole highlight set when the dragged node is in `highlightedPaths`** (same-tree, `operation: 'move'`, `shouldAutoHandleMove: true`): the algorithm picks the **top-level selected** subset — highlighted paths whose nearest highlighted ancestor is NOT in the set — and moves each one's full subtree. Selected descendants of a top-level node are absorbed (they ride along inside their ancestor's subtree, not separately extracted). Cross-tree multi-drag still moves the single dragged node (the cross-tree payload only carries one node).
113
+ - **Multi-drag now chains subsequent moves 'after' the previously moved subtree** (was: subsequent moves landed as children of `dropNode` regardless of the requested position). Dropping selection {A, B, C} 'after D' now produces `[D, A, B, C]` as siblings (previously: `[D]` at root with A/B/C as children of D). 'before D' produces `[A, B, C, D]`. 'child of D' produces D with children `[A, B, C]`. Source order is preserved within the moved set.
114
+ - **Dragging a non-highlighted node now replaces the highlight with that single node before the drag begins**: Matches the Windows Explorer / macOS Finder convention where mousedown on an unselected item selects it. Previously, the prior highlight stayed visible (the `click` event never fired because the user dragged instead of releasing) while the drag silently carried only the single grabbed node — making it impossible to tell what would actually move. The replacement runs in `_onNodeDragStart` and fires `onHighlightChange` / mirrors to `selectedPaths` like a plain click. Skipped if the dragged node is already in the highlight set (multi-drag) or is not selectable.
115
+ - **Keyboard: Enter toggles focused highlight in multi mode (was: always expand/collapse)**. In single mode Enter is now a no-op (falls through to the host). Space falls back to expand/collapse when no checkboxes are shown, but toggles the focused node's checkbox when `shouldShowCheckboxes` is on.
116
+ - **Ctrl/Shift+click on a node no longer toggles expand/collapse**: The modifier-click gesture is reserved for highlight management (toggle / range). Previously, in the default `clickBehavior='expand-and-focus'` mode, modified clicks both updated the highlight set AND expanded/collapsed the row, which made it impossible to multi-select folders without their contents flickering open. Plain click still expands as before. Matches OS file explorer conventions.
117
+ - **Stylesheet ported from SCSS to pure CSS**: `src/lib/styles/main.scss` (1095 lines, one file) split into eleven partials under `src/lib/styles/` (`_variables.css`, `_base.css`, `_node.css`, `_toggle-icons.css`, `_checkbox.css`, `_states.css`, `_drag-drop.css`, `_drop-zones.css`, `_context-menu.css`, `_debug.css`, `_loading.css`) plus a `main.css` entry point that chains them via CSS `@import`. SCSS `@mixin` constructs replaced with shared base-class boilerplate. Build now uses `lightningcss-cli` (one devDep) to bundle the imports into `dist/styles.css`; `sass` removed from devDependencies. Pattern matches the file layout in `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`.
118
+ - **`--ltree-rem` base sizing unit**: New CSS variable (default `10px`) drives every dimension via `calc(N * var(--ltree-rem))` font sizes, paddings, margins, widths, radii, spinner size, etc. Set `--ltree-rem` once to scale the whole component proportionally (e.g., `--ltree-rem: 12px` for 20% larger). Set it to `1rem` to scale with document font-size (Pure Admin pattern). Default rendering is visually identical to the previous absolute-px output. Hairline borders (`1.5px` checkbox, `3px` glow) and animation timings stay absolute. Matches the `--ms-rem` / `--drp-rem` pattern in the sibling packages.
119
+ - **`component-variables.manifest.json` published at the package root**: Mirrors the schema used by `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`. Lists every `--base-*` token the component reads (with `required: true/false` flags) and every `--ltree-*` variable the component publishes (categorized: `color`, `sizing`, `spacing`, `border`, `surface`, `typography`, `state`, `drop-zone`, `context-menu`, `icon`, `animation`). Intended for IDE auto-completion, Storybook integration, and theme-designer tooling. Exported via the `./component-variables.manifest.json` package entry.
120
+ - **Hover background follows `--ltree-primary` by default**: `--ltree-node-hover-bg` default changed from solid `#f8f9fa` to `color-mix(in srgb, var(--ltree-primary) 8%, transparent)`. Themes that set only `--ltree-primary` now get a matching hover tint automatically; the explicit `--base-hover-bg` token override still wins over the derived default. Visually similar to the previous gray at default primary `#0d6efd`.
121
+ - **`--ltree-checkbox-focus-ring` split into tunable parts**: `--ltree-checkbox-focus-ring-width` (default `2px`) and `--ltree-checkbox-focus-ring-color` (default `color-mix(--ltree-primary 25%)`) — the composed `--ltree-checkbox-focus-ring` shorthand still works and now references both. Set just one part to widen the ring or recolor it without redeclaring the full box-shadow.
122
+
123
+ ### Breaking
124
+ - **Default `selectionMode = 'single'` changes click semantics for existing multi-select users**: Previously Ctrl/Shift+click always built a multi-highlight regardless of any prop. Now the default is single-select — to keep the old behaviour, opt in with `selectionMode='multi'`. Plain click + Arrow nav are unchanged.
125
+ - **`selectedPaths` becomes populated in no-checkbox trees** because highlight is mirrored into selection. Consumers reading `selectedPaths` from no-checkbox `<Tree>` instances will now see values that previously stayed empty. To keep the old empty-`selectedPaths` behaviour, render checkboxes (and ignore them with CSS) or keep your own derived "real selection" set.
126
+ - **`lastHighlightedPath` (private), `isHighlightAnchor` (public on `LTreeNode`), `.ltree-highlight-anchor` (CSS class), `--ltree-highlight-anchor-width` / `--ltree-highlight-anchor-color` (CSS vars), and the matching `component-variables.manifest.json` entries removed**: The focused node now serves as the anchor for Shift+range operations. Internal Shift+Arrow uses a hidden `_shiftCursor` field — consumers never see it.
127
+ - **`./styles.scss` package export removed.** Consumers using `import '@keenmate/svelte-treeview/styles.scss'` must switch to `import '@keenmate/svelte-treeview/styles.css'` (the canonical export, unchanged). For per-section theming, individual partials are now available under `./styles/_<section>.css`.
128
+ - **`--ltree-primary-rgb` / `--ltree-success-rgb` / `--ltree-danger-rgb` removed** (along with `--base-accent-color-rgb` / `--base-success-color-rgb` / `--base-danger-color-rgb` from the `--base-*` chain). Tinted variants are now derived from the parent color via `color-mix(in srgb, var(--ltree-x) N%, transparent)` — so setting `--ltree-primary: red` automatically tints the multi-select background, dragover background, drop placeholder, focus ring, scroll highlight, debug panel, and danger context-menu hover without needing a companion `-rgb` variable. Matches the `@keenmate/web-multiselect` pattern. Minimum browser baseline: Chrome 111 / Safari 16.4 / Firefox 113 (all from March-May 2023).
129
+ - **Indent scaling semantics**: `--ltree-node-indent-per-level` was previously `0.5rem` (document-relative — followed `html { font-size }`). It is now `calc(0.8 * var(--ltree-rem))` — same visual default (8 px), but scales with `--ltree-rem` instead of document `rem`. To restore the old document-relative behavior, set `--ltree-rem: 1rem`.
130
+ - **SCSS variable overrides (`$tree-*`, `$drop-*`, `$primary-*`) no longer supported.** Migrate to CSS custom properties at runtime: `--ltree-node-font-size: 16px` instead of `@use '@keenmate/svelte-treeview/styles.scss' with ($tree-node-font-size: 16px)`. The full variable surface is documented at `/examples/theming`.
131
+
132
+ ### Fixed
133
+ - **Stale `dragOverNodeClass` highlights piling up across rows during drag**: Each `Node` tracked its own local `isDraggedOver` flag, toggled by the row's own `ondragover` / `ondragleave`. HTML5 `dragleave` is unreliable when crossing between sibling rows fast (it can fire with cursor coordinates still inside the leaving row's rect, or skip altogether when `dragenter` on the next row beats it) so the dashed-green dragover highlight got stuck on rows the cursor had already left, leaking one stale highlight per missed leave. The class is now applied via direct `classList.add/remove` on the previous-and-new DOM nodes from a controller `$effect` that watches the centralized `hoveredNodeForDrop`, matching the existing touch-drag pattern. This guarantees exactly one highlighted row at a time and the cost stays O(1) per node-crossing regardless of tree size — no per-Node prop propagation. The dead per-node `isDraggedOver` state was removed.
134
+ - **`ltree-selected-brackets` invisible when checkboxes are enabled**: The adjacent-sibling rule that hides the `❯ ❮` pseudo-elements (because the checkbox already signals selection) left the class with zero visual effect — highlighted nodes looked identical to unhighlighted ones. The suppressed selector now also applies `font-weight: bold` and `color: var(--ltree-primary)` as a fallback so the highlight is always visible.
135
+ - **Independent checkbox mode auto-checking parents**: In `checkboxMode="independent"`, the post-toggle ancestor walk still ran and propagated `isSelected = true` up to parents when all descendants happened to be selected — making parent checkboxes auto-check themselves despite the "independent" promise. The walk is now gated to cascade mode only; independent mode leaves parents fully alone.
136
+ - **`Node.svelte` stale icon/highlight classes after `tree.update()`**: `expandIconClass`, `collapseIconClass`, `leafIconClass`, `highlightedNodeClass`, `focusedNodeClass`, `dragOverNodeClass`, and `isCopyAllowed` were destructured from the shared `NodeConfig` `$state` proxy as primitive snapshots, so once Node mounted they never updated. Replaced with `$derived(config.x)` so runtime updates (including the new icon-set radios in the theming demo) actually propagate.
137
+
138
+ ## [5.0.0-rc08] - 2026-05-27
139
+
140
+ ### Added
141
+ - **`{ silent: true }` option on highlight/selection methods**: `highlightNode`, `highlightNodes`, `clearHighlight`, `deselectAll` (and deprecated `selectNode`/`selectNodes`) now accept `{ silent: true }` to update state without firing `onNodeClick` / `onHighlightChange` / `onSelectionChange`. Intended for URL-restore flows (deep links loading form data from query params) where firing the change callback would re-trigger form loaders and clobber the data the URL just supplied. Silent mode also skips focusing the tree container so it doesn't steal focus from whatever the user is interacting with.
142
+ - **Array variants on expand/collapse methods**: `expandNodes`, `collapseNodes`, `expandAll`, and `collapseAll` now accept `string | string[]`. Single emit per call regardless of array length.
143
+ - **`{ exclusive: true }` option on `expandNodes` and `expandAll`**: Opens the target path(s) and collapses anything currently expanded that isn't on the union-of-spines (and, for `expandAll`, not under a target subtree). Equivalent to `collapseAll() + expandNodes(path)` but in a single pass with one emit — downstream listeners (transition animations, URL sync, virtualized renderers) don't see the intermediate fully-collapsed state. Non-collapsible nodes (`isCollapsible === false`) are never touched.
144
+ - **`{ noEmit: true }` option on all four expand/collapse methods**: Skips the change emit, enabling batching multiple operations and emitting once at the end via `tree.refresh()`.
145
+ - **`/examples/silent-highlight` demo page**: URL-restore scenario with loud vs. silent toggle showing how form data is preserved in silent mode.
146
+ - **`/examples/expand-collapse` demo page**: Demonstrates array variants and exclusive focus mode.
147
+
148
+ ## [5.0.0-rc07] - 2026-05-23
149
+
150
+ ### Added
151
+ - **`isSelectedMember` prop**: Data field name that seeds `node.isSelected` at `insertArray` time. The controller walks the tree after insert and pre-populates the bindable `selectedPaths` Set with every path where the field is truthy — independent of `isSelectableMember`, so non-selectable nodes can still ship checked.
152
+ - **`isSelectableMember` prop now publicly wired**: Previously declared on the core types but not exposed through `Tree.svelte`'s prop/update interface. Now end-to-end usable. Controls whether a node renders a checkbox and carries the `ltree-clickable` class.
153
+
154
+ ### Fixed
155
+ - **Filter race with async indexing**: A `bind:searchText` change that landed while the FlexSearch index was still being built (via `requestIdleCallback` batches) saw an empty index, hid the entire tree, and never recovered no mechanism re-applied the filter once indexing completed. `filterNodes` now remembers the active query and the indexer's `onComplete` callback re-runs it, so the visible filter catches up automatically regardless of `indexerBatchSize`, dataset size, or how fast the user types.
156
+
157
+ ## [5.0.0-rc06] - 2026-03-31
158
+
159
+ ### Added
160
+ - **`shouldShowCheckboxes` prop**: Renders a checkbox before each selectable node with custom styling and indeterminate support.
161
+ - **`checkboxMode` prop** (`'independent'` | `'cascade'`): Controls whether checking a parent cascades to all descendants. Indeterminate state shown when partial.
162
+ - **`beforeCheckboxToggleCallback` interceptor**: Cancel or override checkbox toggles.
163
+ - **Three-level selection model**: Separated into `focusedNode` (single node, click/arrows), `highlightedPaths` (multi-select, Ctrl/Shift+click), and `selectedPaths` (checkbox data state). Highlight and checkbox state are independent build a highlight selection, then check/uncheck all highlighted nodes with one checkbox click.
164
+ - **`onHighlightChange` event**: Fires when highlighted paths change (Ctrl+click, Shift+click, etc.).
165
+ - **Bulk checkbox via highlight**: When multiple nodes are highlighted and a checkbox in the highlight is clicked, all highlighted nodes toggle together.
166
+ - **Shift+Arrow/Home/End keyboard highlight**: Shift+ArrowDown/Up extends highlight range by one sibling, Shift+Home/End extends to first/last visible node.
167
+ - **PageUp/PageDown navigation**: Jumps 10 visible nodes forward/back. Shift+PageUp/PageDown extends highlight by 10 nodes.
168
+ - **`ltree-selected-highlight` CSS class**: Explorer-style blue background highlight. Customizable via `--ltree-highlight-bg` and `--ltree-highlight-color`.
169
+ - **Interaction example page** (`/examples/interaction`): Interactive demos for click behavior, checkboxes, multi-select, and keyboard navigation. All settings persisted to localStorage.
170
+
171
+ ### Fixed
172
+ - **Keyboard navigation not working after node click**: Clicking a node now auto-focuses the tree container, so arrow keys work immediately without having to click the container separately.
173
+ - **Svelte proxy equality warning on checkbox toggle**: `_setFocusedNode` now compares by path instead of object identity to avoid `state_proxy_equality_mismatch`.
174
+
175
+ ### Breaking
176
+ - **`selectedNode` `focusedNode`**: Renamed prop and bindable. The single focused node (last clicked / arrow-keyed to).
177
+ - **`selectedPaths` repurposed**: Now represents checkbox-only data state. For click/highlight multi-select, use `highlightedPaths`.
178
+ - **`highlightedPaths` (new)**: Replaces old `selectedPaths` for Ctrl+click / Shift+click UI highlight.
179
+ - **`selectedNodeClass` `highlightedNodeClass`**: CSS class applied to highlighted nodes.
180
+ - **`focusedNodeClass` (new)**: CSS class applied to the single focused node.
181
+ - **`onSelectionChange` repurposed**: Now fires on checkbox selection changes only. Use `onHighlightChange` for highlight changes.
182
+ - **`selectNode()` / `selectNodes()` deprecated**: Use `highlightNode()` / `highlightNodes()` instead.
183
+
184
+ ## [5.0.0-rc05] - 2026-03-26
185
+
186
+ ### Added
187
+ - **`clickBehavior` prop** (`'select'` | `'expand'` | `'expand-and-focus'`, default `'expand-and-focus'`): Controls what happens on node click. Replaces the boolean `shouldToggleOnNodeClick` prop. Matches canvas package's `ClickBehavior` type.
188
+
189
+ ### Breaking
190
+ - **`shouldToggleOnNodeClick` removed**: Replace `shouldToggleOnNodeClick={true}` with `clickBehavior="expand-and-focus"` (default) and `shouldToggleOnNodeClick={false}` with `clickBehavior="select"`.
191
+
192
+ ## [5.0.0-rc04] - 2026-03-12
193
+
194
+ ### Added
195
+ - **`TreeNavigation<T>` interface**: Pluggable keyboard navigation per renderer. Each renderer (HTML, Canvas) provides its own spatial implementation. Users can override individual methods via `TreeNavigationOverrides<T>`.
196
+ - **Bulk subtree operations**: `insertBranch(parentPath, nodes)`, `replaceBranch(path, nodes)`, `deleteBranch(path)` on TreeController — perform subtree-level add/replace/remove with a single tree emission.
197
+ - **Clipboard API on TreeController**: `copyNodes(paths?)`, `cutNodes(paths?)`, `pasteNodes(targetPath, position, options?)`, `cancelCut()`, `hasClipboard()`, `getClipboardOperation()`. Cut nodes are dimmed via `cutPaths` set. Supports `shouldAutoHandlePaste` (default) and manual mode for server-driven workflows.
198
+ - **Branch-operations example** (`/examples/branch-operations`): Server-simulated cut/paste workflow demonstrating `deleteBranch` + `insertBranch` with clipboard integration.
199
+
200
+ ### Fixed
201
+ - **`.ltree-container` missing `outline: none`**: Container div now suppresses browser focus outline.
202
+
203
+ ## [5.0.0-rc03] - 2026-03-08
204
+
205
+ ### Added
206
+ - **Multi-select**: Ctrl+click (Cmd on Mac) toggles individual nodes in/out of selection. Shift+click selects a range from the last-clicked anchor to the current node. Plain click clears selection and selects one node.
207
+ - **`selectedPaths` bindable prop**: `Set<string>` of all selected node paths. Two-way binding for external control.
208
+ - **`rangeSelectionMode` prop**: `'visual'` (default) selects only visible/expanded nodes between anchor and target; `'logical'` selects all nodes in depth-first tree order including collapsed children.
209
+ - **`onSelectionChanged` event**: `(paths: Set<string>, nodes: LTreeNode<T>[]) => void` fires when selection changes.
210
+ - **`contextMenuCallback` 3rd parameter**: Now receives `selectedNodes?: LTreeNode<T>[]` — enables selection-aware context menus.
211
+ - **Public multi-select API on TreeController**: `selectNode(path, mode)`, `selectNodes(paths)`, `deselectAll()`, `getSelectedNodes()`, `isNodeSelected(path)`.
212
+ - **`SelectionModifiers` type export**: `{ ctrl: boolean; shift: boolean }` for modifier-aware click handling.
213
+ - **`.ltree-multi-selected` CSS class**: Styling for nodes in a multi-selection.
214
+ - **Unified Context Menu types** (`ContextMenuItem`, `ContextMenuDivider`, `ContextMenuEntry`): Shared type system across svelte-treeview and canvas-tree. Breaking change from old API: `title` → `label`, `callback` → `onclick`, `isDivider` flag replaced by separate `ContextMenuDivider` type with `divider: true` discriminator.
215
+ - **Named dividers**: `{ divider: true, label: 'Section' }` renders as `──── Section ────`
216
+ - **Keyboard shortcuts**: `shortcut` field renders right-aligned hint and activates on keypress when menu is open
217
+ - **Submenus**: `children: ContextMenuEntry[]` opens nested menu on hover
218
+ - **Visibility control**: `isVisible: false` hides items in callback approach (snippet approach uses `{#if}`)
219
+ - **Flexible styling**: `className` replaces dedicated `danger` boolean use `className="danger"` or any custom class
220
+ - **Async onclick**: `onclick` supports `Promise<void>` return with try/catch error handling
221
+ - **`ContextMenuItemC` / `ContextMenuDividerC` Svelte components**: Declarative context menu building inside the `contextMenu` snippet. Supports nested children slot for submenus.
222
+ - **Context menu keyboard shortcuts**: When context menu is open, pressing a shortcut key (e.g. `C`, `Shift+N`, `F2`) triggers the matching item's `onclick`. Supports modifier keys (Ctrl, Shift, Alt). Escape closes the menu.
223
+ - **`isAccordionExpand` prop**: Per-parent accordion behavior — expanding a node automatically collapses its siblings (children of the same parent). Opt-in via `isAccordionExpand={true}`. Respects `isCollapsibleMember`/`getIsCollapsibleCallback` non-collapsible siblings are not force-collapsed. Programmatic methods (`expandAll`, `expandNodes`) remain unconstrained.
224
+ - **`toggleIconMode` prop** (`'rotate'` | `'swap'`, default `'rotate'`): Controls how expand/collapse icons behave. `'rotate'` mode (new default) always uses `expandIconClass` (▶) and rotates it 90° via CSS when expanded — smooth animated transition. `'swap'` mode switches between `expandIconClass` (▶) and `collapseIconClass` (▼) classes (previous behavior). Ported from `@keenmate/web-treeview`.
225
+
226
+ ### Fixed
227
+ - **Expand/collapse icon not updating in flat rendering mode**: `toggleExpanded()` now bumps `node._rev` so the flat-mode `{#each}` key changes and Svelte re-renders the node with the correct icon state.
228
+ - **`.expanded` CSS transform was a no-op**: `.ltree-toggle-icon.expanded` had `rotate(0deg)` (no effect). Fixed to `rotate(90deg)` for the new `rotate` toggle icon mode.
229
+
230
+ ## [5.0.0-rc02] - 2026-03-05
231
+
232
+ ### Architecture (Breaking)
233
+ - **Core/Renderer split**: Tree logic (`TreeController`) fully separated from rendering. `Tree.svelte` is now a thin wrapper delegating to `TreeController`. New `TreeProvider` component enables custom renderers (Canvas, WebGL, SVG) on the same core.
234
+ - **Drop position naming**: `'above'`/`'below'` renamed to `'before'`/`'after'` throughout (`DropPosition` type, CSS classes, events). `'child'` unchanged.
235
+ - **Canvas rendering extracted**: Canvas-based rendering (`CanvasTree`, layouts, themes) moved to separate package `@keenmate/svelte-treeview-canvas`.
236
+
237
+ ### Added
238
+ - **Render mode switch for examples**: Shared `RenderModeSwitch` component across all example pages. Three-button segmented control (Recursive / Progressive / Virtual) with localStorage persistence. All example `<Tree>` instances receive mode props via `{...getTreeProps()}`.
239
+ - **Virtual scroll mode** (`isVirtualScrollEnabled`, `virtualRowHeight`, `virtualOverscan`, `virtualContainerHeight`): Only renders visible nodes + overscan rows in a fixed-height scrollable container. Enables smooth scrolling through trees with 50,000+ nodes while maintaining ~50 DOM nodes. rAF-throttled scroll handler, auto-measures row height if not explicitly set.
240
+ - **Virtual scroll `scrollToPath`**: Index-based scrolling that centers the target node in the viewport, waits for rAF-throttled scroll + re-render, applies highlight with retry logic.
241
+ - **Search navigation UX**: Filter/search mode toggle on the search example page. Filter mode hides non-matching nodes, search mode keeps tree visible and navigates to highlighted results. Includes result counter, prev/next chevron buttons, Enter/Shift+Enter keyboard navigation (round-robin), Escape to clear.
242
+ - **CSS zone auto-expand**: Floating drop zones auto-expand when positions are hidden (via `allowedDropPositions`). Uses `:not(:has())` CSS rules for "around", "above", "below" layouts.
243
+ - **Unified flat rendering indentation**: Both flat and recursive modes use `--tree-node-indent-per-level` CSS variable. Added `flatGap` logic for parent-to-first-child gap matching.
244
+ - **`overscroll-behavior: contain`** on `.ltree-virtual-scroll` container.
245
+ - **`isCollapsibleMember` / `getIsCollapsibleCallback`**: Per-node collapsibility control.
246
+ - **`getIsDraggableCallback`**: Dynamic per-node draggability.
247
+ - **`applyChanges()` batch method**: Apply multiple tree edits in a single operation.
248
+ - **`_rev` change tracking** on nodes for efficient keyed rendering.
249
+
250
+ ### Fixed
251
+ - **Floating drop zones clipped by scrollable containers**: Moved floating drop zone rendering from `Node.svelte` (absolute positioning inside each node) to `Tree.svelte` (fixed positioning at tree level with `z-index: 10000`). Zones now escape `overflow: hidden` containers. Handler methods (`isFloatingPositionAllowed`, `handleFloatingZoneDragOver`, `handleFloatingZoneDragLeave`, `handleFloatingZoneDrop`) added to `TreeController`.
252
+ - **Glow and floating drop zones showing simultaneously**: In flat rendering mode, `dropZoneMode` destructured from `NodeConfig` context was a stale snapshot (nodes aren't recreated). Changed to `$derived(config.dropZoneMode)` so it reads through the reactive proxy on each evaluation.
253
+ - **"After" drop position placing node at end instead of between siblings**: `moveNode` and `addNode` converted root nodes' `parentPath` from `''` to `null` via `|| null`, while `insertArray` used `getParentPath()` which returns `''`. This mismatch caused `sortCallback` to skip the `sortOrder` comparison (`'' !== null` → entered parentPath branch → returned 0), preserving insertion order instead of respecting sort order.
254
+ - **Empty tree drop placeholder ignoring `dragDropMode`**: `handleEmptyTreeDragOver/Drop/TouchEnd` now check `dragDropMode !== 'none'` before activating the drop placeholder.
255
+ - **`treeId` not synced on prop changes**: Added `$effect` in `Tree.svelte` to sync `treeId` prop to controller after mount.
256
+ - **`dropZoneStart` not used in glow mode**: `calculateGlowPosition` now uses `dropZoneStart` to compute the child zone threshold instead of hardcoded `width/2`.
257
+ - **Sort order mismatch between recursive and flat modes**: `visibleFlatNodes` applied `sortCallback` during traversal while recursive mode relied on insertion-order `Object.values()`. Removed runtime sort from flat traversal to match recursive mode — both now use insertion-time sorting from `insertArray()`.
258
+ - **Critical `$state()` vs `$state.raw()` performance regression**: TreeController's `data` property deep-proxied user data, causing **5,500x slowdown** with 8,000+ nodes. Changed `data`, `selectedNode`, `insertResult`, `contextMenuNode`, `hoveredNodeForDrop`, `touchDragState`, `flatRenderedIds`, `flatRenderQueue` to `$state.raw()`.
259
+
260
+ ### Changed
261
+ - **Custom Layout Example Explicit Drop Zones**: Replaced invisible spatial detection with visible drop zone pills (Before / After / Child) on dendrograms.
262
+
263
+ ## [4.7.2] - 2026-02-17
264
+
265
+ ### Fixed
266
+ - **`bodyClass` prop not working** ([#24](https://github.com/keenmate/svelte-treeview/issues/24)): `class:bodyClass` was toggling a literal CSS class named `"bodyClass"` instead of applying the user's custom class value. Changed to `class={bodyClass}`.
267
+
268
+ ## [4.7.1] - 2026-02-17
269
+
270
+ ### Changed
271
+ - **Drag and Drop Disabled by Default**: `dragDropMode` now defaults to `'none'` instead of `'both'`
272
+ - Most trees are read-only, so this is a safer default
273
+ - To enable drag and drop, explicitly set `dragDropMode="both"` (or `"self"` / `"cross"`)
274
+
275
+ ### Fixed
276
+ - Removed development `console.log` statements from `Tree.svelte` and `ltree.svelte.ts`
277
+
278
+ ## [4.7.0] - 2026-02-11
279
+
280
+ ### Added
281
+ - **Per-Node Drop Position Restrictions**: New feature to control which drop positions are allowed per node
282
+ - `allowedDropPositionsMember` - Property name mapping for static data (e.g., from server)
283
+ - `getAllowedDropPositionsCallback` - Callback for dynamic position logic based on node type
284
+ - `DropPosition` type exported: `'above' | 'below' | 'child'`
285
+ - Example use cases:
286
+ - Trash folder: `['child']` only (can drop INTO, not above/below)
287
+ - Files: `['above', 'below']` only (can't drop INTO a file)
288
+ - Folders: `undefined` or `[]` (all positions allowed - default)
289
+ - Works with both glow mode (snaps to nearest allowed position) and floating mode (only shows allowed zones)
290
+ - Backwards compatible: `undefined`/empty array = all positions allowed
291
+
292
+ ### Example Usage
293
+ ```typescript
294
+ // Callback approach (dynamic logic)
295
+ function getAllowedDropPositionsCallback(node: LTreeNode<T>): DropPosition[] | null {
296
+ if (node.data?.type === 'file') return ['above', 'below'];
297
+ if (node.data?.type === 'trash') return ['child'];
298
+ return undefined; // all positions allowed
299
+ }
300
+
301
+ // Member approach (server data)
302
+ const data = [
303
+ { path: '1', name: 'Trash', allowedDropPositions: ['child'] },
304
+ { path: '2', name: 'Document.pdf', allowedDropPositions: ['above', 'below'] },
305
+ { path: '3', name: 'Projects' }, // all positions (default)
306
+ ];
307
+ ```
308
+
309
+ ## [4.6.0] - 2026-02-11
310
+
311
+ ### Added
312
+ - **Flat Rendering Mode**: New `isFlatRenderingEnabled` prop (default: `true`) for significantly faster rendering
313
+ - Renders all visible nodes in a single `{#each}` loop instead of recursive components
314
+ - Initial render ~12x faster (300ms 25ms for 5500 nodes)
315
+ - Progressive rendering batches initial load to prevent UI freeze
316
+ - Documentation: `docs/FLAT_MODE_PERFORMANCE.md`
317
+ - **Context-Based Node Configuration**: Moved stable callbacks and config to Svelte context
318
+ - `NodeCallbacks<T>` interface for all event handlers (click, drag, drop, touch)
319
+ - `NodeConfig` interface for stable configuration (icons, classes, drop zone settings)
320
+ - Eliminates inline arrow function re-renders (5500 nodes no longer re-evaluate on array changes)
321
+ - Exported types: `NodeCallbacks`, `NodeConfig` from package index
322
+ - **Async beforeDropCallback**: `beforeDropCallback` now supports async/Promise return values
323
+ - Enables showing confirmation dialogs before completing a drop
324
+ - Can await user input to decide whether to cancel, proceed, or modify the drop
325
+ - Example: `async (dropNode, draggedNode, position) => { return await showDialog(); }`
326
+
327
+ ### Enhanced
328
+ - **Exponential Batch Sizing**: Progressive rendering now uses exponential batching (20 → 40 → 80 → 160...)
329
+ - First batch (20 nodes) renders instantly for immediate visual feedback
330
+ - Batch size doubles each frame up to `maxBatchSize` (default 500)
331
+ - New props: `initialBatchSize` (default 20), `maxBatchSize` (default 500)
332
+ - Replaces fixed `renderBatchSize` prop
333
+ - Expand/collapse on large trees (>1000 nodes): Immediate add to minimize diffs
334
+ - **Tree Editor Example**: Enhanced with async drop validation dialog
335
+ - Demonstrates async `beforeDropCallback` with native confirm dialog
336
+ - Shows warning when drop position is modified
337
+
338
+ ### Fixed
339
+ - **Scroll Highlight Stacking**: Fixed multiple nodes staying highlighted when rapidly clicking prev/next
340
+ - Previous highlight now immediately cleared when navigating to new node
341
+ - Timeout properly cancelled to prevent stale highlight removal
342
+ - **Flat Mode Node Updates After Move**: Fixed nodes not re-rendering after `moveNode()` in flat mode
343
+ - Keyed each now uses `node.id + path + hasChildren` to detect moved nodes
344
+ - Moved nodes now correctly update their indentation level
345
+ - Parent nodes correctly update toggle icon when children are moved away
346
+
347
+ ### Changed
348
+ - **Default Rendering Mode**: `isFlatRenderingEnabled` now defaults to `true` (was `false`)
349
+ - **Default Progressive Render**: `isProgressiveRender` now defaults to `true` (was `false`)
350
+
351
+ ## [4.5.0] - 2026-02-09
352
+
353
+ ### Added
354
+ - **Drop Zone Layout Configuration**: New props to customize drop zone appearance and positioning
355
+ - `dropZoneLayout` - Controls zone arrangement with 5 layout options:
356
+ - `'around'` (default) - Above zone on top, Below/Child zones on bottom
357
+ - `'above'` - All 3 zones in a horizontal row above the node
358
+ - `'below'` - All 3 zones in a horizontal row below the node
359
+ - `'wave'` - Zones stacked vertically (above/child/below) with fixed width
360
+ - `'wave2'` - Diagonal wave pattern with Above/Below offset 7% to the left
361
+ - `dropZoneStart` - Number (0-100) controlling where zones start horizontally (default: 33%)
362
+ - `dropZoneMaxWidth` - Max width in pixels for wave layouts (default: 120px)
363
+ - **New TypeScript Type**: `DropZoneLayout` type exported from `types.ts`
364
+ - **Mobile Touch Drag and Drop**: Full touch support for drag and drop on mobile devices
365
+ - Long-press (300ms) to initiate drag - distinguishes from tap and scroll
366
+ - Visual ghost element follows finger during drag showing the dragged node
367
+ - Drop target highlighting using existing `dragOverNodeClass` prop
368
+ - Haptic feedback via `navigator.vibrate()` when drag starts (on supported devices)
369
+ - Automatic cancellation if finger moves >10px before long-press completes (allows normal scrolling)
370
+ - Works alongside existing desktop HTML5 drag and drop - same `onNodeDrop` callback for both
371
+ - **Drop Placeholder for Empty Trees**: When dragging nodes to an empty tree, a drop zone placeholder appears
372
+ - Shows visual drop target in empty trees during drag operations
373
+ - Works with both desktop (HTML5 DnD) and touch drag
374
+ - Customizable via `dropPlaceholder` snippet prop for custom content
375
+ - `onNodeDrop` callback receives `null` as `dropNode` for root-level drops into empty trees
376
+ - **Drop Position Indicators**: Visual indicators showing exactly where dropped items will be placed
377
+ - Three drop positions per node: `'above'` (sibling before), `'child'` (as child), `'below'` (sibling after)
378
+ - Absolutely positioned indicators on right half of node to prevent layout shifts
379
+ - Position calculated from mouse Y: top 25% = above, middle 50% = child, bottom 25% = below
380
+ - New CSS classes: `.ltree-drop-indicators`, `.ltree-drop-above`, `.ltree-drop-child`, `.ltree-drop-below`
381
+ - **Root Drop Zone**: Drop zone that appears at bottom of non-empty trees during drag
382
+ - Allows dropping items as root-level nodes in trees that already have content
383
+ - New CSS class: `.ltree-root-drop-zone`
384
+ - **Drag Drop Mode Control**: New `dragDropMode` prop to control allowed drag operations
385
+ - `'none'` - Drag and drop disabled
386
+ - `'self'` - Only within same tree
387
+ - `'cross'` - Only between different trees
388
+ - `'both'` - Both self and cross-tree (default)
389
+ - **Sibling Order Support**: New `orderMember` prop for explicit sibling ordering
390
+ - Specifies which field in user data contains the sort order value
391
+ - Used by default sort to order siblings within the same parent
392
+ - Required for proper above/below positioning in drag-drop tree editors
393
+ - Example: `orderMember="sortOrder"` with data like `{ path: '1.1', name: 'A', sortOrder: 10 }`
394
+ - **Tree Editor Helper Methods**: New methods for building tree editors
395
+ - `getChildren(parentPath)` - Get direct children of a node
396
+ - `getSiblings(path)` - Get all siblings of a node (including itself)
397
+ - `getNodeByPath(path)` - Get a node by its path
398
+ - `refreshSiblings(parentPath)` - Re-sort children of a parent using orderMember
399
+ - `refreshNode(path)` - Trigger re-render for a specific node
400
+ - **Tree Editor Mutation Methods**: New methods for modifying tree structure
401
+ - `addNode(parentPath, data, pathSegment?)` - Add a new node to the tree
402
+ - `moveNode(sourcePath, targetPath, position)` - Move a node with full subtree to a new location
403
+ - Supports 'above', 'below', and 'child' positions
404
+ - Automatically updates paths of all descendants
405
+ - Calculates order values when orderMember is set
406
+ - `removeNode(path, includeDescendants?)` - Remove a node from the tree
407
+ - **Example Pages**: New `/examples` route with interactive demos (same look-and-feel as web-multiselect)
408
+ - Landing page with feature cards linking to 7 example sections
409
+ - Basic Examples: tree rendering, expand level control, scroll to path, programmatic expand/collapse
410
+ - Drag & Drop: two-tree drag demo, touch drag instructions, drop placeholder customization
411
+ - Context Menu: callback-based menus, dynamic items, icons, disabled states, dividers
412
+ - Search & Filter: live filtering with `searchText`, `searchNodes()` query method
413
+ - Theming: CSS variable reference, theme examples (default, purple, dark, green)
414
+ - Data Structures: path-based hierarchy, custom separators, insert result validation
415
+ - Tree Editor: add/move/remove nodes with drag-drop and orderMember support
416
+ - **Drop Indicator Arrows**: Visual arrow indicators for glow-mode drop zones
417
+ - Arrows positioned at 66% of row width, centered vertically
418
+ - Uses Lucide SVG icons as data URIs: `arrow-big-up`, `arrow-big-down`, `arrow-big-right-dash`
419
+ - Child arrow rotated 45° for diagonal pointing effect
420
+ - Fully customizable via SCSS variables
421
+ - **Ctrl+Drag Copy Operation**: Full support for copying nodes via Ctrl+drag
422
+ - `isCopyAllowed` prop enables Ctrl+drag to copy instead of move
423
+ - `shouldAutoHandleCopy` prop (default: true) controls whether Tree auto-handles same-tree copies
424
+ - `true`: Tree creates copy with generated ID (`{id}_copy_{timestamp}`) - good for batch/offline mode
425
+ - `false`: User callback handles copy (for DB/API integration) - good for online/live mode
426
+ - Copy operations respect drop position (above/below/child) just like moves
427
+ - Visual feedback: `.ltree-drop-copy` class applied during copy operations
428
+ - **Position Support for copyNodeWithDescendants**: Enhanced copy method now supports sibling positioning
429
+ - New optional parameters: `siblingPath` and `position` ('above' | 'below')
430
+ - Copies can be placed at specific positions relative to siblings
431
+ - Uses same `orderMember` logic as `moveNode` for consistent ordering
432
+ - **Logging Infrastructure**: Categorized logging using vendored loglevel library
433
+ - Six log categories: `LTREE:INIT`, `LTREE:DATA`, `LTREE:RENDER`, `LTREE:INDEX`, `LTREE:DRAG`, `LTREE:UI`
434
+ - Color-coded console output with timestamps for easy debugging
435
+ - Disabled by default (silent mode) for production
436
+ - Exported utilities: `enableLogging()`, `disableLogging()`, `setLogLevel()`, `setCategoryLevel()`
437
+ - UI logging: node clicks, expand/collapse, selection changes, context menu
438
+ - Drag logging: drag start/end, drop operations, touch drag events
439
+ - Render logging: progressive rendering frame stats
440
+ - Index logging: async search indexing progress
441
+ - New `/dev/logging` demo page for testing log levels and categories
442
+ - **Performance Logging**: Dedicated performance measurement utilities
443
+ - Measures key operations: `insertArray` (conversion/sort/insert phases), `filterNodes`, `expandAll`, `collapseAll`
444
+ - Output includes duration, item count, per-item time, and items/sec throughput
445
+ - Summary view showing breakdown by phase with percentages
446
+ - Configurable threshold to only log operations slower than X ms
447
+ - Exported utilities: `enablePerfLogging()`, `disablePerfLogging()`, `setPerfThreshold(ms)`
448
+ - Browser console access: `window.components['svelte-treeview'].perf.enable()`
449
+ - Purple color-coded output for easy identification
450
+ - **Global Runtime API**: `window.components['svelte-treeview']` for browser console access
451
+ - `config` - Package info (name, version, author, license, repository, homepage) read from package.json at build time
452
+ - `version()` - Returns current version string
453
+ - `logging.enableLogging()` / `logging.disableLogging()` - Toggle all logging
454
+ - `logging.setLogLevel(level)` - Set level for all categories
455
+ - `logging.setCategoryLevel(category, level)` - Set level for specific category
456
+ - `logging.getCategories()` - List available log categories
457
+ - **Container-Scoped Scrolling**: New `containerScroll` option for `scrollToPath()`
458
+ - `scrollToPath(path, { containerScroll: true })` scrolls only within the nearest scrollable ancestor
459
+ - Prevents page-level scrolling when tree is inside a scrollable container
460
+ - Automatically finds the scrollable parent element (overflow: auto/scroll)
461
+ - Useful for search result navigation without disrupting page position
462
+
463
+ ### Enhanced
464
+ - **Drop Zone Styling**: Improved visual feedback during drag operations
465
+ - Semi-transparent zones (0.25 opacity) that become solid (0.85) when hovered
466
+ - Modern pastel color palette: sage green for Above, peach/coral for Below, lavender for Child
467
+ - Interactive controls in `/examples/drag-drop` to test all layout configurations
468
+ - **Drop Zone SCSS Variables**: Full customization of drop zone appearance via SCSS variables
469
+ - `$drop-zone-border-radius` - Border radius for all zones (default: 0)
470
+ - Per-zone variables for backgrounds, colors, and shadows in both inactive and active states:
471
+ - Above: `$drop-zone-above-bg`, `$drop-zone-above-color`, `$drop-zone-above-active-bg`, `$drop-zone-above-active-color`, `$drop-zone-above-active-shadow`
472
+ - Below: `$drop-zone-below-bg`, `$drop-zone-below-color`, `$drop-zone-below-active-bg`, `$drop-zone-below-active-color`, `$drop-zone-below-active-shadow`
473
+ - Child: `$drop-zone-child-bg`, `$drop-zone-child-color`, `$drop-zone-child-active-bg`, `$drop-zone-child-active-color`, `$drop-zone-child-active-shadow`
474
+ - **Drop Zone Positioning**: Moved drop zones from inside `.ltree-node-content` to `.ltree-node-row` level
475
+ - Eliminates padding-related gaps that made zones hard to reach
476
+ - More predictable positioning relative to the full row width
477
+ - **Wave2 Layout Overlap**: Added 10% overlap for Above/Below zones in wave2 layout
478
+ - Ensures first node's Above zone and last node's Below zone are always reachable
479
+ - Child zone shrunk to 80% height to accommodate overlap without zone collision
480
+ - **dropZoneStart Flexibility**: Now accepts both number (percentage) and string (any CSS value)
481
+ - Number: treated as percentage (e.g., `33` → `33%`)
482
+ - String: used as-is (e.g., `"33%"`, `"50px"`, `"3rem"`)
483
+ - **Touch UX**: Added CSS properties to prevent text selection during touch drag
484
+ - `-webkit-user-select: none` and `-webkit-touch-callout: none` on node content
485
+ - **Ghost Element Styling**: New `.ltree-touch-ghost` CSS class with customizable CSS variables
486
+ - `--tree-ghost-bg`: Background color (default: rgba(59, 130, 246, 0.9))
487
+ - `--tree-ghost-color`: Text color (default: white)
488
+ - **Drop Placeholder Styling**: New `.ltree-drop-placeholder` and `.ltree-drop-placeholder-content` CSS classes
489
+ - **Drop Indicator Arrow SCSS Variables**: Full customization of arrow indicators via SCSS variables
490
+ - `$drop-arrow-above`, `$drop-arrow-below`, `$drop-arrow-child` - SVG data URIs for each direction
491
+ - `$drop-arrow-size` - Arrow size (default: 24px)
492
+ - `$drop-arrow-position` - Horizontal position within row (default: 66%)
493
+ - `$drop-arrow-above-rotation`, `$drop-arrow-below-rotation`, `$drop-arrow-child-rotation` - Rotation angles
494
+ - **Search Example Page Redesign**: Merged filter and search cards into unified search experience
495
+ - Single search input with live filtering and result navigation
496
+ - Prev/Next buttons to traverse search results with wrap-around
497
+ - Result counter showing "X of Y" position indicator
498
+ - Keyboard navigation: Enter = next result, Shift+Enter = previous
499
+ - Clickable result list with active item highlighting
500
+ - Auto-scroll to first result when searching
501
+
502
+ ### Fixed
503
+ - **Empty Tree Drop Placeholder**: Fixed drop placeholder not appearing when dragging to empty trees
504
+ - Added missing `ondragenter` handler to empty state divs
505
+ - Added `min-height: 60px` to `.ltree-empty-state` to ensure drop target is always reachable
506
+ - **Drag-Drop Demo ID/Path Mismatch**: Fixed bug where second node drop to first node didn't work on first try
507
+ - Root cause: `nextId++` post-increment caused id and path to use different values
508
+ - Fixed by extracting `const itemId = nextId++` before using in object properties
509
+ - **Tree Data Reset**: Fixed `insertArray` not clearing existing tree data when called with new/empty data
510
+ - Previously, setting `data = []` would not clear the tree - existing nodes remained visible
511
+ - Now `insertArray` properly resets root children, nodeCount, and maxLevel before inserting
512
+ - **Example Pages Data Insertion**: Added `isSorted={true}` to all example page Tree components
513
+ - Prevents "Could not find parent node" errors caused by `sortCallback` sorting data before insertion
514
+ - When `sortCallback` alphabetizes data, children could be inserted before parents (e.g., "AuthService" before "Services")
515
+ - `isSorted={true}` tells the tree to skip pre-sorting and only use `sortCallback` for display ordering
516
+ - **Search Example Async Index**: Added note explaining that search index is built asynchronously
517
+ - Added Enter key support for better UX when retrying searches
518
+ - Users are now informed to wait a moment if no results appear immediately after page load
519
+ - **Search Example Reactivity**: Made "Search Nodes (Query)" input reactive
520
+ - Added `$effect` to automatically trigger search when input changes
521
+ - **Glow Mode Border Radius**: Fixed border-radius appearing on glow drop indicators during drag
522
+ - Changed `$tree-node-content-border-radius` default from `4px` to `0`
523
+ - **Search Example Documentation**: Fixed incorrect prop name in search configuration table
524
+ - Changed `searchValueCallback` → `getSearchValueCallback` (correct prop name)
525
+ - Fixed callback signature from `(item: T) => string` to `(node: LTreeNode<T>) => string`
526
+ - **LTreeNode Type Export**: Added `LTreeNode` re-export from `types.ts`
527
+ - Fixes import errors when using `import type { LTreeNode } from '$lib/ltree/types'`
528
+ - **Search Example Endless Loop**: Fixed `$effect` causing infinite loop on search
529
+ - Used Svelte's `untrack()` to prevent reactive state updates from re-triggering the effect
530
+ - Effect now only reacts to `searchText` changes, not internal state mutations
531
+ - **Search Example Container Scroll**: Fixed prev/next navigation scrolling the entire page
532
+ - Now uses `scrollToPath(path, { containerScroll: true })` for container-scoped scrolling
533
+ - **Critical Performance Bug in insertArray**: Fixed O(n²) algorithm causing 85+ second load times
534
+ - Progressive render check was iterating all remaining nodes for every node at expandLevel
535
+ - With 17,000 nodes this caused ~145 million iterations instead of ~34,000
536
+ - Fix: Pre-compute last expandLevel index once, then use simple index comparison
537
+ - Result: Load time reduced from 85+ seconds to under 1 second
538
+ - **Global API Constants**: Fixed `__PACKAGE_NAME__ is not defined` error when using library in other projects
539
+ - Vite `define` constants only work during dev, not when library is built with `svelte-package`
540
+ - Added `scripts/generate-constants.js` to bake package.json values into `constants.generated.ts`
541
+
542
+ ### Important - Svelte 5 Performance
543
+
544
+ **Use `$state.raw()` for large datasets passed to Tree component**
545
+
546
+ When passing large arrays (1000+ items) to the Tree component, use `$state.raw()` instead of `$state()` to avoid severe performance degradation:
547
+
548
+ ```typescript
549
+ // SLOW - Svelte deeply proxies all 8000+ objects, causing 5000x slowdown
550
+ let treeNodes = $state<TreeNode[]>([])
551
+ treeNodes = response.data // Each item becomes a Proxy
552
+
553
+ // FAST - Array is reactive but items remain plain objects
554
+ let treeNodes = $state.raw<TreeNode[]>([])
555
+ treeNodes = response.data // Items stay as plain objects
556
+ ```
557
+
558
+ **Why this matters:**
559
+ - `$state()` creates deep proxies - every nested object becomes a Proxy
560
+ - Tree's `insertArray()` accesses multiple properties on each data item
561
+ - With 8000 items × ~10 property accesses = 80,000 proxy operations
562
+ - Proxy overhead: ~2.2ms per item vs ~0.0004ms for plain objects (5,500x slower)
563
+
564
+ **Symptoms of this issue:**
565
+ - Tree takes 15-90+ seconds to render with thousands of items
566
+ - Console shows `[Violation] 'message' handler took XXXXms`
567
+ - Same data loads instantly in isolated test environment
568
+
569
+ **The fix does NOT affect reactivity** - changes to `treeNodes` array itself still trigger updates. Only the individual items inside lose deep reactivity, which Tree doesn't need.
570
+ - Build now runs `npm run generate-constants` before `svelte-package`
571
+ - **Glow Mode Border Radius**: Added `!important` to `border-radius: 0` on glow classes to ensure override
572
+ - Removed `border-radius: 4px !important` from `.ltree-dragover-glow` class
573
+ - Removed `border-radius` from `.ltree-node-content` transition to prevent animation glitch
574
+ - **Glow Mode Drop Validation**: Fixed glow showing on invalid drop targets
575
+ - When `dragDropMode='cross'` prevents drops, glow no longer appears on hover
576
+ - Cleared `hoveredNodeForDrop` state when drop validation fails
577
+ - Debug logging for drop rejection now gated behind `shouldDisplayDebugInformation`
578
+ - **Glow Mode Border Radius Transition**: Fixed double border artifact on node hover during drag
579
+ - Added `border-radius` to transition property for smooth animation
580
+ - Eliminates visual conflict between base rounded corners and glow's straight corners
581
+ - **Ctrl+Drag dropEffect Timing**: Fixed copy operation failing with `dropEffect: none`
582
+ - Root cause: Node's `ondragover` read stale `dropOperation` prop before Tree updated it
583
+ - Fix: Node now reads `event.ctrlKey` directly to calculate dropEffect immediately
584
+ - Also added dropEffect confirmation in `ondrop` handlers for spec compliance
585
+ - **Missing isCopyAllowed Prop in Recursive Node**: Fixed `isCopyAllowed` not being passed to child nodes
586
+ - Caused Ctrl+drag to fail on non-root nodes since they defaulted to `isCopyAllowed=false`
587
+ - Added `{isCopyAllowed}` to recursive Node component call
588
+
589
+ ### Changed
590
+ - **BREAKING: onNodeDrop Signature**: Callback signature updated to include drop position
591
+ - Before: `onNodeDrop?: (dropNode, draggedNode, event) => void`
592
+ - After: `onNodeDrop?: (dropNode, draggedNode, position, event) => void`
593
+ - `position` is `'above'`, `'below'`, or `'child'` indicating where item should be placed
594
+ - `dropNode` can be `null` when dropping into empty tree or root drop zone
595
+
596
+ ## [4.4.0] - 2025-10-02
597
+
598
+ ### Added
599
+ - **External Update Method**: New `update()` method for programmatic prop updates from vanilla JavaScript
600
+ - Allows external code to update component props without Svelte reactivity
601
+ - Accepts partial object with any Tree props (excluding snippets/templates)
602
+ - Useful for HTML/JavaScript integration and dynamic configuration
603
+ - Example: `tree.update({ searchText: 'query', expandLevel: 3, data: newData })`
604
+
605
+ ### Fixed
606
+ - **Search Functionality**: Fixed search filtering in context-menu dev page
607
+ - Added missing `searchValueMember="name"` prop to enable proper search indexing
608
+ - Search now correctly filters nodes by name instead of filtering everything out
609
+
610
+ ### Changed
611
+ - **Code Cleanup**: Renamed internal "trie" references to "tree" for consistency
612
+ - Updated variable names in Tree.svelte, Node.svelte, and ltree-demo.ts
613
+ - Removed "trie" from package.json keywords
614
+ - Improved code readability and naming consistency throughout codebase
615
+
616
+ ## [4.3.1] - 2025-09-25
617
+
618
+ ### Enhanced
619
+ - **Async Callback Support**: Context menu callbacks now fully support async operations
620
+ - Updated `callback: () => void | Promise<void>` signature in `ContextMenuItem` interface
621
+ - Added automatic error handling for async callbacks with try/catch wrapper
622
+ - Menu item clicks properly await async operations before completing
623
+ - Errors in async callbacks are logged to console for debugging
624
+ - **Robust Error Handling**: Async callback failures don't break menu functionality
625
+ - Failed async operations are caught and logged automatically
626
+ - Developers can implement custom error handling within their callbacks
627
+ - Menu stays open on errors, allowing users to retry actions
628
+ - **Enhanced Dev Examples**: Added comprehensive async callback demonstrations
629
+ - Copy action with simulated network delay
630
+ - New folder creation with error simulation (20% failure rate)
631
+ - Database backup with long-running operation simulation
632
+ - Shows patterns for success/failure handling and conditional menu closing
633
+
634
+ ### Documentation
635
+ - **Async Patterns**: Examples showing proper async callback implementation
636
+ - **Error Handling**: Best practices for managing async operation failures
637
+ - **Menu Control**: Demonstrated conditional closing based on operation success/failure
638
+
639
+ ## [4.3.0] - 2025-09-25
640
+
641
+ ### Added
642
+ - **Enhanced Context Menu Control**: Context menu callback now receives `closeMenuCallback` parameter for programmatic menu control
643
+ - `contextMenuCallback?: (node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[]`
644
+ - Developers can now control when/if context menu closes after menu item actions
645
+ - Enables conditional closing patterns (e.g., don't close on cancel, only on success)
646
+ - Public `closeContextMenu()` method exported for external control
647
+ - **Context Menu Item Styling**: New `className?: string` property in `ContextMenuItem` interface
648
+ - Apply custom CSS classes to individual menu items for styling
649
+ - Supports multiple classes (space-separated strings)
650
+ - Example: `className: 'text-danger fw-bold'` for destructive actions
651
+ - **Enhanced Dev Examples**: Updated context menu examples to demonstrate new features
652
+ - Conditional menu closing patterns for different action types
653
+ - CSS class styling demonstrations with Bootstrap classes
654
+ - Improved UX patterns showing when to close vs keep menu open
655
+
656
+ ### Enhanced
657
+ - **Flexible Menu Behavior**: Context menu now supports various interaction patterns
658
+ - Immediate close after action completion
659
+ - Conditional close based on user confirmation
660
+ - Persistent menu for multi-step operations
661
+ - Custom styling per menu item type
662
+
663
+ ## [4.2.1] - 2025-09-24
664
+
665
+ ### Enhanced
666
+ - **Debug Context Menu Positioning**: Improved debug context menu to position relative to tree element instead of viewport
667
+ - Debug menu now appears 200px right and 100px down from each tree's top-left corner
668
+ - Supports multiple trees on same page with individual positioning
669
+ - Enhanced debug logging to include tree ID and calculated position coordinates
670
+ - Better for CSS development when tree is not at top-left of viewport
671
+ - **Debug Context Menu Robustness**: Enhanced debug mode to work with single-node trees
672
+ - Uses second node when available, falls back to first node for single-node trees
673
+ - More flexible node selection for debug menu display
674
+ - Improved reliability for development scenarios
675
+
676
+ ### Fixed
677
+ - **Debug Mode State Management**: Fixed context menu interference between debug mode and normal right-click menus
678
+ - Added `isDebugMenuActive` state tracking to prevent debug logic from hiding user-triggered menus
679
+ - Normal right-click context menus now work properly when debug mode is disabled
680
+ - Proper cleanup of debug state when switching between modes
681
+ - **Debug Mode Requirements**: Relaxed debug context menu requirements to support edge cases
682
+ - Changed minimum tree length requirement from `> 1` to `> 0` for better compatibility
683
+ - Debug mode now works with any non-empty tree structure
684
+
685
+ ## [4.2.0] - 2025-09-24
686
+
687
+ ### Added
688
+ - **Context Menu System**: Comprehensive context menu functionality with two implementation approaches
689
+ - **Callback-based Context Menus**: New `contextMenuCallback` prop that accepts a function `(node: LTreeNode<T>) => ContextMenuItem[]`
690
+ - **ContextMenuItem Interface**: New interface with `icon`, `title`, `isDisabled`, `callback`, and `isDivider` properties
691
+ - **Position Offset Configuration**: New `contextMenuXOffset` (default: 8px) and `contextMenuYOffset` (default: 0px) props for cursor clearance
692
+ - **Debug Mode**: New `shouldDisplayContextMenuInDebugMode` prop for persistent context menu display at fixed position (200px, 100px)
693
+ - **Snippet-based Support**: Maintains backward compatibility with existing `{#snippet contextMenu(node, closeMenu)}` approach
694
+ - **Enhanced Context Menu UX**:
695
+ - Auto-close on scroll events (mouse wheel, scrollbar, touch, programmatic)
696
+ - Auto-close on outside clicks
697
+ - Support for disabled menu items with visual feedback
698
+ - Support for menu dividers for visual organization
699
+ - Rich icon support for menu items
700
+ - **Development Tools**: New `/dev/context-menu` page with comprehensive examples
701
+ - Basic file system context menu example with conditional actions
702
+ - Advanced server management example with status-based and type-specific menus
703
+ - Real-time offset configuration testing
704
+ - Interactive demonstration of all context menu features
705
+ - Debug context menu mode with `shouldDisplayContextMenuInDebugMode` for easy styling development
706
+ - Navigation link added to main layout for easy access
707
+
708
+ ### Enhanced
709
+ - **CSS Styling**: Added comprehensive context menu styles in `main.scss`
710
+ - `.ltree-context-menu`, `.ltree-context-menu-item`, `.ltree-context-menu-icon`, `.ltree-context-menu-divider` classes
711
+ - Support for disabled states with `.ltree-context-menu-item-disabled`
712
+ - Flexible layout with proper hover effects and visual hierarchy
713
+ - **Type Safety**: Full TypeScript support for all context menu features
714
+ - **Documentation**: Comprehensive README and CLAUDE.md updates covering both implementation approaches
715
+
716
+ ### Fixed
717
+ - **Context Menu Scroll Behavior**: Fixed issue where context menu remained visible when scrolling
718
+ - Added scroll event listeners with capture phase to catch all scroll events
719
+ - Added wheel event listeners for mouse wheel scrolling
720
+ - Context menu now properly closes on any scroll interaction
721
+
722
+ ## [4.1.1] - 2025-09-23
723
+
724
+ ### Fixed
725
+ - **TreePathSeparator Default Value**: Fixed `treePathSeparator` parameter to properly default to '.' when not provided to Tree.svelte
726
+ - Previously, when `treePathSeparator` was undefined, the reactive effect would override the ltree's internal default
727
+ - Now defaults to '.' in the parameter destructuring, ensuring consistent behavior
728
+
729
+ ## [4.1.0] - 2025-09-23
730
+
731
+ ### Fixed
732
+ - **Critical Sorting Bug**: Fixed default sort method to sort by level first, ensuring proper hierarchical tree construction
733
+ - Previously sorted by parent path first, causing level 3 nodes to be inserted before level 2 nodes
734
+ - Now sorts by level (depth) first, then parent path, then display value
735
+ - Eliminates "Could not find parent node" errors when nodes are inserted out of level order
736
+ - **Progressive Rendering**: Fixed progressive rendering feature to work correctly with proper level-based sorting
737
+ - Progressive rendering now displays levels 1-2 immediately while deeper levels continue processing
738
+ - Improves perceived performance for large datasets by showing initial tree structure quickly
739
+ - **TreePathSeparator Reactivity**: Fixed Tree component to properly update internal separator when `treePathSeparator` prop changes
740
+ - Added reactive effect to update ltree's separator property when prop changes
741
+ - Prevents race conditions where data is processed with wrong separator
742
+ - Fixes filesystem demo and other custom separator use cases
743
+ - **Sort Functions in Examples**: Updated all demo sort functions to calculate level from path depth during sorting
744
+ - Home page, dev page, and filesystem examples now use path-based level calculation
745
+ - Ensures consistent level-first sorting across all demos and examples
746
+ - Prevents insertion failures in example applications
747
+
748
+ ### Enhanced
749
+ - **Test Coverage**: Added comprehensive test suite for sorting functionality
750
+ - Tests verify level-first sorting behavior with various hierarchical data structures
751
+ - Validates progressive rendering scenarios and sort correctness
752
+ - Uses Vitest framework for fast, reliable testing
753
+
754
+ ### Changed
755
+ - **Default Sort Algorithm**: Updated `_defaultSort` method to prioritize level over parent path for hierarchical correctness
756
+ - **Example Sort Functions**: All demo applications now use level-first sorting for consistent behavior
757
+
758
+ ## [4.0.1] - 2025-01-23
759
+
760
+ ### Fixed
761
+ - **treePathSeparator Propagation**: Fixed helper functions (`getParentPath`, `getRelativePath`, `getPathSegments`) to properly use the configured `treePathSeparator` instead of hardcoded "." separator
762
+ - All path manipulation functions now respect the custom separator setting
763
+ - Ensures consistent path handling throughout the tree operations when using custom separators like "/"
764
+ - Fixed `getRelativePath` to use `pathSeparator.length` instead of assuming single character
765
+ - Fixed `getLevel` to properly count segments with multi-character separators
766
+
767
+ ### Added
768
+ - **Test Suite**: Added comprehensive test coverage for ltree helper functions
769
+ - 24 test cases covering single-character, multi-character, and edge case separators
770
+ - Vitest testing framework integration with `npm run test` and `make test` commands
771
+ - Tests validate proper handling of separators like `"::"`, `"->>"`, `"<|>"` and complex edge cases
772
+
773
+ ## [4.0.0] - 2025-01-09
774
+
775
+ ### Added
776
+ - **Complete Showcase Site Redesign**: Comprehensive overhaul of the documentation and demo site
777
+ - **API Reference Page**: Complete tabbed reference with properties, methods, events, and templates tables
778
+ - **Professional Navigation**: Fixed-top navbar with burger menu, GitHub link, and responsive sidebar
779
+ - **Ocean Color Scheme**: Beautiful blue-themed design using Coolors.co palette (#00171F, #003459, #007EA7, #00A7E1, #FFFFFF)
780
+ - **Responsive Layout**: Mobile-first design with collapsible sidebar and backdrop overlay
781
+ - **Enhanced Examples**: Four comprehensive code examples with descriptions in tabbed interface
782
+ - **Docker Production Setup**: Complete containerization for static site deployment
783
+ - **Multi-stage Dockerfile**: Optimized build with Node.js builder and nginx production stage
784
+ - **Static Site Generation**: SvelteKit configuration for pre-rendered HTML pages
785
+ - **Make Commands**: Docker build, run, and management commands with custom registry support
786
+ - **Nginx Configuration**: Optimized serving with gzip, caching, and SPA routing support
787
+
788
+ ### Changed
789
+ - **Layout Architecture**: Moved from nested Bootstrap containers to clean, consistent structure
790
+ - **Fixed Navigation**: Top navbar with brand, burger menu, and GitHub link
791
+ - **Sidebar Design**: Fixed-width (280px) sidebar with consistent icon spacing
792
+ - **Footer Integration**: Professional footer with KeenMate branding
793
+ - **SvelteKit Configuration**: Updated for optimal static generation
794
+ - **Static Adapter**: Switched from adapter-auto to adapter-static for reliable builds
795
+ - **Prerendering**: Enabled SSR and prerender for all showcase pages
796
+ - **Build Output**: Optimized for nginx serving with proper fallback handling
797
+ - **Page Structure Consistency**: Standardized header structure across all showcase pages
798
+ - **Removed Redundant Containers**: Eliminated nested container-fluid wrappers
799
+ - **Clean Headers**: Direct h1 and description elements without Bootstrap grid overhead
800
+
801
+ ### Enhanced
802
+ - **Visual Design**: Professional styling throughout the showcase site
803
+ - **Fixed Icon Alignment**: Consistent 1.5rem width for sidebar navigation icons
804
+ - **Gradient Backgrounds**: Sophisticated color gradients across navbar, sidebar, and footer
805
+ - **Interactive Elements**: Hover effects, focus states, and smooth transitions
806
+ - **Typography**: Clear hierarchy with proper contrast and accessibility
807
+ - **User Experience**: Improved navigation and usability
808
+ - **Always-Visible Burger Menu**: Toggle sidebar on any screen size for flexible layout
809
+ - **Responsive Behavior**: Automatic sidebar hiding on mobile with backdrop close
810
+ - **Tab Navigation**: Full-width code examples with clean tab interface
811
+ - **Mobile Optimization**: Touch-friendly interactions and responsive text sizing
812
+
813
+ ### Fixed
814
+ - **Container Structure**: Resolved double-container issues causing layout inconsistencies
815
+ - **Sidebar Toggle**: Fixed burger menu functionality to work across all screen sizes
816
+ - **Static Generation**: Proper SvelteKit configuration for nginx-compatible static builds
817
+ - **Icon Spacing**: Consistent navigation icon width preventing text misalignment
818
+
819
+ ### Documentation
820
+ - **API Reference**: Complete tables for all component properties, methods, events, and templates
821
+ - **Usage Examples**: Real-world code examples including organization tree configuration
822
+ - **Docker Documentation**: Make commands and containerization setup
823
+ - **Responsive Design**: Mobile-first approach with professional styling
824
+
825
+ ## [4.0.0-rc.08] - 2025-01-08
826
+
827
+ ### Added
828
+ - **searchNodes() Method**: New public method `searchNodes(searchText)` that returns an array of matching nodes without filtering the tree display
829
+ - Programmatically search nodes using the internal search index
830
+ - Returns `LTreeNode<T>[]` array of matching nodes
831
+ - Useful for building custom search interfaces, suggestions, and result summaries
832
+ - **Configurable Path Separators**: New `treePathSeparator` property allows custom hierarchical path separators
833
+ - Default remains `"."` for backward compatibility (e.g., "1.2.3")
834
+ - Support for custom separators like `"/"` for file system style paths (e.g., "1/src/components")
835
+ - All path operations throughout the component respect the custom separator
836
+ - **Data Structure Showcase Page**: New comprehensive `/data-structure` showcase page with four detailed sections:
837
+ - **LTree Path Structure**: Understanding path-based hierarchical data model
838
+ - **Optimized Data Structure**: Precomputed values for better performance
839
+ - **Custom Path Separators**: Live demo with file system style paths using "/" separator
840
+ - **External Search & Data Management**: Managing search outside the tree component
841
+ - **Invalid Data Structures**: Common mistakes and unsupported patterns
842
+ - **Enhanced Search Showcase**: Added new `searchNodes()` method demonstration section to `/search` page
843
+ - Interactive search interface showing difference between `searchNodes()` and `filterNodes()`
844
+ - Live examples with result display and usage patterns
845
+ - **Insert Result Information**: New `insertResult` bindable property provides detailed information about data insertion
846
+ - `InsertArrayResult<T>` interface with successful count and failed nodes array
847
+ - Each failed node includes original data, processed node, and error message
848
+ - Useful for data validation, debugging, and handling incomplete datasets
849
+ - **Drag-over Visual Feedback**: New `dragOverNodeClass` property for highlighting nodes during drag operations
850
+ - Two built-in classes: `ltree-dragover-highlight` (dashed border) and `ltree-dragover-glow` (shadow effect)
851
+ - Automatic state management with proper drag event handling
852
+ - Provides clear visual feedback for drop targets during drag-and-drop operations
853
+
854
+ ### Changed
855
+ - **Documentation Updates**: Updated README.md, CLAUDE.md, and showcase pages with new features
856
+ - Added `searchNodes` to public methods documentation
857
+ - Added `treePathSeparator` to Tree Configuration properties table
858
+ - Updated architecture description to reflect configurable separators
859
+ - Fixed path requirements documentation to clarify separator flexibility
860
+ - **Navigation Enhancement**: Added "Data Structure" page to sidebar navigation with 🗂️ icon
861
+
862
+ ### Enhanced
863
+ - **Type System**: Updated `Ltree<T>` interface to include `searchNodes` method signature
864
+ - **Internal Architecture**: Enhanced `createLTree` function to accept configurable `treePathSeparator` parameter
865
+ - **Component Integration**: Updated `Tree.svelte` component to pass through `treePathSeparator` property
866
+
867
+ ### Fixed
868
+ - **Node Indentation**: Fixed `Node.svelte` indent style to use consistent per-level indentation instead of cumulative indentation
869
+ - Previously: Each level had exponentially increasing indent (level * indent-per-level)
870
+ - Now: Each level uses fixed CSS variable `--tree-node-indent-per-level` allowing proper CSS-based indentation control
871
+ - **Search Index Accuracy**: Fixed `insertArray` to only add successfully inserted nodes to `flatTreeNodes` array
872
+ - Prevents search index from returning incorrect node indices for nodes that failed to insert
873
+ - Failed nodes are no longer included in search operations, ensuring search results match visible tree structure
874
+ - **Error Message Clarity**: Improved `insertTreeNode` error messages to include the failing node's path
875
+ - Error format: `"Node: {path} - Could not find parent node: {parentPath}"`
876
+ - Makes debugging hierarchical data issues much clearer
877
+
878
+ ### Documentation
879
+ - **Comprehensive Examples**: Added working code examples for both basic and advanced use cases
880
+ - **Path Separator Flexibility**: Clarified that paths don't need to be dot-separated, can use any consistent separator
881
+ - **External Data Management**: Detailed examples of filtering data outside the tree component
882
+ - **Performance Optimization**: Guidelines for when to use precomputed values vs automatic calculations
883
+
884
+ ## [4.0.0-rc.07] - 2025-01-06
885
+
886
+ ### Added
887
+ - **Customizable Scroll Highlight**: New `scrollHighlightClass` property allows users to define custom CSS classes for scroll highlight effects
888
+ - **Built-in Highlight Options**: Added pre-built highlight classes:
889
+ - `ltree-scroll-highlight` - Background glow with blue color (default)
890
+ - `ltree-scroll-highlight-arrow` - Red arrow indicator positioned to the right of the node
891
+ - **Scroll Highlight Timeout Control**: New `scrollHighlightTimeout` property (default: 4000ms) controls duration of highlight effect
892
+ - **Enhanced scrollToPath Method**: Improved scroll highlighting with proper element targeting and CSS class management
893
+ - **Debug Logging Control for Indexer**: Added `shouldDisplayDebugInformation` property to Indexer class for consistent debug logging control
894
+
895
+ ### Changed
896
+ - **Removed CSS Animation Dependencies**: Scroll highlighting now uses pure CSS classes instead of CSS animations for better timeout control
897
+ - **Improved Element Targeting**: `scrollToPath` now targets `.ltree-node-content` specifically for more precise highlighting
898
+ - **Enhanced Documentation**: Updated README with comprehensive examples for highlight customization
899
+ - **Consistent Debug Logging**: All indexer console.log messages now respect the `shouldDisplayDebugInformation` flag for unified logging control
900
+
901
+ ### Fixed
902
+ - **Scroll Highlight Duration**: Fixed issue where CSS animations overrode JavaScript timeout values
903
+ - **Element Selection**: Improved DOM element selection for scroll highlighting functionality
904
+ - **LTree Path Traversal**: Fixed `expandNodes` and `collapseNodes` methods by correctly prefixing path segments with 'x' prefix to match internal tree structure storage
905
+
906
+ ## [4.0.0-rc.05] - 2025-09-05
907
+
908
+ ### Added
909
+ - **Optimized Async Search Indexing**: Improved indexing implementation that processes entire queue at once during idle time instead of small batches
910
+ - **Enhanced Indexing Performance**: Increased batch size from 100 to 1000 nodes and streamlined queue processing
911
+ - **Better Debug Logging**: Added conditional debug logging for indexing operations when `shouldDisplayDebugInformation` is enabled
912
+
913
+ ### Changed
914
+ - **Indexing Architecture**: Refactored async indexing to process all queued nodes in a single idle callback rather than batched processing
915
+ - **Queue Management**: Simplified indexing queue processing with more efficient completion handling
916
+ - **TypeScript Support**: Added `Tuple<T, U>` type import for enhanced type safety
917
+
918
+ ### Performance
919
+ - **Faster Indexing**: Single-pass indexing of entire queue reduces overhead and callback scheduling
920
+ - **Reduced Idle Callbacks**: Less frequent but more efficient use of `requestIdleCallback`
921
+ - **Improved Memory Usage**: More efficient queue management with immediate processing
922
+
923
+ ## [4.0.0] - 2025-09-01
924
+
925
+ ### Added
926
+ - **Asynchronous Search Indexing**: Search indexing now uses `requestIdleCallback` for non-blocking performance
927
+ - **Statistics Tracking**: New `statistics` getter provides real-time data:
928
+ - `nodeCount`: Total number of nodes in the tree
929
+ - `maxLevel`: Maximum depth level of the tree
930
+ - `filteredNodeCount`: Number of nodes currently visible when filtering
931
+ - `isIndexing`: Boolean indicating if search indexing is in progress
932
+ - `pendingIndexCount`: Number of nodes pending indexing
933
+ - **Expand Level Control**: New `expandLevel` property (default: 2) automatically expands nodes up to specified depth
934
+ - **Drag & Drop Properties**: Added `isDraggableMember` and `isDropAllowedMember` for fine-grained drag & drop control
935
+ - **Debug Information Panel**: Enhanced debug display with collapsible interface showing tree statistics and indexing progress
936
+
937
+ ### Changed
938
+ - **Breaking**: Renamed internal references from "Trie" to "LTree" for consistency
939
+ - **Breaking**: `trieId` property renamed to `treeId`
940
+ - **Search Performance**: Tree now renders immediately while search indexing happens asynchronously
941
+ - **Debug Styling**: Updated debug panel styling to use `em` units with reduced padding
942
+
943
+ ### Performance
944
+ - **Non-blocking UI**: Tree renders immediately while search indexing occurs during browser idle time
945
+ - **Improved Large Dataset Handling**: Async indexing prevents UI freezing with large data sets
946
+ - **Batch Processing**: Search indexing processes nodes in batches during idle periods
947
+ - **Graceful Degradation**: Falls back to `setTimeout` on browsers without `requestIdleCallback` support
948
+
949
+ ### Documentation
950
+ - Added comprehensive documentation for async search indexing
951
+ - Added warning about search indexing requirements
952
+ - Enhanced API documentation with new properties and statistics
953
+ - Updated performance section highlighting async capabilities