@keenmate/svelte-treeview 5.0.0-rc09 → 5.0.0-rc11

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 (53) hide show
  1. package/CHANGELOG.md +931 -866
  2. package/README.md +362 -1100
  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 -142
  9. package/dist/components/ContextMenuDivider.svelte +2 -2
  10. package/dist/components/ContextMenuItem.svelte +58 -10
  11. package/dist/components/ContextMenuLevel.svelte +131 -0
  12. package/dist/components/ContextMenuLevel.svelte.d.ts +32 -0
  13. package/dist/components/Node.svelte +491 -486
  14. package/dist/components/Node.svelte.d.ts +1 -1
  15. package/dist/components/Tree.svelte +1323 -1276
  16. package/dist/components/Tree.svelte.d.ts +77 -69
  17. package/dist/components/TreeProvider.svelte +1 -0
  18. package/dist/constants.generated.d.ts +1 -1
  19. package/dist/constants.generated.js +1 -1
  20. package/dist/core/TreeController.svelte.d.ts +84 -53
  21. package/dist/core/TreeController.svelte.js +540 -126
  22. package/dist/index.d.ts +1 -1
  23. package/dist/ltree/ltree-node.svelte.js +2 -2
  24. package/dist/ltree/ltree.svelte.d.ts +1 -1
  25. package/dist/ltree/ltree.svelte.js +94 -9
  26. package/dist/ltree/types.d.ts +10 -0
  27. package/dist/styles/animations.css +17 -0
  28. package/dist/styles/{_base.css → base.css} +14 -11
  29. package/dist/styles/checkbox.css +96 -0
  30. package/dist/styles/context-menu.css +117 -0
  31. package/dist/styles/controls.css +4 -0
  32. package/dist/styles/dark-mode.css +44 -0
  33. package/dist/styles/debug.css +45 -0
  34. package/dist/styles/drag-drop.css +170 -0
  35. package/dist/styles/drop-zones.css +270 -0
  36. package/dist/styles/floating.css +5 -0
  37. package/dist/styles/loading.css +36 -0
  38. package/dist/styles/main.css +38 -31
  39. package/dist/styles/node.css +63 -0
  40. package/dist/styles/states.css +123 -0
  41. package/dist/styles/{_toggle-icons.css → toggle-icons.css} +29 -29
  42. package/dist/styles/variables.css +217 -0
  43. package/dist/styles.css +887 -869
  44. package/package.json +9 -3
  45. package/dist/styles/_checkbox.css +0 -83
  46. package/dist/styles/_context-menu.css +0 -134
  47. package/dist/styles/_debug.css +0 -45
  48. package/dist/styles/_drag-drop.css +0 -174
  49. package/dist/styles/_drop-zones.css +0 -270
  50. package/dist/styles/_loading.css +0 -40
  51. package/dist/styles/_node.css +0 -60
  52. package/dist/styles/_states.css +0 -92
  53. package/dist/styles/_variables.css +0 -189
package/CHANGELOG.md CHANGED
@@ -1,866 +1,931 @@
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-rc09] - 2026-06-07
11
-
12
- ### Added
13
- - **`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.
14
- - **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 decoupled highlight 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 }`.
15
- - **`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.
16
- - **`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.
17
- - **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.
18
- - **`--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.
19
- - **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.
20
- - **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`.
21
-
22
- ### Changed
23
- - **`!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.).
24
- - **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.
25
- - **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** 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).
26
- - **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.
27
- - **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.
28
- - **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.
29
- - **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.
30
- - **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`.
31
- - **`--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.
32
- - **`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.
33
- - **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`.
34
- - **`--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.
35
-
36
- ### Breaking
37
- - **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.
38
- - **`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.
39
- - **`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.
40
- - **`./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`.
41
- - **`--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).
42
- - **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`.
43
- - **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`.
44
-
45
- ### Fixed
46
- - **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.
47
- - **`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 effecthighlighted 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.
48
- - **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.
49
- - **`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.
50
-
51
- ## [5.0.0-rc08] - 2026-05-27
52
-
53
- ### Added
54
- - **`{ 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.
55
- - **Array variants on expand/collapse methods**: `expandNodes`, `collapseNodes`, `expandAll`, and `collapseAll` now accept `string | string[]`. Single emit per call regardless of array length.
56
- - **`{ 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.
57
- - **`{ 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()`.
58
- - **`/examples/silent-highlight` demo page**: URL-restore scenario with loud vs. silent toggle showing how form data is preserved in silent mode.
59
- - **`/examples/expand-collapse` demo page**: Demonstrates array variants and exclusive focus mode.
60
-
61
- ## [5.0.0-rc07] - 2026-05-23
62
-
63
- ### Added
64
- - **`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 truthyindependent of `isSelectableMember`, so non-selectable nodes can still ship checked.
65
- - **`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.
66
-
67
- ### Fixed
68
- - **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.
69
-
70
- ## [5.0.0-rc06] - 2026-03-31
71
-
72
- ### Added
73
- - **`showCheckboxes` prop**: Renders a checkbox before each selectable node with custom styling and indeterminate support.
74
- - **`checkboxMode` prop** (`'independent'` | `'cascade'`): Controls whether checking a parent cascades to all descendants. Indeterminate state shown when partial.
75
- - **`beforeCheckboxToggleCallback` interceptor**: Cancel or override checkbox toggles.
76
- - **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.
77
- - **`onHighlightChange` event**: Fires when highlighted paths change (Ctrl+click, Shift+click, etc.).
78
- - **Bulk checkbox via highlight**: When multiple nodes are highlighted and a checkbox in the highlight is clicked, all highlighted nodes toggle together.
79
- - **Shift+Arrow/Home/End keyboard highlight**: Shift+ArrowDown/Up extends highlight range by one sibling, Shift+Home/End extends to first/last visible node.
80
- - **PageUp/PageDown navigation**: Jumps 10 visible nodes forward/back. Shift+PageUp/PageDown extends highlight by 10 nodes.
81
- - **`ltree-selected-highlight` CSS class**: Explorer-style blue background highlight. Customizable via `--ltree-highlight-bg` and `--ltree-highlight-color`.
82
- - **Interaction example page** (`/examples/interaction`): Interactive demos for click behavior, checkboxes, multi-select, and keyboard navigation. All settings persisted to localStorage.
83
-
84
- ### Fixed
85
- - **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.
86
- - **Svelte proxy equality warning on checkbox toggle**: `_setFocusedNode` now compares by path instead of object identity to avoid `state_proxy_equality_mismatch`.
87
-
88
- ### Breaking
89
- - **`selectedNode` `focusedNode`**: Renamed prop and bindable. The single focused node (last clicked / arrow-keyed to).
90
- - **`selectedPaths` repurposed**: Now represents checkbox-only data state. For click/highlight multi-select, use `highlightedPaths`.
91
- - **`highlightedPaths` (new)**: Replaces old `selectedPaths` for Ctrl+click / Shift+click UI highlight.
92
- - **`selectedNodeClass` `highlightedNodeClass`**: CSS class applied to highlighted nodes.
93
- - **`focusedNodeClass` (new)**: CSS class applied to the single focused node.
94
- - **`onSelectionChange` repurposed**: Now fires on checkbox selection changes only. Use `onHighlightChange` for highlight changes.
95
- - **`selectNode()` / `selectNodes()` deprecated**: Use `highlightNode()` / `highlightNodes()` instead.
96
-
97
- ## [5.0.0-rc05] - 2026-03-26
98
-
99
- ### Added
100
- - **`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.
101
-
102
- ### Breaking
103
- - **`shouldToggleOnNodeClick` removed**: Replace `shouldToggleOnNodeClick={true}` with `clickBehavior="expand-and-focus"` (default) and `shouldToggleOnNodeClick={false}` with `clickBehavior="select"`.
104
-
105
- ## [5.0.0-rc04] - 2026-03-12
106
-
107
- ### Added
108
- - **`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>`.
109
- - **Bulk subtree operations**: `insertBranch(parentPath, nodes)`, `replaceBranch(path, nodes)`, `deleteBranch(path)` on TreeController — perform subtree-level add/replace/remove with a single tree emission.
110
- - **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.
111
- - **Branch-operations example** (`/examples/branch-operations`): Server-simulated cut/paste workflow demonstrating `deleteBranch` + `insertBranch` with clipboard integration.
112
-
113
- ### Fixed
114
- - **`.ltree-container` missing `outline: none`**: Container div now suppresses browser focus outline.
115
-
116
- ## [5.0.0-rc03] - 2026-03-08
117
-
118
- ### Added
119
- - **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.
120
- - **`selectedPaths` bindable prop**: `Set<string>` of all selected node paths. Two-way binding for external control.
121
- - **`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.
122
- - **`onSelectionChanged` event**: `(paths: Set<string>, nodes: LTreeNode<T>[]) => void` fires when selection changes.
123
- - **`contextMenuCallback` 3rd parameter**: Now receives `selectedNodes?: LTreeNode<T>[]` enables selection-aware context menus.
124
- - **Public multi-select API on TreeController**: `selectNode(path, mode)`, `selectNodes(paths)`, `deselectAll()`, `getSelectedNodes()`, `isNodeSelected(path)`.
125
- - **`SelectionModifiers` type export**: `{ ctrl: boolean; shift: boolean }` for modifier-aware click handling.
126
- - **`.ltree-multi-selected` CSS class**: Styling for nodes in a multi-selection.
127
- - **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.
128
- - **Named dividers**: `{ divider: true, label: 'Section' }` renders as `──── Section ────`
129
- - **Keyboard shortcuts**: `shortcut` field renders right-aligned hint and activates on keypress when menu is open
130
- - **Submenus**: `children: ContextMenuEntry[]` opens nested menu on hover
131
- - **Visibility control**: `isVisible: false` hides items in callback approach (snippet approach uses `{#if}`)
132
- - **Flexible styling**: `className` replaces dedicated `danger` boolean — use `className="danger"` or any custom class
133
- - **Async onclick**: `onclick` supports `Promise<void>` return with try/catch error handling
134
- - **`ContextMenuItemC` / `ContextMenuDividerC` Svelte components**: Declarative context menu building inside the `contextMenu` snippet. Supports nested children slot for submenus.
135
- - **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.
136
- - **`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.
137
- - **`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`.
138
-
139
- ### Fixed
140
- - **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.
141
- - **`.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.
142
-
143
- ## [5.0.0-rc02] - 2026-03-05
144
-
145
- ### Architecture (Breaking)
146
- - **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.
147
- - **Drop position naming**: `'above'`/`'below'` renamed to `'before'`/`'after'` throughout (`DropPosition` type, CSS classes, events). `'child'` unchanged.
148
- - **Canvas rendering extracted**: Canvas-based rendering (`CanvasTree`, layouts, themes) moved to separate package `@keenmate/svelte-treeview-canvas`.
149
-
150
- ### Added
151
- - **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()}`.
152
- - **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.
153
- - **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.
154
- - **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.
155
- - **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.
156
- - **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.
157
- - **`overscroll-behavior: contain`** on `.ltree-virtual-scroll` container.
158
- - **`isCollapsibleMember` / `getIsCollapsibleCallback`**: Per-node collapsibility control.
159
- - **`getIsDraggableCallback`**: Dynamic per-node draggability.
160
- - **`applyChanges()` batch method**: Apply multiple tree edits in a single operation.
161
- - **`_rev` change tracking** on nodes for efficient keyed rendering.
162
-
163
- ### Fixed
164
- - **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`.
165
- - **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.
166
- - **"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.
167
- - **Empty tree drop placeholder ignoring `dragDropMode`**: `handleEmptyTreeDragOver/Drop/TouchEnd` now check `dragDropMode !== 'none'` before activating the drop placeholder.
168
- - **`treeId` not synced on prop changes**: Added `$effect` in `Tree.svelte` to sync `treeId` prop to controller after mount.
169
- - **`dropZoneStart` not used in glow mode**: `calculateGlowPosition` now uses `dropZoneStart` to compute the child zone threshold instead of hardcoded `width/2`.
170
- - **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()`.
171
- - **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()`.
172
-
173
- ### Changed
174
- - **Custom Layout Example — Explicit Drop Zones**: Replaced invisible spatial detection with visible drop zone pills (Before / After / Child) on dendrograms.
175
-
176
- ## [4.7.2] - 2026-02-17
177
-
178
- ### Fixed
179
- - **`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}`.
180
-
181
- ## [4.7.1] - 2026-02-17
182
-
183
- ### Changed
184
- - **Drag and Drop Disabled by Default**: `dragDropMode` now defaults to `'none'` instead of `'both'`
185
- - Most trees are read-only, so this is a safer default
186
- - To enable drag and drop, explicitly set `dragDropMode="both"` (or `"self"` / `"cross"`)
187
-
188
- ### Fixed
189
- - Removed development `console.log` statements from `Tree.svelte` and `ltree.svelte.ts`
190
-
191
- ## [4.7.0] - 2026-02-11
192
-
193
- ### Added
194
- - **Per-Node Drop Position Restrictions**: New feature to control which drop positions are allowed per node
195
- - `allowedDropPositionsMember` - Property name mapping for static data (e.g., from server)
196
- - `getAllowedDropPositionsCallback` - Callback for dynamic position logic based on node type
197
- - `DropPosition` type exported: `'above' | 'below' | 'child'`
198
- - Example use cases:
199
- - Trash folder: `['child']` only (can drop INTO, not above/below)
200
- - Files: `['above', 'below']` only (can't drop INTO a file)
201
- - Folders: `undefined` or `[]` (all positions allowed - default)
202
- - Works with both glow mode (snaps to nearest allowed position) and floating mode (only shows allowed zones)
203
- - Backwards compatible: `undefined`/empty array = all positions allowed
204
-
205
- ### Example Usage
206
- ```typescript
207
- // Callback approach (dynamic logic)
208
- function getAllowedDropPositionsCallback(node: LTreeNode<T>): DropPosition[] | null {
209
- if (node.data?.type === 'file') return ['above', 'below'];
210
- if (node.data?.type === 'trash') return ['child'];
211
- return undefined; // all positions allowed
212
- }
213
-
214
- // Member approach (server data)
215
- const data = [
216
- { path: '1', name: 'Trash', allowedDropPositions: ['child'] },
217
- { path: '2', name: 'Document.pdf', allowedDropPositions: ['above', 'below'] },
218
- { path: '3', name: 'Projects' }, // all positions (default)
219
- ];
220
- ```
221
-
222
- ## [4.6.0] - 2026-02-11
223
-
224
- ### Added
225
- - **Flat Rendering Mode**: New `useFlatRendering` prop (default: `true`) for significantly faster rendering
226
- - Renders all visible nodes in a single `{#each}` loop instead of recursive components
227
- - Initial render ~12x faster (300ms → 25ms for 5500 nodes)
228
- - Progressive rendering batches initial load to prevent UI freeze
229
- - Documentation: `docs/FLAT_MODE_PERFORMANCE.md`
230
- - **Context-Based Node Configuration**: Moved stable callbacks and config to Svelte context
231
- - `NodeCallbacks<T>` interface for all event handlers (click, drag, drop, touch)
232
- - `NodeConfig` interface for stable configuration (icons, classes, drop zone settings)
233
- - Eliminates inline arrow function re-renders (5500 nodes no longer re-evaluate on array changes)
234
- - Exported types: `NodeCallbacks`, `NodeConfig` from package index
235
- - **Async beforeDropCallback**: `beforeDropCallback` now supports async/Promise return values
236
- - Enables showing confirmation dialogs before completing a drop
237
- - Can await user input to decide whether to cancel, proceed, or modify the drop
238
- - Example: `async (dropNode, draggedNode, position) => { return await showDialog(); }`
239
-
240
- ### Enhanced
241
- - **Exponential Batch Sizing**: Progressive rendering now uses exponential batching (20 → 40 → 80 → 160...)
242
- - First batch (20 nodes) renders instantly for immediate visual feedback
243
- - Batch size doubles each frame up to `maxBatchSize` (default 500)
244
- - New props: `initialBatchSize` (default 20), `maxBatchSize` (default 500)
245
- - Replaces fixed `renderBatchSize` prop
246
- - Expand/collapse on large trees (>1000 nodes): Immediate add to minimize diffs
247
- - **Tree Editor Example**: Enhanced with async drop validation dialog
248
- - Demonstrates async `beforeDropCallback` with native confirm dialog
249
- - Shows warning when drop position is modified
250
-
251
- ### Fixed
252
- - **Scroll Highlight Stacking**: Fixed multiple nodes staying highlighted when rapidly clicking prev/next
253
- - Previous highlight now immediately cleared when navigating to new node
254
- - Timeout properly cancelled to prevent stale highlight removal
255
- - **Flat Mode Node Updates After Move**: Fixed nodes not re-rendering after `moveNode()` in flat mode
256
- - Keyed each now uses `node.id + path + hasChildren` to detect moved nodes
257
- - Moved nodes now correctly update their indentation level
258
- - Parent nodes correctly update toggle icon when children are moved away
259
-
260
- ### Changed
261
- - **Default Rendering Mode**: `useFlatRendering` now defaults to `true` (was `false`)
262
- - **Default Progressive Render**: `progressiveRender` now defaults to `true` (was `false`)
263
-
264
- ## [4.5.0] - 2026-02-09
265
-
266
- ### Added
267
- - **Drop Zone Layout Configuration**: New props to customize drop zone appearance and positioning
268
- - `dropZoneLayout` - Controls zone arrangement with 5 layout options:
269
- - `'around'` (default) - Above zone on top, Below/Child zones on bottom
270
- - `'above'` - All 3 zones in a horizontal row above the node
271
- - `'below'` - All 3 zones in a horizontal row below the node
272
- - `'wave'` - Zones stacked vertically (above/child/below) with fixed width
273
- - `'wave2'` - Diagonal wave pattern with Above/Below offset 7% to the left
274
- - `dropZoneStart` - Number (0-100) controlling where zones start horizontally (default: 33%)
275
- - `dropZoneMaxWidth` - Max width in pixels for wave layouts (default: 120px)
276
- - **New TypeScript Type**: `DropZoneLayout` type exported from `types.ts`
277
- - **Mobile Touch Drag and Drop**: Full touch support for drag and drop on mobile devices
278
- - Long-press (300ms) to initiate drag - distinguishes from tap and scroll
279
- - Visual ghost element follows finger during drag showing the dragged node
280
- - Drop target highlighting using existing `dragOverNodeClass` prop
281
- - Haptic feedback via `navigator.vibrate()` when drag starts (on supported devices)
282
- - Automatic cancellation if finger moves >10px before long-press completes (allows normal scrolling)
283
- - Works alongside existing desktop HTML5 drag and drop - same `onNodeDrop` callback for both
284
- - **Drop Placeholder for Empty Trees**: When dragging nodes to an empty tree, a drop zone placeholder appears
285
- - Shows visual drop target in empty trees during drag operations
286
- - Works with both desktop (HTML5 DnD) and touch drag
287
- - Customizable via `dropPlaceholder` snippet prop for custom content
288
- - `onNodeDrop` callback receives `null` as `dropNode` for root-level drops into empty trees
289
- - **Drop Position Indicators**: Visual indicators showing exactly where dropped items will be placed
290
- - Three drop positions per node: `'above'` (sibling before), `'child'` (as child), `'below'` (sibling after)
291
- - Absolutely positioned indicators on right half of node to prevent layout shifts
292
- - Position calculated from mouse Y: top 25% = above, middle 50% = child, bottom 25% = below
293
- - New CSS classes: `.ltree-drop-indicators`, `.ltree-drop-above`, `.ltree-drop-child`, `.ltree-drop-below`
294
- - **Root Drop Zone**: Drop zone that appears at bottom of non-empty trees during drag
295
- - Allows dropping items as root-level nodes in trees that already have content
296
- - New CSS class: `.ltree-root-drop-zone`
297
- - **Drag Drop Mode Control**: New `dragDropMode` prop to control allowed drag operations
298
- - `'none'` - Drag and drop disabled
299
- - `'self'` - Only within same tree
300
- - `'cross'` - Only between different trees
301
- - `'both'` - Both self and cross-tree (default)
302
- - **Sibling Order Support**: New `orderMember` prop for explicit sibling ordering
303
- - Specifies which field in user data contains the sort order value
304
- - Used by default sort to order siblings within the same parent
305
- - Required for proper above/below positioning in drag-drop tree editors
306
- - Example: `orderMember="sortOrder"` with data like `{ path: '1.1', name: 'A', sortOrder: 10 }`
307
- - **Tree Editor Helper Methods**: New methods for building tree editors
308
- - `getChildren(parentPath)` - Get direct children of a node
309
- - `getSiblings(path)` - Get all siblings of a node (including itself)
310
- - `getNodeByPath(path)` - Get a node by its path
311
- - `refreshSiblings(parentPath)` - Re-sort children of a parent using orderMember
312
- - `refreshNode(path)` - Trigger re-render for a specific node
313
- - **Tree Editor Mutation Methods**: New methods for modifying tree structure
314
- - `addNode(parentPath, data, pathSegment?)` - Add a new node to the tree
315
- - `moveNode(sourcePath, targetPath, position)` - Move a node with full subtree to a new location
316
- - Supports 'above', 'below', and 'child' positions
317
- - Automatically updates paths of all descendants
318
- - Calculates order values when orderMember is set
319
- - `removeNode(path, includeDescendants?)` - Remove a node from the tree
320
- - **Example Pages**: New `/examples` route with interactive demos (same look-and-feel as web-multiselect)
321
- - Landing page with feature cards linking to 7 example sections
322
- - Basic Examples: tree rendering, expand level control, scroll to path, programmatic expand/collapse
323
- - Drag & Drop: two-tree drag demo, touch drag instructions, drop placeholder customization
324
- - Context Menu: callback-based menus, dynamic items, icons, disabled states, dividers
325
- - Search & Filter: live filtering with `searchText`, `searchNodes()` query method
326
- - Theming: CSS variable reference, theme examples (default, purple, dark, green)
327
- - Data Structures: path-based hierarchy, custom separators, insert result validation
328
- - Tree Editor: add/move/remove nodes with drag-drop and orderMember support
329
- - **Drop Indicator Arrows**: Visual arrow indicators for glow-mode drop zones
330
- - Arrows positioned at 66% of row width, centered vertically
331
- - Uses Lucide SVG icons as data URIs: `arrow-big-up`, `arrow-big-down`, `arrow-big-right-dash`
332
- - Child arrow rotated 45° for diagonal pointing effect
333
- - Fully customizable via SCSS variables
334
- - **Ctrl+Drag Copy Operation**: Full support for copying nodes via Ctrl+drag
335
- - `allowCopy` prop enables Ctrl+drag to copy instead of move
336
- - `autoHandleCopy` prop (default: true) controls whether Tree auto-handles same-tree copies
337
- - `true`: Tree creates copy with generated ID (`{id}_copy_{timestamp}`) - good for batch/offline mode
338
- - `false`: User callback handles copy (for DB/API integration) - good for online/live mode
339
- - Copy operations respect drop position (above/below/child) just like moves
340
- - Visual feedback: `.ltree-drop-copy` class applied during copy operations
341
- - **Position Support for copyNodeWithDescendants**: Enhanced copy method now supports sibling positioning
342
- - New optional parameters: `siblingPath` and `position` ('above' | 'below')
343
- - Copies can be placed at specific positions relative to siblings
344
- - Uses same `orderMember` logic as `moveNode` for consistent ordering
345
- - **Logging Infrastructure**: Categorized logging using vendored loglevel library
346
- - Six log categories: `LTREE:INIT`, `LTREE:DATA`, `LTREE:RENDER`, `LTREE:INDEX`, `LTREE:DRAG`, `LTREE:UI`
347
- - Color-coded console output with timestamps for easy debugging
348
- - Disabled by default (silent mode) for production
349
- - Exported utilities: `enableLogging()`, `disableLogging()`, `setLogLevel()`, `setCategoryLevel()`
350
- - UI logging: node clicks, expand/collapse, selection changes, context menu
351
- - Drag logging: drag start/end, drop operations, touch drag events
352
- - Render logging: progressive rendering frame stats
353
- - Index logging: async search indexing progress
354
- - New `/dev/logging` demo page for testing log levels and categories
355
- - **Performance Logging**: Dedicated performance measurement utilities
356
- - Measures key operations: `insertArray` (conversion/sort/insert phases), `filterNodes`, `expandAll`, `collapseAll`
357
- - Output includes duration, item count, per-item time, and items/sec throughput
358
- - Summary view showing breakdown by phase with percentages
359
- - Configurable threshold to only log operations slower than X ms
360
- - Exported utilities: `enablePerfLogging()`, `disablePerfLogging()`, `setPerfThreshold(ms)`
361
- - Browser console access: `window.components['svelte-treeview'].perf.enable()`
362
- - Purple color-coded output for easy identification
363
- - **Global Runtime API**: `window.components['svelte-treeview']` for browser console access
364
- - `config` - Package info (name, version, author, license, repository, homepage) read from package.json at build time
365
- - `version()` - Returns current version string
366
- - `logging.enableLogging()` / `logging.disableLogging()` - Toggle all logging
367
- - `logging.setLogLevel(level)` - Set level for all categories
368
- - `logging.setCategoryLevel(category, level)` - Set level for specific category
369
- - `logging.getCategories()` - List available log categories
370
- - **Container-Scoped Scrolling**: New `containerScroll` option for `scrollToPath()`
371
- - `scrollToPath(path, { containerScroll: true })` scrolls only within the nearest scrollable ancestor
372
- - Prevents page-level scrolling when tree is inside a scrollable container
373
- - Automatically finds the scrollable parent element (overflow: auto/scroll)
374
- - Useful for search result navigation without disrupting page position
375
-
376
- ### Enhanced
377
- - **Drop Zone Styling**: Improved visual feedback during drag operations
378
- - Semi-transparent zones (0.25 opacity) that become solid (0.85) when hovered
379
- - Modern pastel color palette: sage green for Above, peach/coral for Below, lavender for Child
380
- - Interactive controls in `/examples/drag-drop` to test all layout configurations
381
- - **Drop Zone SCSS Variables**: Full customization of drop zone appearance via SCSS variables
382
- - `$drop-zone-border-radius` - Border radius for all zones (default: 0)
383
- - Per-zone variables for backgrounds, colors, and shadows in both inactive and active states:
384
- - Above: `$drop-zone-above-bg`, `$drop-zone-above-color`, `$drop-zone-above-active-bg`, `$drop-zone-above-active-color`, `$drop-zone-above-active-shadow`
385
- - Below: `$drop-zone-below-bg`, `$drop-zone-below-color`, `$drop-zone-below-active-bg`, `$drop-zone-below-active-color`, `$drop-zone-below-active-shadow`
386
- - Child: `$drop-zone-child-bg`, `$drop-zone-child-color`, `$drop-zone-child-active-bg`, `$drop-zone-child-active-color`, `$drop-zone-child-active-shadow`
387
- - **Drop Zone Positioning**: Moved drop zones from inside `.ltree-node-content` to `.ltree-node-row` level
388
- - Eliminates padding-related gaps that made zones hard to reach
389
- - More predictable positioning relative to the full row width
390
- - **Wave2 Layout Overlap**: Added 10% overlap for Above/Below zones in wave2 layout
391
- - Ensures first node's Above zone and last node's Below zone are always reachable
392
- - Child zone shrunk to 80% height to accommodate overlap without zone collision
393
- - **dropZoneStart Flexibility**: Now accepts both number (percentage) and string (any CSS value)
394
- - Number: treated as percentage (e.g., `33` `33%`)
395
- - String: used as-is (e.g., `"33%"`, `"50px"`, `"3rem"`)
396
- - **Touch UX**: Added CSS properties to prevent text selection during touch drag
397
- - `-webkit-user-select: none` and `-webkit-touch-callout: none` on node content
398
- - **Ghost Element Styling**: New `.ltree-touch-ghost` CSS class with customizable CSS variables
399
- - `--tree-ghost-bg`: Background color (default: rgba(59, 130, 246, 0.9))
400
- - `--tree-ghost-color`: Text color (default: white)
401
- - **Drop Placeholder Styling**: New `.ltree-drop-placeholder` and `.ltree-drop-placeholder-content` CSS classes
402
- - **Drop Indicator Arrow SCSS Variables**: Full customization of arrow indicators via SCSS variables
403
- - `$drop-arrow-above`, `$drop-arrow-below`, `$drop-arrow-child` - SVG data URIs for each direction
404
- - `$drop-arrow-size` - Arrow size (default: 24px)
405
- - `$drop-arrow-position` - Horizontal position within row (default: 66%)
406
- - `$drop-arrow-above-rotation`, `$drop-arrow-below-rotation`, `$drop-arrow-child-rotation` - Rotation angles
407
- - **Search Example Page Redesign**: Merged filter and search cards into unified search experience
408
- - Single search input with live filtering and result navigation
409
- - Prev/Next buttons to traverse search results with wrap-around
410
- - Result counter showing "X of Y" position indicator
411
- - Keyboard navigation: Enter = next result, Shift+Enter = previous
412
- - Clickable result list with active item highlighting
413
- - Auto-scroll to first result when searching
414
-
415
- ### Fixed
416
- - **Empty Tree Drop Placeholder**: Fixed drop placeholder not appearing when dragging to empty trees
417
- - Added missing `ondragenter` handler to empty state divs
418
- - Added `min-height: 60px` to `.ltree-empty-state` to ensure drop target is always reachable
419
- - **Drag-Drop Demo ID/Path Mismatch**: Fixed bug where second node drop to first node didn't work on first try
420
- - Root cause: `nextId++` post-increment caused id and path to use different values
421
- - Fixed by extracting `const itemId = nextId++` before using in object properties
422
- - **Tree Data Reset**: Fixed `insertArray` not clearing existing tree data when called with new/empty data
423
- - Previously, setting `data = []` would not clear the tree - existing nodes remained visible
424
- - Now `insertArray` properly resets root children, nodeCount, and maxLevel before inserting
425
- - **Example Pages Data Insertion**: Added `isSorted={true}` to all example page Tree components
426
- - Prevents "Could not find parent node" errors caused by `sortCallback` sorting data before insertion
427
- - When `sortCallback` alphabetizes data, children could be inserted before parents (e.g., "AuthService" before "Services")
428
- - `isSorted={true}` tells the tree to skip pre-sorting and only use `sortCallback` for display ordering
429
- - **Search Example Async Index**: Added note explaining that search index is built asynchronously
430
- - Added Enter key support for better UX when retrying searches
431
- - Users are now informed to wait a moment if no results appear immediately after page load
432
- - **Search Example Reactivity**: Made "Search Nodes (Query)" input reactive
433
- - Added `$effect` to automatically trigger search when input changes
434
- - **Glow Mode Border Radius**: Fixed border-radius appearing on glow drop indicators during drag
435
- - Changed `$tree-node-content-border-radius` default from `4px` to `0`
436
- - **Search Example Documentation**: Fixed incorrect prop name in search configuration table
437
- - Changed `searchValueCallback` `getSearchValueCallback` (correct prop name)
438
- - Fixed callback signature from `(item: T) => string` to `(node: LTreeNode<T>) => string`
439
- - **LTreeNode Type Export**: Added `LTreeNode` re-export from `types.ts`
440
- - Fixes import errors when using `import type { LTreeNode } from '$lib/ltree/types'`
441
- - **Search Example Endless Loop**: Fixed `$effect` causing infinite loop on search
442
- - Used Svelte's `untrack()` to prevent reactive state updates from re-triggering the effect
443
- - Effect now only reacts to `searchText` changes, not internal state mutations
444
- - **Search Example Container Scroll**: Fixed prev/next navigation scrolling the entire page
445
- - Now uses `scrollToPath(path, { containerScroll: true })` for container-scoped scrolling
446
- - **Critical Performance Bug in insertArray**: Fixed O(n²) algorithm causing 85+ second load times
447
- - Progressive render check was iterating all remaining nodes for every node at expandLevel
448
- - With 17,000 nodes this caused ~145 million iterations instead of ~34,000
449
- - Fix: Pre-compute last expandLevel index once, then use simple index comparison
450
- - Result: Load time reduced from 85+ seconds to under 1 second
451
- - **Global API Constants**: Fixed `__PACKAGE_NAME__ is not defined` error when using library in other projects
452
- - Vite `define` constants only work during dev, not when library is built with `svelte-package`
453
- - Added `scripts/generate-constants.js` to bake package.json values into `constants.generated.ts`
454
-
455
- ### Important - Svelte 5 Performance
456
-
457
- **Use `$state.raw()` for large datasets passed to Tree component**
458
-
459
- When passing large arrays (1000+ items) to the Tree component, use `$state.raw()` instead of `$state()` to avoid severe performance degradation:
460
-
461
- ```typescript
462
- // SLOW - Svelte deeply proxies all 8000+ objects, causing 5000x slowdown
463
- let treeNodes = $state<TreeNode[]>([])
464
- treeNodes = response.data // Each item becomes a Proxy
465
-
466
- // FAST - Array is reactive but items remain plain objects
467
- let treeNodes = $state.raw<TreeNode[]>([])
468
- treeNodes = response.data // Items stay as plain objects
469
- ```
470
-
471
- **Why this matters:**
472
- - `$state()` creates deep proxies - every nested object becomes a Proxy
473
- - Tree's `insertArray()` accesses multiple properties on each data item
474
- - With 8000 items × ~10 property accesses = 80,000 proxy operations
475
- - Proxy overhead: ~2.2ms per item vs ~0.0004ms for plain objects (5,500x slower)
476
-
477
- **Symptoms of this issue:**
478
- - Tree takes 15-90+ seconds to render with thousands of items
479
- - Console shows `[Violation] 'message' handler took XXXXms`
480
- - Same data loads instantly in isolated test environment
481
-
482
- **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.
483
- - Build now runs `npm run generate-constants` before `svelte-package`
484
- - **Glow Mode Border Radius**: Added `!important` to `border-radius: 0` on glow classes to ensure override
485
- - Removed `border-radius: 4px !important` from `.ltree-dragover-glow` class
486
- - Removed `border-radius` from `.ltree-node-content` transition to prevent animation glitch
487
- - **Glow Mode Drop Validation**: Fixed glow showing on invalid drop targets
488
- - When `dragDropMode='cross'` prevents drops, glow no longer appears on hover
489
- - Cleared `hoveredNodeForDrop` state when drop validation fails
490
- - Debug logging for drop rejection now gated behind `shouldDisplayDebugInformation`
491
- - **Glow Mode Border Radius Transition**: Fixed double border artifact on node hover during drag
492
- - Added `border-radius` to transition property for smooth animation
493
- - Eliminates visual conflict between base rounded corners and glow's straight corners
494
- - **Ctrl+Drag dropEffect Timing**: Fixed copy operation failing with `dropEffect: none`
495
- - Root cause: Node's `ondragover` read stale `dropOperation` prop before Tree updated it
496
- - Fix: Node now reads `event.ctrlKey` directly to calculate dropEffect immediately
497
- - Also added dropEffect confirmation in `ondrop` handlers for spec compliance
498
- - **Missing allowCopy Prop in Recursive Node**: Fixed `allowCopy` not being passed to child nodes
499
- - Caused Ctrl+drag to fail on non-root nodes since they defaulted to `allowCopy=false`
500
- - Added `{allowCopy}` to recursive Node component call
501
-
502
- ### Changed
503
- - **BREAKING: onNodeDrop Signature**: Callback signature updated to include drop position
504
- - Before: `onNodeDrop?: (dropNode, draggedNode, event) => void`
505
- - After: `onNodeDrop?: (dropNode, draggedNode, position, event) => void`
506
- - `position` is `'above'`, `'below'`, or `'child'` indicating where item should be placed
507
- - `dropNode` can be `null` when dropping into empty tree or root drop zone
508
-
509
- ## [4.4.0] - 2025-10-02
510
-
511
- ### Added
512
- - **External Update Method**: New `update()` method for programmatic prop updates from vanilla JavaScript
513
- - Allows external code to update component props without Svelte reactivity
514
- - Accepts partial object with any Tree props (excluding snippets/templates)
515
- - Useful for HTML/JavaScript integration and dynamic configuration
516
- - Example: `tree.update({ searchText: 'query', expandLevel: 3, data: newData })`
517
-
518
- ### Fixed
519
- - **Search Functionality**: Fixed search filtering in context-menu dev page
520
- - Added missing `searchValueMember="name"` prop to enable proper search indexing
521
- - Search now correctly filters nodes by name instead of filtering everything out
522
-
523
- ### Changed
524
- - **Code Cleanup**: Renamed internal "trie" references to "tree" for consistency
525
- - Updated variable names in Tree.svelte, Node.svelte, and ltree-demo.ts
526
- - Removed "trie" from package.json keywords
527
- - Improved code readability and naming consistency throughout codebase
528
-
529
- ## [4.3.1] - 2025-09-25
530
-
531
- ### Enhanced
532
- - **Async Callback Support**: Context menu callbacks now fully support async operations
533
- - Updated `callback: () => void | Promise<void>` signature in `ContextMenuItem` interface
534
- - Added automatic error handling for async callbacks with try/catch wrapper
535
- - Menu item clicks properly await async operations before completing
536
- - Errors in async callbacks are logged to console for debugging
537
- - **Robust Error Handling**: Async callback failures don't break menu functionality
538
- - Failed async operations are caught and logged automatically
539
- - Developers can implement custom error handling within their callbacks
540
- - Menu stays open on errors, allowing users to retry actions
541
- - **Enhanced Dev Examples**: Added comprehensive async callback demonstrations
542
- - Copy action with simulated network delay
543
- - New folder creation with error simulation (20% failure rate)
544
- - Database backup with long-running operation simulation
545
- - Shows patterns for success/failure handling and conditional menu closing
546
-
547
- ### Documentation
548
- - **Async Patterns**: Examples showing proper async callback implementation
549
- - **Error Handling**: Best practices for managing async operation failures
550
- - **Menu Control**: Demonstrated conditional closing based on operation success/failure
551
-
552
- ## [4.3.0] - 2025-09-25
553
-
554
- ### Added
555
- - **Enhanced Context Menu Control**: Context menu callback now receives `closeMenuCallback` parameter for programmatic menu control
556
- - `contextMenuCallback?: (node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[]`
557
- - Developers can now control when/if context menu closes after menu item actions
558
- - Enables conditional closing patterns (e.g., don't close on cancel, only on success)
559
- - Public `closeContextMenu()` method exported for external control
560
- - **Context Menu Item Styling**: New `className?: string` property in `ContextMenuItem` interface
561
- - Apply custom CSS classes to individual menu items for styling
562
- - Supports multiple classes (space-separated strings)
563
- - Example: `className: 'text-danger fw-bold'` for destructive actions
564
- - **Enhanced Dev Examples**: Updated context menu examples to demonstrate new features
565
- - Conditional menu closing patterns for different action types
566
- - CSS class styling demonstrations with Bootstrap classes
567
- - Improved UX patterns showing when to close vs keep menu open
568
-
569
- ### Enhanced
570
- - **Flexible Menu Behavior**: Context menu now supports various interaction patterns
571
- - Immediate close after action completion
572
- - Conditional close based on user confirmation
573
- - Persistent menu for multi-step operations
574
- - Custom styling per menu item type
575
-
576
- ## [4.2.1] - 2025-09-24
577
-
578
- ### Enhanced
579
- - **Debug Context Menu Positioning**: Improved debug context menu to position relative to tree element instead of viewport
580
- - Debug menu now appears 200px right and 100px down from each tree's top-left corner
581
- - Supports multiple trees on same page with individual positioning
582
- - Enhanced debug logging to include tree ID and calculated position coordinates
583
- - Better for CSS development when tree is not at top-left of viewport
584
- - **Debug Context Menu Robustness**: Enhanced debug mode to work with single-node trees
585
- - Uses second node when available, falls back to first node for single-node trees
586
- - More flexible node selection for debug menu display
587
- - Improved reliability for development scenarios
588
-
589
- ### Fixed
590
- - **Debug Mode State Management**: Fixed context menu interference between debug mode and normal right-click menus
591
- - Added `isDebugMenuActive` state tracking to prevent debug logic from hiding user-triggered menus
592
- - Normal right-click context menus now work properly when debug mode is disabled
593
- - Proper cleanup of debug state when switching between modes
594
- - **Debug Mode Requirements**: Relaxed debug context menu requirements to support edge cases
595
- - Changed minimum tree length requirement from `> 1` to `> 0` for better compatibility
596
- - Debug mode now works with any non-empty tree structure
597
-
598
- ## [4.2.0] - 2025-09-24
599
-
600
- ### Added
601
- - **Context Menu System**: Comprehensive context menu functionality with two implementation approaches
602
- - **Callback-based Context Menus**: New `contextMenuCallback` prop that accepts a function `(node: LTreeNode<T>) => ContextMenuItem[]`
603
- - **ContextMenuItem Interface**: New interface with `icon`, `title`, `isDisabled`, `callback`, and `isDivider` properties
604
- - **Position Offset Configuration**: New `contextMenuXOffset` (default: 8px) and `contextMenuYOffset` (default: 0px) props for cursor clearance
605
- - **Debug Mode**: New `shouldDisplayContextMenuInDebugMode` prop for persistent context menu display at fixed position (200px, 100px)
606
- - **Snippet-based Support**: Maintains backward compatibility with existing `{#snippet contextMenu(node, closeMenu)}` approach
607
- - **Enhanced Context Menu UX**:
608
- - Auto-close on scroll events (mouse wheel, scrollbar, touch, programmatic)
609
- - Auto-close on outside clicks
610
- - Support for disabled menu items with visual feedback
611
- - Support for menu dividers for visual organization
612
- - Rich icon support for menu items
613
- - **Development Tools**: New `/dev/context-menu` page with comprehensive examples
614
- - Basic file system context menu example with conditional actions
615
- - Advanced server management example with status-based and type-specific menus
616
- - Real-time offset configuration testing
617
- - Interactive demonstration of all context menu features
618
- - Debug context menu mode with `shouldDisplayContextMenuInDebugMode` for easy styling development
619
- - Navigation link added to main layout for easy access
620
-
621
- ### Enhanced
622
- - **CSS Styling**: Added comprehensive context menu styles in `main.scss`
623
- - `.ltree-context-menu`, `.ltree-context-menu-item`, `.ltree-context-menu-icon`, `.ltree-context-menu-divider` classes
624
- - Support for disabled states with `.ltree-context-menu-item-disabled`
625
- - Flexible layout with proper hover effects and visual hierarchy
626
- - **Type Safety**: Full TypeScript support for all context menu features
627
- - **Documentation**: Comprehensive README and CLAUDE.md updates covering both implementation approaches
628
-
629
- ### Fixed
630
- - **Context Menu Scroll Behavior**: Fixed issue where context menu remained visible when scrolling
631
- - Added scroll event listeners with capture phase to catch all scroll events
632
- - Added wheel event listeners for mouse wheel scrolling
633
- - Context menu now properly closes on any scroll interaction
634
-
635
- ## [4.1.1] - 2025-09-23
636
-
637
- ### Fixed
638
- - **TreePathSeparator Default Value**: Fixed `treePathSeparator` parameter to properly default to '.' when not provided to Tree.svelte
639
- - Previously, when `treePathSeparator` was undefined, the reactive effect would override the ltree's internal default
640
- - Now defaults to '.' in the parameter destructuring, ensuring consistent behavior
641
-
642
- ## [4.1.0] - 2025-09-23
643
-
644
- ### Fixed
645
- - **Critical Sorting Bug**: Fixed default sort method to sort by level first, ensuring proper hierarchical tree construction
646
- - Previously sorted by parent path first, causing level 3 nodes to be inserted before level 2 nodes
647
- - Now sorts by level (depth) first, then parent path, then display value
648
- - Eliminates "Could not find parent node" errors when nodes are inserted out of level order
649
- - **Progressive Rendering**: Fixed progressive rendering feature to work correctly with proper level-based sorting
650
- - Progressive rendering now displays levels 1-2 immediately while deeper levels continue processing
651
- - Improves perceived performance for large datasets by showing initial tree structure quickly
652
- - **TreePathSeparator Reactivity**: Fixed Tree component to properly update internal separator when `treePathSeparator` prop changes
653
- - Added reactive effect to update ltree's separator property when prop changes
654
- - Prevents race conditions where data is processed with wrong separator
655
- - Fixes filesystem demo and other custom separator use cases
656
- - **Sort Functions in Examples**: Updated all demo sort functions to calculate level from path depth during sorting
657
- - Home page, dev page, and filesystem examples now use path-based level calculation
658
- - Ensures consistent level-first sorting across all demos and examples
659
- - Prevents insertion failures in example applications
660
-
661
- ### Enhanced
662
- - **Test Coverage**: Added comprehensive test suite for sorting functionality
663
- - Tests verify level-first sorting behavior with various hierarchical data structures
664
- - Validates progressive rendering scenarios and sort correctness
665
- - Uses Vitest framework for fast, reliable testing
666
-
667
- ### Changed
668
- - **Default Sort Algorithm**: Updated `_defaultSort` method to prioritize level over parent path for hierarchical correctness
669
- - **Example Sort Functions**: All demo applications now use level-first sorting for consistent behavior
670
-
671
- ## [4.0.1] - 2025-01-23
672
-
673
- ### Fixed
674
- - **treePathSeparator Propagation**: Fixed helper functions (`getParentPath`, `getRelativePath`, `getPathSegments`) to properly use the configured `treePathSeparator` instead of hardcoded "." separator
675
- - All path manipulation functions now respect the custom separator setting
676
- - Ensures consistent path handling throughout the tree operations when using custom separators like "/"
677
- - Fixed `getRelativePath` to use `pathSeparator.length` instead of assuming single character
678
- - Fixed `getLevel` to properly count segments with multi-character separators
679
-
680
- ### Added
681
- - **Test Suite**: Added comprehensive test coverage for ltree helper functions
682
- - 24 test cases covering single-character, multi-character, and edge case separators
683
- - Vitest testing framework integration with `npm run test` and `make test` commands
684
- - Tests validate proper handling of separators like `"::"`, `"->>"`, `"<|>"` and complex edge cases
685
-
686
- ## [4.0.0] - 2025-01-09
687
-
688
- ### Added
689
- - **Complete Showcase Site Redesign**: Comprehensive overhaul of the documentation and demo site
690
- - **API Reference Page**: Complete tabbed reference with properties, methods, events, and templates tables
691
- - **Professional Navigation**: Fixed-top navbar with burger menu, GitHub link, and responsive sidebar
692
- - **Ocean Color Scheme**: Beautiful blue-themed design using Coolors.co palette (#00171F, #003459, #007EA7, #00A7E1, #FFFFFF)
693
- - **Responsive Layout**: Mobile-first design with collapsible sidebar and backdrop overlay
694
- - **Enhanced Examples**: Four comprehensive code examples with descriptions in tabbed interface
695
- - **Docker Production Setup**: Complete containerization for static site deployment
696
- - **Multi-stage Dockerfile**: Optimized build with Node.js builder and nginx production stage
697
- - **Static Site Generation**: SvelteKit configuration for pre-rendered HTML pages
698
- - **Make Commands**: Docker build, run, and management commands with custom registry support
699
- - **Nginx Configuration**: Optimized serving with gzip, caching, and SPA routing support
700
-
701
- ### Changed
702
- - **Layout Architecture**: Moved from nested Bootstrap containers to clean, consistent structure
703
- - **Fixed Navigation**: Top navbar with brand, burger menu, and GitHub link
704
- - **Sidebar Design**: Fixed-width (280px) sidebar with consistent icon spacing
705
- - **Footer Integration**: Professional footer with KeenMate branding
706
- - **SvelteKit Configuration**: Updated for optimal static generation
707
- - **Static Adapter**: Switched from adapter-auto to adapter-static for reliable builds
708
- - **Prerendering**: Enabled SSR and prerender for all showcase pages
709
- - **Build Output**: Optimized for nginx serving with proper fallback handling
710
- - **Page Structure Consistency**: Standardized header structure across all showcase pages
711
- - **Removed Redundant Containers**: Eliminated nested container-fluid wrappers
712
- - **Clean Headers**: Direct h1 and description elements without Bootstrap grid overhead
713
-
714
- ### Enhanced
715
- - **Visual Design**: Professional styling throughout the showcase site
716
- - **Fixed Icon Alignment**: Consistent 1.5rem width for sidebar navigation icons
717
- - **Gradient Backgrounds**: Sophisticated color gradients across navbar, sidebar, and footer
718
- - **Interactive Elements**: Hover effects, focus states, and smooth transitions
719
- - **Typography**: Clear hierarchy with proper contrast and accessibility
720
- - **User Experience**: Improved navigation and usability
721
- - **Always-Visible Burger Menu**: Toggle sidebar on any screen size for flexible layout
722
- - **Responsive Behavior**: Automatic sidebar hiding on mobile with backdrop close
723
- - **Tab Navigation**: Full-width code examples with clean tab interface
724
- - **Mobile Optimization**: Touch-friendly interactions and responsive text sizing
725
-
726
- ### Fixed
727
- - **Container Structure**: Resolved double-container issues causing layout inconsistencies
728
- - **Sidebar Toggle**: Fixed burger menu functionality to work across all screen sizes
729
- - **Static Generation**: Proper SvelteKit configuration for nginx-compatible static builds
730
- - **Icon Spacing**: Consistent navigation icon width preventing text misalignment
731
-
732
- ### Documentation
733
- - **API Reference**: Complete tables for all component properties, methods, events, and templates
734
- - **Usage Examples**: Real-world code examples including organization tree configuration
735
- - **Docker Documentation**: Make commands and containerization setup
736
- - **Responsive Design**: Mobile-first approach with professional styling
737
-
738
- ## [4.0.0-rc.08] - 2025-01-08
739
-
740
- ### Added
741
- - **searchNodes() Method**: New public method `searchNodes(searchText)` that returns an array of matching nodes without filtering the tree display
742
- - Programmatically search nodes using the internal search index
743
- - Returns `LTreeNode<T>[]` array of matching nodes
744
- - Useful for building custom search interfaces, suggestions, and result summaries
745
- - **Configurable Path Separators**: New `treePathSeparator` property allows custom hierarchical path separators
746
- - Default remains `"."` for backward compatibility (e.g., "1.2.3")
747
- - Support for custom separators like `"/"` for file system style paths (e.g., "1/src/components")
748
- - All path operations throughout the component respect the custom separator
749
- - **Data Structure Showcase Page**: New comprehensive `/data-structure` showcase page with four detailed sections:
750
- - **LTree Path Structure**: Understanding path-based hierarchical data model
751
- - **Optimized Data Structure**: Precomputed values for better performance
752
- - **Custom Path Separators**: Live demo with file system style paths using "/" separator
753
- - **External Search & Data Management**: Managing search outside the tree component
754
- - **Invalid Data Structures**: Common mistakes and unsupported patterns
755
- - **Enhanced Search Showcase**: Added new `searchNodes()` method demonstration section to `/search` page
756
- - Interactive search interface showing difference between `searchNodes()` and `filterNodes()`
757
- - Live examples with result display and usage patterns
758
- - **Insert Result Information**: New `insertResult` bindable property provides detailed information about data insertion
759
- - `InsertArrayResult<T>` interface with successful count and failed nodes array
760
- - Each failed node includes original data, processed node, and error message
761
- - Useful for data validation, debugging, and handling incomplete datasets
762
- - **Drag-over Visual Feedback**: New `dragOverNodeClass` property for highlighting nodes during drag operations
763
- - Two built-in classes: `ltree-dragover-highlight` (dashed border) and `ltree-dragover-glow` (shadow effect)
764
- - Automatic state management with proper drag event handling
765
- - Provides clear visual feedback for drop targets during drag-and-drop operations
766
-
767
- ### Changed
768
- - **Documentation Updates**: Updated README.md, CLAUDE.md, and showcase pages with new features
769
- - Added `searchNodes` to public methods documentation
770
- - Added `treePathSeparator` to Tree Configuration properties table
771
- - Updated architecture description to reflect configurable separators
772
- - Fixed path requirements documentation to clarify separator flexibility
773
- - **Navigation Enhancement**: Added "Data Structure" page to sidebar navigation with 🗂️ icon
774
-
775
- ### Enhanced
776
- - **Type System**: Updated `Ltree<T>` interface to include `searchNodes` method signature
777
- - **Internal Architecture**: Enhanced `createLTree` function to accept configurable `treePathSeparator` parameter
778
- - **Component Integration**: Updated `Tree.svelte` component to pass through `treePathSeparator` property
779
-
780
- ### Fixed
781
- - **Node Indentation**: Fixed `Node.svelte` indent style to use consistent per-level indentation instead of cumulative indentation
782
- - Previously: Each level had exponentially increasing indent (level * indent-per-level)
783
- - Now: Each level uses fixed CSS variable `--tree-node-indent-per-level` allowing proper CSS-based indentation control
784
- - **Search Index Accuracy**: Fixed `insertArray` to only add successfully inserted nodes to `flatTreeNodes` array
785
- - Prevents search index from returning incorrect node indices for nodes that failed to insert
786
- - Failed nodes are no longer included in search operations, ensuring search results match visible tree structure
787
- - **Error Message Clarity**: Improved `insertTreeNode` error messages to include the failing node's path
788
- - Error format: `"Node: {path} - Could not find parent node: {parentPath}"`
789
- - Makes debugging hierarchical data issues much clearer
790
-
791
- ### Documentation
792
- - **Comprehensive Examples**: Added working code examples for both basic and advanced use cases
793
- - **Path Separator Flexibility**: Clarified that paths don't need to be dot-separated, can use any consistent separator
794
- - **External Data Management**: Detailed examples of filtering data outside the tree component
795
- - **Performance Optimization**: Guidelines for when to use precomputed values vs automatic calculations
796
-
797
- ## [4.0.0-rc.07] - 2025-01-06
798
-
799
- ### Added
800
- - **Customizable Scroll Highlight**: New `scrollHighlightClass` property allows users to define custom CSS classes for scroll highlight effects
801
- - **Built-in Highlight Options**: Added pre-built highlight classes:
802
- - `ltree-scroll-highlight` - Background glow with blue color (default)
803
- - `ltree-scroll-highlight-arrow` - Red arrow indicator positioned to the right of the node
804
- - **Scroll Highlight Timeout Control**: New `scrollHighlightTimeout` property (default: 4000ms) controls duration of highlight effect
805
- - **Enhanced scrollToPath Method**: Improved scroll highlighting with proper element targeting and CSS class management
806
- - **Debug Logging Control for Indexer**: Added `shouldDisplayDebugInformation` property to Indexer class for consistent debug logging control
807
-
808
- ### Changed
809
- - **Removed CSS Animation Dependencies**: Scroll highlighting now uses pure CSS classes instead of CSS animations for better timeout control
810
- - **Improved Element Targeting**: `scrollToPath` now targets `.ltree-node-content` specifically for more precise highlighting
811
- - **Enhanced Documentation**: Updated README with comprehensive examples for highlight customization
812
- - **Consistent Debug Logging**: All indexer console.log messages now respect the `shouldDisplayDebugInformation` flag for unified logging control
813
-
814
- ### Fixed
815
- - **Scroll Highlight Duration**: Fixed issue where CSS animations overrode JavaScript timeout values
816
- - **Element Selection**: Improved DOM element selection for scroll highlighting functionality
817
- - **LTree Path Traversal**: Fixed `expandNodes` and `collapseNodes` methods by correctly prefixing path segments with 'x' prefix to match internal tree structure storage
818
-
819
- ## [4.0.0-rc.05] - 2025-09-05
820
-
821
- ### Added
822
- - **Optimized Async Search Indexing**: Improved indexing implementation that processes entire queue at once during idle time instead of small batches
823
- - **Enhanced Indexing Performance**: Increased batch size from 100 to 1000 nodes and streamlined queue processing
824
- - **Better Debug Logging**: Added conditional debug logging for indexing operations when `shouldDisplayDebugInformation` is enabled
825
-
826
- ### Changed
827
- - **Indexing Architecture**: Refactored async indexing to process all queued nodes in a single idle callback rather than batched processing
828
- - **Queue Management**: Simplified indexing queue processing with more efficient completion handling
829
- - **TypeScript Support**: Added `Tuple<T, U>` type import for enhanced type safety
830
-
831
- ### Performance
832
- - **Faster Indexing**: Single-pass indexing of entire queue reduces overhead and callback scheduling
833
- - **Reduced Idle Callbacks**: Less frequent but more efficient use of `requestIdleCallback`
834
- - **Improved Memory Usage**: More efficient queue management with immediate processing
835
-
836
- ## [4.0.0] - 2025-09-01
837
-
838
- ### Added
839
- - **Asynchronous Search Indexing**: Search indexing now uses `requestIdleCallback` for non-blocking performance
840
- - **Statistics Tracking**: New `statistics` getter provides real-time data:
841
- - `nodeCount`: Total number of nodes in the tree
842
- - `maxLevel`: Maximum depth level of the tree
843
- - `filteredNodeCount`: Number of nodes currently visible when filtering
844
- - `isIndexing`: Boolean indicating if search indexing is in progress
845
- - `pendingIndexCount`: Number of nodes pending indexing
846
- - **Expand Level Control**: New `expandLevel` property (default: 2) automatically expands nodes up to specified depth
847
- - **Drag & Drop Properties**: Added `isDraggableMember` and `isDropAllowedMember` for fine-grained drag & drop control
848
- - **Debug Information Panel**: Enhanced debug display with collapsible interface showing tree statistics and indexing progress
849
-
850
- ### Changed
851
- - **Breaking**: Renamed internal references from "Trie" to "LTree" for consistency
852
- - **Breaking**: `trieId` property renamed to `treeId`
853
- - **Search Performance**: Tree now renders immediately while search indexing happens asynchronously
854
- - **Debug Styling**: Updated debug panel styling to use `em` units with reduced padding
855
-
856
- ### Performance
857
- - **Non-blocking UI**: Tree renders immediately while search indexing occurs during browser idle time
858
- - **Improved Large Dataset Handling**: Async indexing prevents UI freezing with large data sets
859
- - **Batch Processing**: Search indexing processes nodes in batches during idle periods
860
- - **Graceful Degradation**: Falls back to `setTimeout` on browsers without `requestIdleCallback` support
861
-
862
- ### Documentation
863
- - Added comprehensive documentation for async search indexing
864
- - Added warning about search indexing requirements
865
- - Enhanced API documentation with new properties and statistics
866
- - 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-rc11] - 2026-06-25 [PUBLISHED]
9
+
10
+ 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.
11
+
12
+ ### Changed
13
+ - **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.
14
+ - **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.
15
+ - **`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`.
16
+ - **`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.
17
+ - **`--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.
18
+ - **`.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`.
19
+ - **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.
20
+ - **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.
21
+ - **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`.
22
+
23
+ ### Added
24
+ - **`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.
25
+ - **`## 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.
26
+ - **`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.
27
+ - **`.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.
28
+ - **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.
29
+ - **`@floating-ui/dom` runtime dependency** (`^1.7.6`): Required by the context menu positioning above. Matches the version pinned in `@keenmate/web-treeview`.
30
+
31
+ ### Fixed
32
+ - **`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.
33
+ - **`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.
34
+ - **`.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.
35
+ - **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).
36
+ - **`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.
37
+ - **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).
38
+ - **`.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).
39
+ - **`.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.
40
+ - **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`).
41
+ - **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 anywaysurprising 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.
42
+ - **`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.)
43
+ - **`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).
44
+ - **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.)
45
+
46
+ ### Added
47
+ - **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`.
48
+
49
+ - **`/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).
50
+ - **`/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.
51
+
52
+ ## [5.0.0-rc10] - 2026-06-10
53
+
54
+ ### Changed
55
+ - **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.
56
+
57
+ **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 }`.
58
+
59
+ ### Added
60
+ - **`--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.
61
+ - **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.
62
+ - **`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.).
63
+ - **`--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.
64
+ - **`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-timeonly consumed lazily in some paths). Precedence: callback > member > default for both.
65
+
66
+ ### Changed
67
+ - **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`.
68
+ - **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.
69
+ - **`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.
70
+
71
+ ### Fixed
72
+ - **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.
73
+ - **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`).
74
+
75
+ ## [5.0.0-rc09] - 2026-06-07
76
+
77
+ ### Added
78
+ - **`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.
79
+ - **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 }`.
80
+ - **`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.
81
+ - **`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.
82
+ - **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.
83
+ - **`--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.
84
+ - **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.
85
+ - **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`.
86
+
87
+ ### Changed
88
+ - **`!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.).
89
+ - **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.
90
+ - **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).
91
+ - **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.
92
+ - **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.
93
+ - **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.
94
+ - **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.
95
+ - **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`.
96
+ - **`--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.
97
+ - **`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.
98
+ - **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`.
99
+ - **`--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.
100
+
101
+ ### Breaking
102
+ - **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.
103
+ - **`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.
104
+ - **`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.
105
+ - **`./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`.
106
+ - **`--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).
107
+ - **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`.
108
+ - **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`.
109
+
110
+ ### Fixed
111
+ - **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.
112
+ - **`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.
113
+ - **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.
114
+ - **`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.
115
+
116
+ ## [5.0.0-rc08] - 2026-05-27
117
+
118
+ ### Added
119
+ - **`{ 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.
120
+ - **Array variants on expand/collapse methods**: `expandNodes`, `collapseNodes`, `expandAll`, and `collapseAll` now accept `string | string[]`. Single emit per call regardless of array length.
121
+ - **`{ 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.
122
+ - **`{ 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()`.
123
+ - **`/examples/silent-highlight` demo page**: URL-restore scenario with loud vs. silent toggle showing how form data is preserved in silent mode.
124
+ - **`/examples/expand-collapse` demo page**: Demonstrates array variants and exclusive focus mode.
125
+
126
+ ## [5.0.0-rc07] - 2026-05-23
127
+
128
+ ### Added
129
+ - **`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.
130
+ - **`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.
131
+
132
+ ### Fixed
133
+ - **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.
134
+
135
+ ## [5.0.0-rc06] - 2026-03-31
136
+
137
+ ### Added
138
+ - **`shouldShowCheckboxes` prop**: Renders a checkbox before each selectable node with custom styling and indeterminate support.
139
+ - **`checkboxMode` prop** (`'independent'` | `'cascade'`): Controls whether checking a parent cascades to all descendants. Indeterminate state shown when partial.
140
+ - **`beforeCheckboxToggleCallback` interceptor**: Cancel or override checkbox toggles.
141
+ - **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.
142
+ - **`onHighlightChange` event**: Fires when highlighted paths change (Ctrl+click, Shift+click, etc.).
143
+ - **Bulk checkbox via highlight**: When multiple nodes are highlighted and a checkbox in the highlight is clicked, all highlighted nodes toggle together.
144
+ - **Shift+Arrow/Home/End keyboard highlight**: Shift+ArrowDown/Up extends highlight range by one sibling, Shift+Home/End extends to first/last visible node.
145
+ - **PageUp/PageDown navigation**: Jumps 10 visible nodes forward/back. Shift+PageUp/PageDown extends highlight by 10 nodes.
146
+ - **`ltree-selected-highlight` CSS class**: Explorer-style blue background highlight. Customizable via `--ltree-highlight-bg` and `--ltree-highlight-color`.
147
+ - **Interaction example page** (`/examples/interaction`): Interactive demos for click behavior, checkboxes, multi-select, and keyboard navigation. All settings persisted to localStorage.
148
+
149
+ ### Fixed
150
+ - **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.
151
+ - **Svelte proxy equality warning on checkbox toggle**: `_setFocusedNode` now compares by path instead of object identity to avoid `state_proxy_equality_mismatch`.
152
+
153
+ ### Breaking
154
+ - **`selectedNode` `focusedNode`**: Renamed prop and bindable. The single focused node (last clicked / arrow-keyed to).
155
+ - **`selectedPaths` repurposed**: Now represents checkbox-only data state. For click/highlight multi-select, use `highlightedPaths`.
156
+ - **`highlightedPaths` (new)**: Replaces old `selectedPaths` for Ctrl+click / Shift+click UI highlight.
157
+ - **`selectedNodeClass` `highlightedNodeClass`**: CSS class applied to highlighted nodes.
158
+ - **`focusedNodeClass` (new)**: CSS class applied to the single focused node.
159
+ - **`onSelectionChange` repurposed**: Now fires on checkbox selection changes only. Use `onHighlightChange` for highlight changes.
160
+ - **`selectNode()` / `selectNodes()` deprecated**: Use `highlightNode()` / `highlightNodes()` instead.
161
+
162
+ ## [5.0.0-rc05] - 2026-03-26
163
+
164
+ ### Added
165
+ - **`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.
166
+
167
+ ### Breaking
168
+ - **`shouldToggleOnNodeClick` removed**: Replace `shouldToggleOnNodeClick={true}` with `clickBehavior="expand-and-focus"` (default) and `shouldToggleOnNodeClick={false}` with `clickBehavior="select"`.
169
+
170
+ ## [5.0.0-rc04] - 2026-03-12
171
+
172
+ ### Added
173
+ - **`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>`.
174
+ - **Bulk subtree operations**: `insertBranch(parentPath, nodes)`, `replaceBranch(path, nodes)`, `deleteBranch(path)` on TreeController perform subtree-level add/replace/remove with a single tree emission.
175
+ - **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.
176
+ - **Branch-operations example** (`/examples/branch-operations`): Server-simulated cut/paste workflow demonstrating `deleteBranch` + `insertBranch` with clipboard integration.
177
+
178
+ ### Fixed
179
+ - **`.ltree-container` missing `outline: none`**: Container div now suppresses browser focus outline.
180
+
181
+ ## [5.0.0-rc03] - 2026-03-08
182
+
183
+ ### Added
184
+ - **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.
185
+ - **`selectedPaths` bindable prop**: `Set<string>` of all selected node paths. Two-way binding for external control.
186
+ - **`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.
187
+ - **`onSelectionChanged` event**: `(paths: Set<string>, nodes: LTreeNode<T>[]) => void` fires when selection changes.
188
+ - **`contextMenuCallback` 3rd parameter**: Now receives `selectedNodes?: LTreeNode<T>[]` — enables selection-aware context menus.
189
+ - **Public multi-select API on TreeController**: `selectNode(path, mode)`, `selectNodes(paths)`, `deselectAll()`, `getSelectedNodes()`, `isNodeSelected(path)`.
190
+ - **`SelectionModifiers` type export**: `{ ctrl: boolean; shift: boolean }` for modifier-aware click handling.
191
+ - **`.ltree-multi-selected` CSS class**: Styling for nodes in a multi-selection.
192
+ - **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.
193
+ - **Named dividers**: `{ divider: true, label: 'Section' }` renders as `──── Section ────`
194
+ - **Keyboard shortcuts**: `shortcut` field renders right-aligned hint and activates on keypress when menu is open
195
+ - **Submenus**: `children: ContextMenuEntry[]` opens nested menu on hover
196
+ - **Visibility control**: `isVisible: false` hides items in callback approach (snippet approach uses `{#if}`)
197
+ - **Flexible styling**: `className` replaces dedicated `danger` boolean use `className="danger"` or any custom class
198
+ - **Async onclick**: `onclick` supports `Promise<void>` return with try/catch error handling
199
+ - **`ContextMenuItemC` / `ContextMenuDividerC` Svelte components**: Declarative context menu building inside the `contextMenu` snippet. Supports nested children slot for submenus.
200
+ - **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.
201
+ - **`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.
202
+ - **`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`.
203
+
204
+ ### Fixed
205
+ - **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.
206
+ - **`.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.
207
+
208
+ ## [5.0.0-rc02] - 2026-03-05
209
+
210
+ ### Architecture (Breaking)
211
+ - **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.
212
+ - **Drop position naming**: `'above'`/`'below'` renamed to `'before'`/`'after'` throughout (`DropPosition` type, CSS classes, events). `'child'` unchanged.
213
+ - **Canvas rendering extracted**: Canvas-based rendering (`CanvasTree`, layouts, themes) moved to separate package `@keenmate/svelte-treeview-canvas`.
214
+
215
+ ### Added
216
+ - **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()}`.
217
+ - **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.
218
+ - **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.
219
+ - **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.
220
+ - **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.
221
+ - **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.
222
+ - **`overscroll-behavior: contain`** on `.ltree-virtual-scroll` container.
223
+ - **`isCollapsibleMember` / `getIsCollapsibleCallback`**: Per-node collapsibility control.
224
+ - **`getIsDraggableCallback`**: Dynamic per-node draggability.
225
+ - **`applyChanges()` batch method**: Apply multiple tree edits in a single operation.
226
+ - **`_rev` change tracking** on nodes for efficient keyed rendering.
227
+
228
+ ### Fixed
229
+ - **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`.
230
+ - **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.
231
+ - **"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.
232
+ - **Empty tree drop placeholder ignoring `dragDropMode`**: `handleEmptyTreeDragOver/Drop/TouchEnd` now check `dragDropMode !== 'none'` before activating the drop placeholder.
233
+ - **`treeId` not synced on prop changes**: Added `$effect` in `Tree.svelte` to sync `treeId` prop to controller after mount.
234
+ - **`dropZoneStart` not used in glow mode**: `calculateGlowPosition` now uses `dropZoneStart` to compute the child zone threshold instead of hardcoded `width/2`.
235
+ - **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()`.
236
+ - **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()`.
237
+
238
+ ### Changed
239
+ - **Custom Layout Example — Explicit Drop Zones**: Replaced invisible spatial detection with visible drop zone pills (Before / After / Child) on dendrograms.
240
+
241
+ ## [4.7.2] - 2026-02-17
242
+
243
+ ### Fixed
244
+ - **`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}`.
245
+
246
+ ## [4.7.1] - 2026-02-17
247
+
248
+ ### Changed
249
+ - **Drag and Drop Disabled by Default**: `dragDropMode` now defaults to `'none'` instead of `'both'`
250
+ - Most trees are read-only, so this is a safer default
251
+ - To enable drag and drop, explicitly set `dragDropMode="both"` (or `"self"` / `"cross"`)
252
+
253
+ ### Fixed
254
+ - Removed development `console.log` statements from `Tree.svelte` and `ltree.svelte.ts`
255
+
256
+ ## [4.7.0] - 2026-02-11
257
+
258
+ ### Added
259
+ - **Per-Node Drop Position Restrictions**: New feature to control which drop positions are allowed per node
260
+ - `allowedDropPositionsMember` - Property name mapping for static data (e.g., from server)
261
+ - `getAllowedDropPositionsCallback` - Callback for dynamic position logic based on node type
262
+ - `DropPosition` type exported: `'above' | 'below' | 'child'`
263
+ - Example use cases:
264
+ - Trash folder: `['child']` only (can drop INTO, not above/below)
265
+ - Files: `['above', 'below']` only (can't drop INTO a file)
266
+ - Folders: `undefined` or `[]` (all positions allowed - default)
267
+ - Works with both glow mode (snaps to nearest allowed position) and floating mode (only shows allowed zones)
268
+ - Backwards compatible: `undefined`/empty array = all positions allowed
269
+
270
+ ### Example Usage
271
+ ```typescript
272
+ // Callback approach (dynamic logic)
273
+ function getAllowedDropPositionsCallback(node: LTreeNode<T>): DropPosition[] | null {
274
+ if (node.data?.type === 'file') return ['above', 'below'];
275
+ if (node.data?.type === 'trash') return ['child'];
276
+ return undefined; // all positions allowed
277
+ }
278
+
279
+ // Member approach (server data)
280
+ const data = [
281
+ { path: '1', name: 'Trash', allowedDropPositions: ['child'] },
282
+ { path: '2', name: 'Document.pdf', allowedDropPositions: ['above', 'below'] },
283
+ { path: '3', name: 'Projects' }, // all positions (default)
284
+ ];
285
+ ```
286
+
287
+ ## [4.6.0] - 2026-02-11
288
+
289
+ ### Added
290
+ - **Flat Rendering Mode**: New `isFlatRenderingEnabled` prop (default: `true`) for significantly faster rendering
291
+ - Renders all visible nodes in a single `{#each}` loop instead of recursive components
292
+ - Initial render ~12x faster (300ms 25ms for 5500 nodes)
293
+ - Progressive rendering batches initial load to prevent UI freeze
294
+ - Documentation: `docs/FLAT_MODE_PERFORMANCE.md`
295
+ - **Context-Based Node Configuration**: Moved stable callbacks and config to Svelte context
296
+ - `NodeCallbacks<T>` interface for all event handlers (click, drag, drop, touch)
297
+ - `NodeConfig` interface for stable configuration (icons, classes, drop zone settings)
298
+ - Eliminates inline arrow function re-renders (5500 nodes no longer re-evaluate on array changes)
299
+ - Exported types: `NodeCallbacks`, `NodeConfig` from package index
300
+ - **Async beforeDropCallback**: `beforeDropCallback` now supports async/Promise return values
301
+ - Enables showing confirmation dialogs before completing a drop
302
+ - Can await user input to decide whether to cancel, proceed, or modify the drop
303
+ - Example: `async (dropNode, draggedNode, position) => { return await showDialog(); }`
304
+
305
+ ### Enhanced
306
+ - **Exponential Batch Sizing**: Progressive rendering now uses exponential batching (20 40 → 80 → 160...)
307
+ - First batch (20 nodes) renders instantly for immediate visual feedback
308
+ - Batch size doubles each frame up to `maxBatchSize` (default 500)
309
+ - New props: `initialBatchSize` (default 20), `maxBatchSize` (default 500)
310
+ - Replaces fixed `renderBatchSize` prop
311
+ - Expand/collapse on large trees (>1000 nodes): Immediate add to minimize diffs
312
+ - **Tree Editor Example**: Enhanced with async drop validation dialog
313
+ - Demonstrates async `beforeDropCallback` with native confirm dialog
314
+ - Shows warning when drop position is modified
315
+
316
+ ### Fixed
317
+ - **Scroll Highlight Stacking**: Fixed multiple nodes staying highlighted when rapidly clicking prev/next
318
+ - Previous highlight now immediately cleared when navigating to new node
319
+ - Timeout properly cancelled to prevent stale highlight removal
320
+ - **Flat Mode Node Updates After Move**: Fixed nodes not re-rendering after `moveNode()` in flat mode
321
+ - Keyed each now uses `node.id + path + hasChildren` to detect moved nodes
322
+ - Moved nodes now correctly update their indentation level
323
+ - Parent nodes correctly update toggle icon when children are moved away
324
+
325
+ ### Changed
326
+ - **Default Rendering Mode**: `isFlatRenderingEnabled` now defaults to `true` (was `false`)
327
+ - **Default Progressive Render**: `isProgressiveRender` now defaults to `true` (was `false`)
328
+
329
+ ## [4.5.0] - 2026-02-09
330
+
331
+ ### Added
332
+ - **Drop Zone Layout Configuration**: New props to customize drop zone appearance and positioning
333
+ - `dropZoneLayout` - Controls zone arrangement with 5 layout options:
334
+ - `'around'` (default) - Above zone on top, Below/Child zones on bottom
335
+ - `'above'` - All 3 zones in a horizontal row above the node
336
+ - `'below'` - All 3 zones in a horizontal row below the node
337
+ - `'wave'` - Zones stacked vertically (above/child/below) with fixed width
338
+ - `'wave2'` - Diagonal wave pattern with Above/Below offset 7% to the left
339
+ - `dropZoneStart` - Number (0-100) controlling where zones start horizontally (default: 33%)
340
+ - `dropZoneMaxWidth` - Max width in pixels for wave layouts (default: 120px)
341
+ - **New TypeScript Type**: `DropZoneLayout` type exported from `types.ts`
342
+ - **Mobile Touch Drag and Drop**: Full touch support for drag and drop on mobile devices
343
+ - Long-press (300ms) to initiate drag - distinguishes from tap and scroll
344
+ - Visual ghost element follows finger during drag showing the dragged node
345
+ - Drop target highlighting using existing `dragOverNodeClass` prop
346
+ - Haptic feedback via `navigator.vibrate()` when drag starts (on supported devices)
347
+ - Automatic cancellation if finger moves >10px before long-press completes (allows normal scrolling)
348
+ - Works alongside existing desktop HTML5 drag and drop - same `onNodeDrop` callback for both
349
+ - **Drop Placeholder for Empty Trees**: When dragging nodes to an empty tree, a drop zone placeholder appears
350
+ - Shows visual drop target in empty trees during drag operations
351
+ - Works with both desktop (HTML5 DnD) and touch drag
352
+ - Customizable via `dropPlaceholder` snippet prop for custom content
353
+ - `onNodeDrop` callback receives `null` as `dropNode` for root-level drops into empty trees
354
+ - **Drop Position Indicators**: Visual indicators showing exactly where dropped items will be placed
355
+ - Three drop positions per node: `'above'` (sibling before), `'child'` (as child), `'below'` (sibling after)
356
+ - Absolutely positioned indicators on right half of node to prevent layout shifts
357
+ - Position calculated from mouse Y: top 25% = above, middle 50% = child, bottom 25% = below
358
+ - New CSS classes: `.ltree-drop-indicators`, `.ltree-drop-above`, `.ltree-drop-child`, `.ltree-drop-below`
359
+ - **Root Drop Zone**: Drop zone that appears at bottom of non-empty trees during drag
360
+ - Allows dropping items as root-level nodes in trees that already have content
361
+ - New CSS class: `.ltree-root-drop-zone`
362
+ - **Drag Drop Mode Control**: New `dragDropMode` prop to control allowed drag operations
363
+ - `'none'` - Drag and drop disabled
364
+ - `'self'` - Only within same tree
365
+ - `'cross'` - Only between different trees
366
+ - `'both'` - Both self and cross-tree (default)
367
+ - **Sibling Order Support**: New `orderMember` prop for explicit sibling ordering
368
+ - Specifies which field in user data contains the sort order value
369
+ - Used by default sort to order siblings within the same parent
370
+ - Required for proper above/below positioning in drag-drop tree editors
371
+ - Example: `orderMember="sortOrder"` with data like `{ path: '1.1', name: 'A', sortOrder: 10 }`
372
+ - **Tree Editor Helper Methods**: New methods for building tree editors
373
+ - `getChildren(parentPath)` - Get direct children of a node
374
+ - `getSiblings(path)` - Get all siblings of a node (including itself)
375
+ - `getNodeByPath(path)` - Get a node by its path
376
+ - `refreshSiblings(parentPath)` - Re-sort children of a parent using orderMember
377
+ - `refreshNode(path)` - Trigger re-render for a specific node
378
+ - **Tree Editor Mutation Methods**: New methods for modifying tree structure
379
+ - `addNode(parentPath, data, pathSegment?)` - Add a new node to the tree
380
+ - `moveNode(sourcePath, targetPath, position)` - Move a node with full subtree to a new location
381
+ - Supports 'above', 'below', and 'child' positions
382
+ - Automatically updates paths of all descendants
383
+ - Calculates order values when orderMember is set
384
+ - `removeNode(path, includeDescendants?)` - Remove a node from the tree
385
+ - **Example Pages**: New `/examples` route with interactive demos (same look-and-feel as web-multiselect)
386
+ - Landing page with feature cards linking to 7 example sections
387
+ - Basic Examples: tree rendering, expand level control, scroll to path, programmatic expand/collapse
388
+ - Drag & Drop: two-tree drag demo, touch drag instructions, drop placeholder customization
389
+ - Context Menu: callback-based menus, dynamic items, icons, disabled states, dividers
390
+ - Search & Filter: live filtering with `searchText`, `searchNodes()` query method
391
+ - Theming: CSS variable reference, theme examples (default, purple, dark, green)
392
+ - Data Structures: path-based hierarchy, custom separators, insert result validation
393
+ - Tree Editor: add/move/remove nodes with drag-drop and orderMember support
394
+ - **Drop Indicator Arrows**: Visual arrow indicators for glow-mode drop zones
395
+ - Arrows positioned at 66% of row width, centered vertically
396
+ - Uses Lucide SVG icons as data URIs: `arrow-big-up`, `arrow-big-down`, `arrow-big-right-dash`
397
+ - Child arrow rotated 45° for diagonal pointing effect
398
+ - Fully customizable via SCSS variables
399
+ - **Ctrl+Drag Copy Operation**: Full support for copying nodes via Ctrl+drag
400
+ - `isCopyAllowed` prop enables Ctrl+drag to copy instead of move
401
+ - `shouldAutoHandleCopy` prop (default: true) controls whether Tree auto-handles same-tree copies
402
+ - `true`: Tree creates copy with generated ID (`{id}_copy_{timestamp}`) - good for batch/offline mode
403
+ - `false`: User callback handles copy (for DB/API integration) - good for online/live mode
404
+ - Copy operations respect drop position (above/below/child) just like moves
405
+ - Visual feedback: `.ltree-drop-copy` class applied during copy operations
406
+ - **Position Support for copyNodeWithDescendants**: Enhanced copy method now supports sibling positioning
407
+ - New optional parameters: `siblingPath` and `position` ('above' | 'below')
408
+ - Copies can be placed at specific positions relative to siblings
409
+ - Uses same `orderMember` logic as `moveNode` for consistent ordering
410
+ - **Logging Infrastructure**: Categorized logging using vendored loglevel library
411
+ - Six log categories: `LTREE:INIT`, `LTREE:DATA`, `LTREE:RENDER`, `LTREE:INDEX`, `LTREE:DRAG`, `LTREE:UI`
412
+ - Color-coded console output with timestamps for easy debugging
413
+ - Disabled by default (silent mode) for production
414
+ - Exported utilities: `enableLogging()`, `disableLogging()`, `setLogLevel()`, `setCategoryLevel()`
415
+ - UI logging: node clicks, expand/collapse, selection changes, context menu
416
+ - Drag logging: drag start/end, drop operations, touch drag events
417
+ - Render logging: progressive rendering frame stats
418
+ - Index logging: async search indexing progress
419
+ - New `/dev/logging` demo page for testing log levels and categories
420
+ - **Performance Logging**: Dedicated performance measurement utilities
421
+ - Measures key operations: `insertArray` (conversion/sort/insert phases), `filterNodes`, `expandAll`, `collapseAll`
422
+ - Output includes duration, item count, per-item time, and items/sec throughput
423
+ - Summary view showing breakdown by phase with percentages
424
+ - Configurable threshold to only log operations slower than X ms
425
+ - Exported utilities: `enablePerfLogging()`, `disablePerfLogging()`, `setPerfThreshold(ms)`
426
+ - Browser console access: `window.components['svelte-treeview'].perf.enable()`
427
+ - Purple color-coded output for easy identification
428
+ - **Global Runtime API**: `window.components['svelte-treeview']` for browser console access
429
+ - `config` - Package info (name, version, author, license, repository, homepage) read from package.json at build time
430
+ - `version()` - Returns current version string
431
+ - `logging.enableLogging()` / `logging.disableLogging()` - Toggle all logging
432
+ - `logging.setLogLevel(level)` - Set level for all categories
433
+ - `logging.setCategoryLevel(category, level)` - Set level for specific category
434
+ - `logging.getCategories()` - List available log categories
435
+ - **Container-Scoped Scrolling**: New `containerScroll` option for `scrollToPath()`
436
+ - `scrollToPath(path, { containerScroll: true })` scrolls only within the nearest scrollable ancestor
437
+ - Prevents page-level scrolling when tree is inside a scrollable container
438
+ - Automatically finds the scrollable parent element (overflow: auto/scroll)
439
+ - Useful for search result navigation without disrupting page position
440
+
441
+ ### Enhanced
442
+ - **Drop Zone Styling**: Improved visual feedback during drag operations
443
+ - Semi-transparent zones (0.25 opacity) that become solid (0.85) when hovered
444
+ - Modern pastel color palette: sage green for Above, peach/coral for Below, lavender for Child
445
+ - Interactive controls in `/examples/drag-drop` to test all layout configurations
446
+ - **Drop Zone SCSS Variables**: Full customization of drop zone appearance via SCSS variables
447
+ - `$drop-zone-border-radius` - Border radius for all zones (default: 0)
448
+ - Per-zone variables for backgrounds, colors, and shadows in both inactive and active states:
449
+ - Above: `$drop-zone-above-bg`, `$drop-zone-above-color`, `$drop-zone-above-active-bg`, `$drop-zone-above-active-color`, `$drop-zone-above-active-shadow`
450
+ - Below: `$drop-zone-below-bg`, `$drop-zone-below-color`, `$drop-zone-below-active-bg`, `$drop-zone-below-active-color`, `$drop-zone-below-active-shadow`
451
+ - Child: `$drop-zone-child-bg`, `$drop-zone-child-color`, `$drop-zone-child-active-bg`, `$drop-zone-child-active-color`, `$drop-zone-child-active-shadow`
452
+ - **Drop Zone Positioning**: Moved drop zones from inside `.ltree-node-content` to `.ltree-node-row` level
453
+ - Eliminates padding-related gaps that made zones hard to reach
454
+ - More predictable positioning relative to the full row width
455
+ - **Wave2 Layout Overlap**: Added 10% overlap for Above/Below zones in wave2 layout
456
+ - Ensures first node's Above zone and last node's Below zone are always reachable
457
+ - Child zone shrunk to 80% height to accommodate overlap without zone collision
458
+ - **dropZoneStart Flexibility**: Now accepts both number (percentage) and string (any CSS value)
459
+ - Number: treated as percentage (e.g., `33` `33%`)
460
+ - String: used as-is (e.g., `"33%"`, `"50px"`, `"3rem"`)
461
+ - **Touch UX**: Added CSS properties to prevent text selection during touch drag
462
+ - `-webkit-user-select: none` and `-webkit-touch-callout: none` on node content
463
+ - **Ghost Element Styling**: New `.ltree-touch-ghost` CSS class with customizable CSS variables
464
+ - `--tree-ghost-bg`: Background color (default: rgba(59, 130, 246, 0.9))
465
+ - `--tree-ghost-color`: Text color (default: white)
466
+ - **Drop Placeholder Styling**: New `.ltree-drop-placeholder` and `.ltree-drop-placeholder-content` CSS classes
467
+ - **Drop Indicator Arrow SCSS Variables**: Full customization of arrow indicators via SCSS variables
468
+ - `$drop-arrow-above`, `$drop-arrow-below`, `$drop-arrow-child` - SVG data URIs for each direction
469
+ - `$drop-arrow-size` - Arrow size (default: 24px)
470
+ - `$drop-arrow-position` - Horizontal position within row (default: 66%)
471
+ - `$drop-arrow-above-rotation`, `$drop-arrow-below-rotation`, `$drop-arrow-child-rotation` - Rotation angles
472
+ - **Search Example Page Redesign**: Merged filter and search cards into unified search experience
473
+ - Single search input with live filtering and result navigation
474
+ - Prev/Next buttons to traverse search results with wrap-around
475
+ - Result counter showing "X of Y" position indicator
476
+ - Keyboard navigation: Enter = next result, Shift+Enter = previous
477
+ - Clickable result list with active item highlighting
478
+ - Auto-scroll to first result when searching
479
+
480
+ ### Fixed
481
+ - **Empty Tree Drop Placeholder**: Fixed drop placeholder not appearing when dragging to empty trees
482
+ - Added missing `ondragenter` handler to empty state divs
483
+ - Added `min-height: 60px` to `.ltree-empty-state` to ensure drop target is always reachable
484
+ - **Drag-Drop Demo ID/Path Mismatch**: Fixed bug where second node drop to first node didn't work on first try
485
+ - Root cause: `nextId++` post-increment caused id and path to use different values
486
+ - Fixed by extracting `const itemId = nextId++` before using in object properties
487
+ - **Tree Data Reset**: Fixed `insertArray` not clearing existing tree data when called with new/empty data
488
+ - Previously, setting `data = []` would not clear the tree - existing nodes remained visible
489
+ - Now `insertArray` properly resets root children, nodeCount, and maxLevel before inserting
490
+ - **Example Pages Data Insertion**: Added `isSorted={true}` to all example page Tree components
491
+ - Prevents "Could not find parent node" errors caused by `sortCallback` sorting data before insertion
492
+ - When `sortCallback` alphabetizes data, children could be inserted before parents (e.g., "AuthService" before "Services")
493
+ - `isSorted={true}` tells the tree to skip pre-sorting and only use `sortCallback` for display ordering
494
+ - **Search Example Async Index**: Added note explaining that search index is built asynchronously
495
+ - Added Enter key support for better UX when retrying searches
496
+ - Users are now informed to wait a moment if no results appear immediately after page load
497
+ - **Search Example Reactivity**: Made "Search Nodes (Query)" input reactive
498
+ - Added `$effect` to automatically trigger search when input changes
499
+ - **Glow Mode Border Radius**: Fixed border-radius appearing on glow drop indicators during drag
500
+ - Changed `$tree-node-content-border-radius` default from `4px` to `0`
501
+ - **Search Example Documentation**: Fixed incorrect prop name in search configuration table
502
+ - Changed `searchValueCallback` → `getSearchValueCallback` (correct prop name)
503
+ - Fixed callback signature from `(item: T) => string` to `(node: LTreeNode<T>) => string`
504
+ - **LTreeNode Type Export**: Added `LTreeNode` re-export from `types.ts`
505
+ - Fixes import errors when using `import type { LTreeNode } from '$lib/ltree/types'`
506
+ - **Search Example Endless Loop**: Fixed `$effect` causing infinite loop on search
507
+ - Used Svelte's `untrack()` to prevent reactive state updates from re-triggering the effect
508
+ - Effect now only reacts to `searchText` changes, not internal state mutations
509
+ - **Search Example Container Scroll**: Fixed prev/next navigation scrolling the entire page
510
+ - Now uses `scrollToPath(path, { containerScroll: true })` for container-scoped scrolling
511
+ - **Critical Performance Bug in insertArray**: Fixed O(n²) algorithm causing 85+ second load times
512
+ - Progressive render check was iterating all remaining nodes for every node at expandLevel
513
+ - With 17,000 nodes this caused ~145 million iterations instead of ~34,000
514
+ - Fix: Pre-compute last expandLevel index once, then use simple index comparison
515
+ - Result: Load time reduced from 85+ seconds to under 1 second
516
+ - **Global API Constants**: Fixed `__PACKAGE_NAME__ is not defined` error when using library in other projects
517
+ - Vite `define` constants only work during dev, not when library is built with `svelte-package`
518
+ - Added `scripts/generate-constants.js` to bake package.json values into `constants.generated.ts`
519
+
520
+ ### Important - Svelte 5 Performance
521
+
522
+ **Use `$state.raw()` for large datasets passed to Tree component**
523
+
524
+ When passing large arrays (1000+ items) to the Tree component, use `$state.raw()` instead of `$state()` to avoid severe performance degradation:
525
+
526
+ ```typescript
527
+ // SLOW - Svelte deeply proxies all 8000+ objects, causing 5000x slowdown
528
+ let treeNodes = $state<TreeNode[]>([])
529
+ treeNodes = response.data // Each item becomes a Proxy
530
+
531
+ // FAST - Array is reactive but items remain plain objects
532
+ let treeNodes = $state.raw<TreeNode[]>([])
533
+ treeNodes = response.data // Items stay as plain objects
534
+ ```
535
+
536
+ **Why this matters:**
537
+ - `$state()` creates deep proxies - every nested object becomes a Proxy
538
+ - Tree's `insertArray()` accesses multiple properties on each data item
539
+ - With 8000 items × ~10 property accesses = 80,000 proxy operations
540
+ - Proxy overhead: ~2.2ms per item vs ~0.0004ms for plain objects (5,500x slower)
541
+
542
+ **Symptoms of this issue:**
543
+ - Tree takes 15-90+ seconds to render with thousands of items
544
+ - Console shows `[Violation] 'message' handler took XXXXms`
545
+ - Same data loads instantly in isolated test environment
546
+
547
+ **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.
548
+ - Build now runs `npm run generate-constants` before `svelte-package`
549
+ - **Glow Mode Border Radius**: Added `!important` to `border-radius: 0` on glow classes to ensure override
550
+ - Removed `border-radius: 4px !important` from `.ltree-dragover-glow` class
551
+ - Removed `border-radius` from `.ltree-node-content` transition to prevent animation glitch
552
+ - **Glow Mode Drop Validation**: Fixed glow showing on invalid drop targets
553
+ - When `dragDropMode='cross'` prevents drops, glow no longer appears on hover
554
+ - Cleared `hoveredNodeForDrop` state when drop validation fails
555
+ - Debug logging for drop rejection now gated behind `shouldDisplayDebugInformation`
556
+ - **Glow Mode Border Radius Transition**: Fixed double border artifact on node hover during drag
557
+ - Added `border-radius` to transition property for smooth animation
558
+ - Eliminates visual conflict between base rounded corners and glow's straight corners
559
+ - **Ctrl+Drag dropEffect Timing**: Fixed copy operation failing with `dropEffect: none`
560
+ - Root cause: Node's `ondragover` read stale `dropOperation` prop before Tree updated it
561
+ - Fix: Node now reads `event.ctrlKey` directly to calculate dropEffect immediately
562
+ - Also added dropEffect confirmation in `ondrop` handlers for spec compliance
563
+ - **Missing isCopyAllowed Prop in Recursive Node**: Fixed `isCopyAllowed` not being passed to child nodes
564
+ - Caused Ctrl+drag to fail on non-root nodes since they defaulted to `isCopyAllowed=false`
565
+ - Added `{isCopyAllowed}` to recursive Node component call
566
+
567
+ ### Changed
568
+ - **BREAKING: onNodeDrop Signature**: Callback signature updated to include drop position
569
+ - Before: `onNodeDrop?: (dropNode, draggedNode, event) => void`
570
+ - After: `onNodeDrop?: (dropNode, draggedNode, position, event) => void`
571
+ - `position` is `'above'`, `'below'`, or `'child'` indicating where item should be placed
572
+ - `dropNode` can be `null` when dropping into empty tree or root drop zone
573
+
574
+ ## [4.4.0] - 2025-10-02
575
+
576
+ ### Added
577
+ - **External Update Method**: New `update()` method for programmatic prop updates from vanilla JavaScript
578
+ - Allows external code to update component props without Svelte reactivity
579
+ - Accepts partial object with any Tree props (excluding snippets/templates)
580
+ - Useful for HTML/JavaScript integration and dynamic configuration
581
+ - Example: `tree.update({ searchText: 'query', expandLevel: 3, data: newData })`
582
+
583
+ ### Fixed
584
+ - **Search Functionality**: Fixed search filtering in context-menu dev page
585
+ - Added missing `searchValueMember="name"` prop to enable proper search indexing
586
+ - Search now correctly filters nodes by name instead of filtering everything out
587
+
588
+ ### Changed
589
+ - **Code Cleanup**: Renamed internal "trie" references to "tree" for consistency
590
+ - Updated variable names in Tree.svelte, Node.svelte, and ltree-demo.ts
591
+ - Removed "trie" from package.json keywords
592
+ - Improved code readability and naming consistency throughout codebase
593
+
594
+ ## [4.3.1] - 2025-09-25
595
+
596
+ ### Enhanced
597
+ - **Async Callback Support**: Context menu callbacks now fully support async operations
598
+ - Updated `callback: () => void | Promise<void>` signature in `ContextMenuItem` interface
599
+ - Added automatic error handling for async callbacks with try/catch wrapper
600
+ - Menu item clicks properly await async operations before completing
601
+ - Errors in async callbacks are logged to console for debugging
602
+ - **Robust Error Handling**: Async callback failures don't break menu functionality
603
+ - Failed async operations are caught and logged automatically
604
+ - Developers can implement custom error handling within their callbacks
605
+ - Menu stays open on errors, allowing users to retry actions
606
+ - **Enhanced Dev Examples**: Added comprehensive async callback demonstrations
607
+ - Copy action with simulated network delay
608
+ - New folder creation with error simulation (20% failure rate)
609
+ - Database backup with long-running operation simulation
610
+ - Shows patterns for success/failure handling and conditional menu closing
611
+
612
+ ### Documentation
613
+ - **Async Patterns**: Examples showing proper async callback implementation
614
+ - **Error Handling**: Best practices for managing async operation failures
615
+ - **Menu Control**: Demonstrated conditional closing based on operation success/failure
616
+
617
+ ## [4.3.0] - 2025-09-25
618
+
619
+ ### Added
620
+ - **Enhanced Context Menu Control**: Context menu callback now receives `closeMenuCallback` parameter for programmatic menu control
621
+ - `contextMenuCallback?: (node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[]`
622
+ - Developers can now control when/if context menu closes after menu item actions
623
+ - Enables conditional closing patterns (e.g., don't close on cancel, only on success)
624
+ - Public `closeContextMenu()` method exported for external control
625
+ - **Context Menu Item Styling**: New `className?: string` property in `ContextMenuItem` interface
626
+ - Apply custom CSS classes to individual menu items for styling
627
+ - Supports multiple classes (space-separated strings)
628
+ - Example: `className: 'text-danger fw-bold'` for destructive actions
629
+ - **Enhanced Dev Examples**: Updated context menu examples to demonstrate new features
630
+ - Conditional menu closing patterns for different action types
631
+ - CSS class styling demonstrations with Bootstrap classes
632
+ - Improved UX patterns showing when to close vs keep menu open
633
+
634
+ ### Enhanced
635
+ - **Flexible Menu Behavior**: Context menu now supports various interaction patterns
636
+ - Immediate close after action completion
637
+ - Conditional close based on user confirmation
638
+ - Persistent menu for multi-step operations
639
+ - Custom styling per menu item type
640
+
641
+ ## [4.2.1] - 2025-09-24
642
+
643
+ ### Enhanced
644
+ - **Debug Context Menu Positioning**: Improved debug context menu to position relative to tree element instead of viewport
645
+ - Debug menu now appears 200px right and 100px down from each tree's top-left corner
646
+ - Supports multiple trees on same page with individual positioning
647
+ - Enhanced debug logging to include tree ID and calculated position coordinates
648
+ - Better for CSS development when tree is not at top-left of viewport
649
+ - **Debug Context Menu Robustness**: Enhanced debug mode to work with single-node trees
650
+ - Uses second node when available, falls back to first node for single-node trees
651
+ - More flexible node selection for debug menu display
652
+ - Improved reliability for development scenarios
653
+
654
+ ### Fixed
655
+ - **Debug Mode State Management**: Fixed context menu interference between debug mode and normal right-click menus
656
+ - Added `isDebugMenuActive` state tracking to prevent debug logic from hiding user-triggered menus
657
+ - Normal right-click context menus now work properly when debug mode is disabled
658
+ - Proper cleanup of debug state when switching between modes
659
+ - **Debug Mode Requirements**: Relaxed debug context menu requirements to support edge cases
660
+ - Changed minimum tree length requirement from `> 1` to `> 0` for better compatibility
661
+ - Debug mode now works with any non-empty tree structure
662
+
663
+ ## [4.2.0] - 2025-09-24
664
+
665
+ ### Added
666
+ - **Context Menu System**: Comprehensive context menu functionality with two implementation approaches
667
+ - **Callback-based Context Menus**: New `contextMenuCallback` prop that accepts a function `(node: LTreeNode<T>) => ContextMenuItem[]`
668
+ - **ContextMenuItem Interface**: New interface with `icon`, `title`, `isDisabled`, `callback`, and `isDivider` properties
669
+ - **Position Offset Configuration**: New `contextMenuXOffset` (default: 8px) and `contextMenuYOffset` (default: 0px) props for cursor clearance
670
+ - **Debug Mode**: New `shouldDisplayContextMenuInDebugMode` prop for persistent context menu display at fixed position (200px, 100px)
671
+ - **Snippet-based Support**: Maintains backward compatibility with existing `{#snippet contextMenu(node, closeMenu)}` approach
672
+ - **Enhanced Context Menu UX**:
673
+ - Auto-close on scroll events (mouse wheel, scrollbar, touch, programmatic)
674
+ - Auto-close on outside clicks
675
+ - Support for disabled menu items with visual feedback
676
+ - Support for menu dividers for visual organization
677
+ - Rich icon support for menu items
678
+ - **Development Tools**: New `/dev/context-menu` page with comprehensive examples
679
+ - Basic file system context menu example with conditional actions
680
+ - Advanced server management example with status-based and type-specific menus
681
+ - Real-time offset configuration testing
682
+ - Interactive demonstration of all context menu features
683
+ - Debug context menu mode with `shouldDisplayContextMenuInDebugMode` for easy styling development
684
+ - Navigation link added to main layout for easy access
685
+
686
+ ### Enhanced
687
+ - **CSS Styling**: Added comprehensive context menu styles in `main.scss`
688
+ - `.ltree-context-menu`, `.ltree-context-menu-item`, `.ltree-context-menu-icon`, `.ltree-context-menu-divider` classes
689
+ - Support for disabled states with `.ltree-context-menu-item-disabled`
690
+ - Flexible layout with proper hover effects and visual hierarchy
691
+ - **Type Safety**: Full TypeScript support for all context menu features
692
+ - **Documentation**: Comprehensive README and CLAUDE.md updates covering both implementation approaches
693
+
694
+ ### Fixed
695
+ - **Context Menu Scroll Behavior**: Fixed issue where context menu remained visible when scrolling
696
+ - Added scroll event listeners with capture phase to catch all scroll events
697
+ - Added wheel event listeners for mouse wheel scrolling
698
+ - Context menu now properly closes on any scroll interaction
699
+
700
+ ## [4.1.1] - 2025-09-23
701
+
702
+ ### Fixed
703
+ - **TreePathSeparator Default Value**: Fixed `treePathSeparator` parameter to properly default to '.' when not provided to Tree.svelte
704
+ - Previously, when `treePathSeparator` was undefined, the reactive effect would override the ltree's internal default
705
+ - Now defaults to '.' in the parameter destructuring, ensuring consistent behavior
706
+
707
+ ## [4.1.0] - 2025-09-23
708
+
709
+ ### Fixed
710
+ - **Critical Sorting Bug**: Fixed default sort method to sort by level first, ensuring proper hierarchical tree construction
711
+ - Previously sorted by parent path first, causing level 3 nodes to be inserted before level 2 nodes
712
+ - Now sorts by level (depth) first, then parent path, then display value
713
+ - Eliminates "Could not find parent node" errors when nodes are inserted out of level order
714
+ - **Progressive Rendering**: Fixed progressive rendering feature to work correctly with proper level-based sorting
715
+ - Progressive rendering now displays levels 1-2 immediately while deeper levels continue processing
716
+ - Improves perceived performance for large datasets by showing initial tree structure quickly
717
+ - **TreePathSeparator Reactivity**: Fixed Tree component to properly update internal separator when `treePathSeparator` prop changes
718
+ - Added reactive effect to update ltree's separator property when prop changes
719
+ - Prevents race conditions where data is processed with wrong separator
720
+ - Fixes filesystem demo and other custom separator use cases
721
+ - **Sort Functions in Examples**: Updated all demo sort functions to calculate level from path depth during sorting
722
+ - Home page, dev page, and filesystem examples now use path-based level calculation
723
+ - Ensures consistent level-first sorting across all demos and examples
724
+ - Prevents insertion failures in example applications
725
+
726
+ ### Enhanced
727
+ - **Test Coverage**: Added comprehensive test suite for sorting functionality
728
+ - Tests verify level-first sorting behavior with various hierarchical data structures
729
+ - Validates progressive rendering scenarios and sort correctness
730
+ - Uses Vitest framework for fast, reliable testing
731
+
732
+ ### Changed
733
+ - **Default Sort Algorithm**: Updated `_defaultSort` method to prioritize level over parent path for hierarchical correctness
734
+ - **Example Sort Functions**: All demo applications now use level-first sorting for consistent behavior
735
+
736
+ ## [4.0.1] - 2025-01-23
737
+
738
+ ### Fixed
739
+ - **treePathSeparator Propagation**: Fixed helper functions (`getParentPath`, `getRelativePath`, `getPathSegments`) to properly use the configured `treePathSeparator` instead of hardcoded "." separator
740
+ - All path manipulation functions now respect the custom separator setting
741
+ - Ensures consistent path handling throughout the tree operations when using custom separators like "/"
742
+ - Fixed `getRelativePath` to use `pathSeparator.length` instead of assuming single character
743
+ - Fixed `getLevel` to properly count segments with multi-character separators
744
+
745
+ ### Added
746
+ - **Test Suite**: Added comprehensive test coverage for ltree helper functions
747
+ - 24 test cases covering single-character, multi-character, and edge case separators
748
+ - Vitest testing framework integration with `npm run test` and `make test` commands
749
+ - Tests validate proper handling of separators like `"::"`, `"->>"`, `"<|>"` and complex edge cases
750
+
751
+ ## [4.0.0] - 2025-01-09
752
+
753
+ ### Added
754
+ - **Complete Showcase Site Redesign**: Comprehensive overhaul of the documentation and demo site
755
+ - **API Reference Page**: Complete tabbed reference with properties, methods, events, and templates tables
756
+ - **Professional Navigation**: Fixed-top navbar with burger menu, GitHub link, and responsive sidebar
757
+ - **Ocean Color Scheme**: Beautiful blue-themed design using Coolors.co palette (#00171F, #003459, #007EA7, #00A7E1, #FFFFFF)
758
+ - **Responsive Layout**: Mobile-first design with collapsible sidebar and backdrop overlay
759
+ - **Enhanced Examples**: Four comprehensive code examples with descriptions in tabbed interface
760
+ - **Docker Production Setup**: Complete containerization for static site deployment
761
+ - **Multi-stage Dockerfile**: Optimized build with Node.js builder and nginx production stage
762
+ - **Static Site Generation**: SvelteKit configuration for pre-rendered HTML pages
763
+ - **Make Commands**: Docker build, run, and management commands with custom registry support
764
+ - **Nginx Configuration**: Optimized serving with gzip, caching, and SPA routing support
765
+
766
+ ### Changed
767
+ - **Layout Architecture**: Moved from nested Bootstrap containers to clean, consistent structure
768
+ - **Fixed Navigation**: Top navbar with brand, burger menu, and GitHub link
769
+ - **Sidebar Design**: Fixed-width (280px) sidebar with consistent icon spacing
770
+ - **Footer Integration**: Professional footer with KeenMate branding
771
+ - **SvelteKit Configuration**: Updated for optimal static generation
772
+ - **Static Adapter**: Switched from adapter-auto to adapter-static for reliable builds
773
+ - **Prerendering**: Enabled SSR and prerender for all showcase pages
774
+ - **Build Output**: Optimized for nginx serving with proper fallback handling
775
+ - **Page Structure Consistency**: Standardized header structure across all showcase pages
776
+ - **Removed Redundant Containers**: Eliminated nested container-fluid wrappers
777
+ - **Clean Headers**: Direct h1 and description elements without Bootstrap grid overhead
778
+
779
+ ### Enhanced
780
+ - **Visual Design**: Professional styling throughout the showcase site
781
+ - **Fixed Icon Alignment**: Consistent 1.5rem width for sidebar navigation icons
782
+ - **Gradient Backgrounds**: Sophisticated color gradients across navbar, sidebar, and footer
783
+ - **Interactive Elements**: Hover effects, focus states, and smooth transitions
784
+ - **Typography**: Clear hierarchy with proper contrast and accessibility
785
+ - **User Experience**: Improved navigation and usability
786
+ - **Always-Visible Burger Menu**: Toggle sidebar on any screen size for flexible layout
787
+ - **Responsive Behavior**: Automatic sidebar hiding on mobile with backdrop close
788
+ - **Tab Navigation**: Full-width code examples with clean tab interface
789
+ - **Mobile Optimization**: Touch-friendly interactions and responsive text sizing
790
+
791
+ ### Fixed
792
+ - **Container Structure**: Resolved double-container issues causing layout inconsistencies
793
+ - **Sidebar Toggle**: Fixed burger menu functionality to work across all screen sizes
794
+ - **Static Generation**: Proper SvelteKit configuration for nginx-compatible static builds
795
+ - **Icon Spacing**: Consistent navigation icon width preventing text misalignment
796
+
797
+ ### Documentation
798
+ - **API Reference**: Complete tables for all component properties, methods, events, and templates
799
+ - **Usage Examples**: Real-world code examples including organization tree configuration
800
+ - **Docker Documentation**: Make commands and containerization setup
801
+ - **Responsive Design**: Mobile-first approach with professional styling
802
+
803
+ ## [4.0.0-rc.08] - 2025-01-08
804
+
805
+ ### Added
806
+ - **searchNodes() Method**: New public method `searchNodes(searchText)` that returns an array of matching nodes without filtering the tree display
807
+ - Programmatically search nodes using the internal search index
808
+ - Returns `LTreeNode<T>[]` array of matching nodes
809
+ - Useful for building custom search interfaces, suggestions, and result summaries
810
+ - **Configurable Path Separators**: New `treePathSeparator` property allows custom hierarchical path separators
811
+ - Default remains `"."` for backward compatibility (e.g., "1.2.3")
812
+ - Support for custom separators like `"/"` for file system style paths (e.g., "1/src/components")
813
+ - All path operations throughout the component respect the custom separator
814
+ - **Data Structure Showcase Page**: New comprehensive `/data-structure` showcase page with four detailed sections:
815
+ - **LTree Path Structure**: Understanding path-based hierarchical data model
816
+ - **Optimized Data Structure**: Precomputed values for better performance
817
+ - **Custom Path Separators**: Live demo with file system style paths using "/" separator
818
+ - **External Search & Data Management**: Managing search outside the tree component
819
+ - **Invalid Data Structures**: Common mistakes and unsupported patterns
820
+ - **Enhanced Search Showcase**: Added new `searchNodes()` method demonstration section to `/search` page
821
+ - Interactive search interface showing difference between `searchNodes()` and `filterNodes()`
822
+ - Live examples with result display and usage patterns
823
+ - **Insert Result Information**: New `insertResult` bindable property provides detailed information about data insertion
824
+ - `InsertArrayResult<T>` interface with successful count and failed nodes array
825
+ - Each failed node includes original data, processed node, and error message
826
+ - Useful for data validation, debugging, and handling incomplete datasets
827
+ - **Drag-over Visual Feedback**: New `dragOverNodeClass` property for highlighting nodes during drag operations
828
+ - Two built-in classes: `ltree-dragover-highlight` (dashed border) and `ltree-dragover-glow` (shadow effect)
829
+ - Automatic state management with proper drag event handling
830
+ - Provides clear visual feedback for drop targets during drag-and-drop operations
831
+
832
+ ### Changed
833
+ - **Documentation Updates**: Updated README.md, CLAUDE.md, and showcase pages with new features
834
+ - Added `searchNodes` to public methods documentation
835
+ - Added `treePathSeparator` to Tree Configuration properties table
836
+ - Updated architecture description to reflect configurable separators
837
+ - Fixed path requirements documentation to clarify separator flexibility
838
+ - **Navigation Enhancement**: Added "Data Structure" page to sidebar navigation with 🗂️ icon
839
+
840
+ ### Enhanced
841
+ - **Type System**: Updated `Ltree<T>` interface to include `searchNodes` method signature
842
+ - **Internal Architecture**: Enhanced `createLTree` function to accept configurable `treePathSeparator` parameter
843
+ - **Component Integration**: Updated `Tree.svelte` component to pass through `treePathSeparator` property
844
+
845
+ ### Fixed
846
+ - **Node Indentation**: Fixed `Node.svelte` indent style to use consistent per-level indentation instead of cumulative indentation
847
+ - Previously: Each level had exponentially increasing indent (level * indent-per-level)
848
+ - Now: Each level uses fixed CSS variable `--tree-node-indent-per-level` allowing proper CSS-based indentation control
849
+ - **Search Index Accuracy**: Fixed `insertArray` to only add successfully inserted nodes to `flatTreeNodes` array
850
+ - Prevents search index from returning incorrect node indices for nodes that failed to insert
851
+ - Failed nodes are no longer included in search operations, ensuring search results match visible tree structure
852
+ - **Error Message Clarity**: Improved `insertTreeNode` error messages to include the failing node's path
853
+ - Error format: `"Node: {path} - Could not find parent node: {parentPath}"`
854
+ - Makes debugging hierarchical data issues much clearer
855
+
856
+ ### Documentation
857
+ - **Comprehensive Examples**: Added working code examples for both basic and advanced use cases
858
+ - **Path Separator Flexibility**: Clarified that paths don't need to be dot-separated, can use any consistent separator
859
+ - **External Data Management**: Detailed examples of filtering data outside the tree component
860
+ - **Performance Optimization**: Guidelines for when to use precomputed values vs automatic calculations
861
+
862
+ ## [4.0.0-rc.07] - 2025-01-06
863
+
864
+ ### Added
865
+ - **Customizable Scroll Highlight**: New `scrollHighlightClass` property allows users to define custom CSS classes for scroll highlight effects
866
+ - **Built-in Highlight Options**: Added pre-built highlight classes:
867
+ - `ltree-scroll-highlight` - Background glow with blue color (default)
868
+ - `ltree-scroll-highlight-arrow` - Red arrow indicator positioned to the right of the node
869
+ - **Scroll Highlight Timeout Control**: New `scrollHighlightTimeout` property (default: 4000ms) controls duration of highlight effect
870
+ - **Enhanced scrollToPath Method**: Improved scroll highlighting with proper element targeting and CSS class management
871
+ - **Debug Logging Control for Indexer**: Added `shouldDisplayDebugInformation` property to Indexer class for consistent debug logging control
872
+
873
+ ### Changed
874
+ - **Removed CSS Animation Dependencies**: Scroll highlighting now uses pure CSS classes instead of CSS animations for better timeout control
875
+ - **Improved Element Targeting**: `scrollToPath` now targets `.ltree-node-content` specifically for more precise highlighting
876
+ - **Enhanced Documentation**: Updated README with comprehensive examples for highlight customization
877
+ - **Consistent Debug Logging**: All indexer console.log messages now respect the `shouldDisplayDebugInformation` flag for unified logging control
878
+
879
+ ### Fixed
880
+ - **Scroll Highlight Duration**: Fixed issue where CSS animations overrode JavaScript timeout values
881
+ - **Element Selection**: Improved DOM element selection for scroll highlighting functionality
882
+ - **LTree Path Traversal**: Fixed `expandNodes` and `collapseNodes` methods by correctly prefixing path segments with 'x' prefix to match internal tree structure storage
883
+
884
+ ## [4.0.0-rc.05] - 2025-09-05
885
+
886
+ ### Added
887
+ - **Optimized Async Search Indexing**: Improved indexing implementation that processes entire queue at once during idle time instead of small batches
888
+ - **Enhanced Indexing Performance**: Increased batch size from 100 to 1000 nodes and streamlined queue processing
889
+ - **Better Debug Logging**: Added conditional debug logging for indexing operations when `shouldDisplayDebugInformation` is enabled
890
+
891
+ ### Changed
892
+ - **Indexing Architecture**: Refactored async indexing to process all queued nodes in a single idle callback rather than batched processing
893
+ - **Queue Management**: Simplified indexing queue processing with more efficient completion handling
894
+ - **TypeScript Support**: Added `Tuple<T, U>` type import for enhanced type safety
895
+
896
+ ### Performance
897
+ - **Faster Indexing**: Single-pass indexing of entire queue reduces overhead and callback scheduling
898
+ - **Reduced Idle Callbacks**: Less frequent but more efficient use of `requestIdleCallback`
899
+ - **Improved Memory Usage**: More efficient queue management with immediate processing
900
+
901
+ ## [4.0.0] - 2025-09-01
902
+
903
+ ### Added
904
+ - **Asynchronous Search Indexing**: Search indexing now uses `requestIdleCallback` for non-blocking performance
905
+ - **Statistics Tracking**: New `statistics` getter provides real-time data:
906
+ - `nodeCount`: Total number of nodes in the tree
907
+ - `maxLevel`: Maximum depth level of the tree
908
+ - `filteredNodeCount`: Number of nodes currently visible when filtering
909
+ - `isIndexing`: Boolean indicating if search indexing is in progress
910
+ - `pendingIndexCount`: Number of nodes pending indexing
911
+ - **Expand Level Control**: New `expandLevel` property (default: 2) automatically expands nodes up to specified depth
912
+ - **Drag & Drop Properties**: Added `isDraggableMember` and `isDropAllowedMember` for fine-grained drag & drop control
913
+ - **Debug Information Panel**: Enhanced debug display with collapsible interface showing tree statistics and indexing progress
914
+
915
+ ### Changed
916
+ - **Breaking**: Renamed internal references from "Trie" to "LTree" for consistency
917
+ - **Breaking**: `trieId` property renamed to `treeId`
918
+ - **Search Performance**: Tree now renders immediately while search indexing happens asynchronously
919
+ - **Debug Styling**: Updated debug panel styling to use `em` units with reduced padding
920
+
921
+ ### Performance
922
+ - **Non-blocking UI**: Tree renders immediately while search indexing occurs during browser idle time
923
+ - **Improved Large Dataset Handling**: Async indexing prevents UI freezing with large data sets
924
+ - **Batch Processing**: Search indexing processes nodes in batches during idle periods
925
+ - **Graceful Degradation**: Falls back to `setTimeout` on browsers without `requestIdleCallback` support
926
+
927
+ ### Documentation
928
+ - Added comprehensive documentation for async search indexing
929
+ - Added warning about search indexing requirements
930
+ - Enhanced API documentation with new properties and statistics
931
+ - Updated performance section highlighting async capabilities