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

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 (40) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +39 -26
  3. package/ai/basic-setup.txt +336 -339
  4. package/ai/import-patterns.txt +290 -271
  5. package/ai/styling-theming.txt +355 -354
  6. package/component-variables.manifest.json +145 -0
  7. package/dist/components/Node.svelte +42 -36
  8. package/dist/components/Tree.svelte +73 -1
  9. package/dist/components/Tree.svelte.d.ts +54 -3
  10. package/dist/components/TreeProvider.svelte +1 -0
  11. package/dist/constants.generated.d.ts +1 -1
  12. package/dist/constants.generated.js +1 -1
  13. package/dist/core/TreeController.svelte.d.ts +56 -4
  14. package/dist/core/TreeController.svelte.js +330 -70
  15. package/dist/index.d.ts +1 -1
  16. package/dist/ltree/ltree-node.svelte.js +2 -2
  17. package/dist/ltree/ltree.svelte.d.ts +1 -1
  18. package/dist/ltree/ltree.svelte.js +48 -11
  19. package/dist/ltree/types.d.ts +6 -0
  20. package/dist/styles/animations.css +17 -0
  21. package/dist/styles/base.css +43 -0
  22. package/dist/styles/checkbox.css +83 -0
  23. package/dist/styles/context-menu.css +134 -0
  24. package/dist/styles/controls.css +4 -0
  25. package/dist/styles/dark-mode.css +139 -0
  26. package/dist/styles/debug.css +45 -0
  27. package/dist/styles/drag-drop.css +170 -0
  28. package/dist/styles/drop-zones.css +270 -0
  29. package/dist/styles/floating.css +5 -0
  30. package/dist/styles/loading.css +36 -0
  31. package/dist/styles/main.css +44 -0
  32. package/dist/styles/node.css +60 -0
  33. package/dist/styles/states.css +92 -0
  34. package/dist/styles/toggle-icons.css +97 -0
  35. package/dist/styles/variables.css +215 -0
  36. package/dist/styles.css +1029 -778
  37. package/package.json +106 -102
  38. package/dist/styles/main.scss +0 -1074
  39. package/dist/styles.css.map +0 -1
  40. package/dist/styles.scss +0 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.0.0-rc10] - 2026-06-10
11
+
12
+ ### Changed
13
+ - **CSS custom properties rescoped from `:root` to `.ltree-container`**: All `--ltree-*` declarations now live on the tree's root element instead of the document root. This mirrors the `:host`-scoped pattern from `@keenmate/web-multiselect` / `web-daterangepicker` and is the only way `--base-*` theming actually works at subtree scope. Previously, a wrapper around the tree that set `--base-accent-color: red` had no effect because `--ltree-primary`'s `var(--base-accent-color, ...)` substitution was frozen at `:root`. With the new scope, the substitution recomputes on `.ltree-container` (a descendant of the wrapper), and `--base-*` set on any ancestor flows through correctly. Multiple trees on the same page can now be themed differently via subtree wrappers.
14
+
15
+ **Consumer migration**: setting `--ltree-*` on a wrapper element (e.g., `.my-theme { --ltree-primary: red }`) no longer cascades into the tree, because `.ltree-container` has its own direct rule for every `--ltree-*` token. To theme a subtree, prefer `--base-accent-color` (and the rest of the `--base-*` taxonomy) on the wrapper — every primary-derived tint follows via the existing `color-mix()` chains. To override an `--ltree-*` directly, target `.ltree-container`: `.my-wrap .ltree-container { --ltree-primary: red }`.
16
+
17
+ ### Added
18
+ - **`--ltree-bg` CSS variable**: The tree's `.ltree-container` now paints its own default background (`var(--base-main-bg, light-dark(#ffffff, #1a1a1a))`) so consumers don't need to wrap the tree in a colored container for a visible surface. The dark-mode CSS flips `--ltree-bg` alongside the other surface tokens. Set `--ltree-bg: transparent` to restore the pre-rc10 layered behavior.
19
+ - **Built-in dark mode with belt-and-suspenders coverage**: New `dark-mode.css` partial in an `@layer overrides;` cascade catches all four signals — OS preference via `@media (prefers-color-scheme: dark)`, framework theme classes (`[data-theme="dark"]`, `[data-bs-theme="dark"]`, `.dark`), per-instance opt-in via the new `theme` prop on `<Tree>`, and symmetric `light` selectors so a single tree can be forced back to light on a dark page. Every `--ltree-*` color variable's fallback now uses `light-dark(<light>, <dark>)` so consumers who declare `html { color-scheme: light dark }` get OS-aware behavior automatically with zero JavaScript.
20
+ - **`theme` prop on `<Tree>`**: `'dark' | 'light' | null | undefined`. Forwards to the root `.ltree-container` as `data-theme`, where the stylesheet's per-instance selectors (`.ltree-container[data-theme="dark"]`) take over. Leave `undefined` to inherit from the page (OS, framework class, etc.).
21
+ - **`--ltree-elevated-bg`, `--tree-ghost-shadow` CSS variables**: `elevated-bg` reads through the canonical `--base-elevated-bg` chain for surfaces above the main background (currently feeds the context-menu chain); `tree-ghost-shadow` replaces a hardcoded `rgba(0,0,0,0.3)` literal in the touch drag ghost so consumers can recolor or remove the shadow.
22
+ - **`getIsDropAllowedCallback` prop + `getNodeIsDropAllowed(node)` method**: Callback variant for the existing `isDropAllowedMember` data-field prop, matching the pattern rc09 introduced for `getIsExpandedCallback` / `getIsSelectableCallback` / `getIsSelectedCallback`. Seeded at `insertArray` time. Precedence: callback > member > default. Additionally, `getIsDraggableCallback` is now actually applied during the seed walk (the prop existed in rc09 but inert at insert-time — only consumed lazily in some paths). Precedence: callback > member > default for both.
23
+
24
+ ### Changed
25
+ - **CSS file layout aligned with the Bliss web-component guidelines**: All `_<partial>.css` renamed to `<partial>.css` (the underscore was a legacy SASS convention and these are plain CSS modules). `main.css` now declares `@layer variables, component, overrides;` and wraps every `@import` in `layer(...)` — consumers' unlayered overrides automatically beat every rule in the library, no `!important` needed. New canonical Tier-2 stubs (`controls.css`, `floating.css`, `animations.css`) added even where empty, so the file set is identical across the KeenMate component suite. `@keyframes bounce` and `@keyframes ltree-spin` moved into `animations.css`.
26
+ - **Loading overlay background adapts to theme**: `--ltree-loading-bg` no longer hardcodes `rgba(255, 255, 255, 0.8)`; it now composes 80% of `--base-main-bg` against the page so the semi-transparent overlay reads correctly on both light and dark surfaces.
27
+ - **`component-variables.manifest.json` extended**: `base-elevated-bg` added to the `baseVariables` list; `ltree-elevated-bg` and `tree-ghost-shadow` added to `componentVariables`. The manifest now stays in sync with the new variable surface.
28
+
29
+ ### Fixed
30
+ - **Virtual scroll stuck at bottom after filter shrinks the tree**: When the user scrolled down and then typed a search filter, `visibleFlatNodes` collapsed and `vsTotalHeight` shrank below the previous scroll position. The browser silently clamped the container's actual `scrollTop`, but the controller's `vsScrollTop` state kept its stale large value — so `vsStartIndex` fell out of range, `flatNodesToRender` was empty, and the scrollbar appeared frozen near the bottom with no content rendered. A new `$effect` now reacts to `vsTotalHeight` and clamps both `vsScrollTop` and the container's `scrollTop` to the new max whenever content shrinks below the current scroll position.
31
+ - **Double-click expand silently broken in `clickBehavior='select'` mode**: The browser's native `dblclick` event never fired because the first click triggered focus → `_setFocusedNode` bumped `node._rev` → the flat-mode `{#each}` destroyed and recreated the row, so the second click landed on a different DOM element and the browser refused to synthesize a `dblclick`. The controller now detects double-clicks manually (`_lastSelectClickPath` / `_lastSelectClickTime`, 400 ms threshold) so a rapid second click on the same node toggles its expand state regardless of re-render. Node-level `_onNodeDblClicked` removed (it could double-toggle if the browser ever did fire `dblclick`).
32
+
33
+ ## [5.0.0-rc09] - 2026-06-07
34
+
35
+ ### Added
36
+ - **`selectionMode: 'single' | 'multi'` prop** (default `'single'`): Decides how highlight cardinality and modifier keys behave. In `'single'`, plain click sets highlight to one node and Ctrl/Shift+click degrade to plain click; Shift+Arrow and Enter are no-ops. In `'multi'`, Ctrl+click toggles, Shift+click extends a range from the focused node, Shift+Arrow extends one step, and Enter toggles highlight on the focused node. Matches the [selection-highlight-model.md](selection-highlight-model.md) decision record.
37
+ - **Implicit highlight → selection mirroring when `showCheckboxes` is false**: Every change to `highlightedPaths` is mirrored into `selectedPaths`, and `onSelectionChange` fires alongside `onHighlightChange`. When checkboxes are visible the two sets stay 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 }`.
38
+ - **`clickTogglesCheckbox` prop**: Boolean (default `false`). When `true` AND `showCheckboxes` is on AND the node is selectable, a plain click on the node label runs the checkbox-toggle path instead of focusing/highlighting — `focusedNode` and `highlightedPaths` stay untouched. Expand-on-click still fires when `clickBehavior` is `'expand'` or `'expand-and-focus'`. Modified clicks (Ctrl/Shift) fall through to the normal multi-highlight path so range/toggle selection still works.
39
+ - **`getIsExpandedCallback`, `getIsSelectableCallback`, `getIsSelectedCallback` props**: Callback variants for the existing `isExpandedMember` / `isSelectableMember` / `isSelectedMember` data-field props, matching the same pattern as `getIsDraggableCallback` and `getIsCollapsibleCallback`. All three are seed-only — invoked once per node during `insertArray` — so subsequent user mutations (checkbox clicks, expand button) aren't overridden by the callback. Precedence: callback > member > default. The post-insert `selectedPaths` seeding walk now also triggers when `getIsSelectedCallback` is set, not just when `isSelectedMember` is set.
40
+ - **Lucide SVG toggle icons**: All built-in `ltree-icon-*` classes (default chevron, alt filled-triangle, plus/minus, arrow, leaf) now render Lucide SVGs via `mask-image` + `background-color: currentColor` instead of UTF-8 glyph characters (`▶ ▼ ⯈ ⯆ + − → ↓ •`). Icons inherit text color automatically, render cleanly at any size, and don't depend on font fallback. Zero added DOM nodes — still a single `::before` pseudo-element per toggle.
41
+ - **`--ltree-toggle-icon-size` / `--ltree-toggle-icon-width` / `--ltree-toggle-icon-color` CSS variables**: Runtime overrides for the toggle column's icon size (default `16px`, was `12px`), column width (`20px`), and color (`#6c757d`). The SCSS variables (`$tree-toggle-icon-*`) still drive the build-time defaults; the CSS vars are bridged via `var(--…, #{$…})` so users can scale icons live without recompiling SCSS.
42
+ - **Toggle icon live demo on `/examples/theming`**: A new card with radio controls for the four built-in icon sets and the `toggleIconMode` (`rotate` / `swap`) prop, wired to a live `<Tree>`. A code snippet under the controls reflects the current selection so users can copy-paste the props they see.
43
+ - **Full CSS-variable theming surface (`--ltree-*` with `--base-*` chain)**: Every styling value is exposed as a CSS custom property at `:root`. The resolution chain is end-user override → `--base-*` token (shared with other `@keenmate/*` web components) → hardcoded default. Roughly fifty new variables covering typography, node layout, toggle icon, checkbox, selection / highlight / drag states, drop zones (pastel + glow modes), context menu, debug info, scroll highlight, and loading overlay. Pattern matches `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`.
44
+
45
+ ### Changed
46
+ - **`!isSelectable` now also gates highlight (and therefore the mirrored selection in no-checkbox mode), not just the checkbox render**: Click handlers and Shift+arrow range expansion skip non-selectable nodes when building the highlight set. Focus and arrow navigation are NOT gated — the focused row can still land on `!isSelectable` nodes so consumers can show external detail panels (sidebars, breadcrumbs, etc.).
47
+ - **Right-click no longer moves focus or highlight**: `_onNodeRightClicked` only opens the context menu at the right-clicked node. Consumers reading the focused/highlighted set inside `getContextMenuItemsCallback` should now use the `node` argument that the callback receives, since right-click no longer rewrites `focusedNode` / `highlightedPaths`. The selected-nodes argument continues to reflect whatever multi-highlight the user had before opening the menu.
48
+ - **Drag and drop now moves the whole highlight set when the dragged node is in `highlightedPaths`** (same-tree, `operation: 'move'`, `autoHandleMove: true`): the algorithm picks the **top-level selected** 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).
49
+ - **Multi-drag now chains subsequent moves 'after' the previously moved subtree** (was: subsequent moves landed as children of `dropNode` regardless of the requested position). Dropping selection {A, B, C} 'after D' now produces `[D, A, B, C]` as siblings (previously: `[D]` at root with A/B/C as children of D). 'before D' produces `[A, B, C, D]`. 'child of D' produces D with children `[A, B, C]`. Source order is preserved within the moved set.
50
+ - **Dragging a non-highlighted node now replaces the highlight with that single node before the drag begins**: Matches the Windows Explorer / macOS Finder convention where mousedown on an unselected item selects it. Previously, the prior highlight stayed visible (the `click` event never fired because the user dragged instead of releasing) while the drag silently carried only the single grabbed node — making it impossible to tell what would actually move. The replacement runs in `_onNodeDragStart` and fires `onHighlightChange` / mirrors to `selectedPaths` like a plain click. Skipped if the dragged node is already in the highlight set (multi-drag) or is not selectable.
51
+ - **Keyboard: Enter toggles focused highlight in multi mode (was: always expand/collapse)**. In single mode Enter is now a no-op (falls through to the host). Space falls back to expand/collapse when no checkboxes are shown, but toggles the focused node's checkbox when `showCheckboxes` is on.
52
+ - **Ctrl/Shift+click on a node no longer toggles expand/collapse**: The modifier-click gesture is reserved for highlight management (toggle / range). Previously, in the default `clickBehavior='expand-and-focus'` mode, modified clicks both updated the highlight set AND expanded/collapsed the row, which made it impossible to multi-select folders without their contents flickering open. Plain click still expands as before. Matches OS file explorer conventions.
53
+ - **Stylesheet ported from SCSS to pure CSS**: `src/lib/styles/main.scss` (1095 lines, one file) split into eleven partials under `src/lib/styles/` (`_variables.css`, `_base.css`, `_node.css`, `_toggle-icons.css`, `_checkbox.css`, `_states.css`, `_drag-drop.css`, `_drop-zones.css`, `_context-menu.css`, `_debug.css`, `_loading.css`) plus a `main.css` entry point that chains them via CSS `@import`. SCSS `@mixin` constructs replaced with shared base-class boilerplate. Build now uses `lightningcss-cli` (one devDep) to bundle the imports into `dist/styles.css`; `sass` removed from devDependencies. Pattern matches the file layout in `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`.
54
+ - **`--ltree-rem` base sizing unit**: New CSS variable (default `10px`) drives every dimension via `calc(N * var(--ltree-rem))` — font sizes, paddings, margins, widths, radii, spinner size, etc. Set `--ltree-rem` once to scale the whole component proportionally (e.g., `--ltree-rem: 12px` for 20% larger). Set it to `1rem` to scale with document font-size (Pure Admin pattern). Default rendering is visually identical to the previous absolute-px output. Hairline borders (`1.5px` checkbox, `3px` glow) and animation timings stay absolute. Matches the `--ms-rem` / `--drp-rem` pattern in the sibling packages.
55
+ - **`component-variables.manifest.json` published at the package root**: Mirrors the schema used by `@keenmate/web-multiselect` and `@keenmate/web-daterangepicker`. Lists every `--base-*` token the component reads (with `required: true/false` flags) and every `--ltree-*` variable the component publishes (categorized: `color`, `sizing`, `spacing`, `border`, `surface`, `typography`, `state`, `drop-zone`, `context-menu`, `icon`, `animation`). Intended for IDE auto-completion, Storybook integration, and theme-designer tooling. Exported via the `./component-variables.manifest.json` package entry.
56
+ - **Hover background follows `--ltree-primary` by default**: `--ltree-node-hover-bg` default changed from solid `#f8f9fa` to `color-mix(in srgb, var(--ltree-primary) 8%, transparent)`. Themes that set only `--ltree-primary` now get a matching hover tint automatically; the explicit `--base-hover-bg` token override still wins over the derived default. Visually similar to the previous gray at default primary `#0d6efd`.
57
+ - **`--ltree-checkbox-focus-ring` split into tunable parts**: `--ltree-checkbox-focus-ring-width` (default `2px`) and `--ltree-checkbox-focus-ring-color` (default `color-mix(--ltree-primary 25%)`) — the composed `--ltree-checkbox-focus-ring` shorthand still works and now references both. Set just one part to widen the ring or recolor it without redeclaring the full box-shadow.
58
+
59
+ ### Breaking
60
+ - **Default `selectionMode = 'single'` changes click semantics for existing multi-select users**: Previously Ctrl/Shift+click always built a multi-highlight regardless of any prop. Now the default is single-select — to keep the old behaviour, opt in with `selectionMode='multi'`. Plain click + Arrow nav are unchanged.
61
+ - **`selectedPaths` becomes populated in no-checkbox trees** because highlight is mirrored into selection. Consumers reading `selectedPaths` from no-checkbox `<Tree>` instances will now see values that previously stayed empty. To keep the old empty-`selectedPaths` behaviour, render checkboxes (and ignore them with CSS) or keep your own derived "real selection" set.
62
+ - **`lastHighlightedPath` (private), `isHighlightAnchor` (public on `LTreeNode`), `.ltree-highlight-anchor` (CSS class), `--ltree-highlight-anchor-width` / `--ltree-highlight-anchor-color` (CSS vars), and the matching `component-variables.manifest.json` entries removed**: The focused node now serves as the anchor for Shift+range operations. Internal Shift+Arrow uses a hidden `_shiftCursor` field — consumers never see it.
63
+ - **`./styles.scss` package export removed.** Consumers using `import '@keenmate/svelte-treeview/styles.scss'` must switch to `import '@keenmate/svelte-treeview/styles.css'` (the canonical export, unchanged). For per-section theming, individual partials are now available under `./styles/_<section>.css`.
64
+ - **`--ltree-primary-rgb` / `--ltree-success-rgb` / `--ltree-danger-rgb` removed** (along with `--base-accent-color-rgb` / `--base-success-color-rgb` / `--base-danger-color-rgb` from the `--base-*` chain). Tinted variants are now derived from the parent color via `color-mix(in srgb, var(--ltree-x) N%, transparent)` — so setting `--ltree-primary: red` automatically tints the multi-select background, dragover background, drop placeholder, focus ring, scroll highlight, debug panel, and danger context-menu hover without needing a companion `-rgb` variable. Matches the `@keenmate/web-multiselect` pattern. Minimum browser baseline: Chrome 111 / Safari 16.4 / Firefox 113 (all from March-May 2023).
65
+ - **Indent scaling semantics**: `--ltree-node-indent-per-level` was previously `0.5rem` (document-relative — followed `html { font-size }`). It is now `calc(0.8 * var(--ltree-rem))` — same visual default (8 px), but scales with `--ltree-rem` instead of document `rem`. To restore the old document-relative behavior, set `--ltree-rem: 1rem`.
66
+ - **SCSS variable overrides (`$tree-*`, `$drop-*`, `$primary-*`) no longer supported.** Migrate to CSS custom properties at runtime: `--ltree-node-font-size: 16px` instead of `@use '@keenmate/svelte-treeview/styles.scss' with ($tree-node-font-size: 16px)`. The full variable surface is documented at `/examples/theming`.
67
+
68
+ ### Fixed
69
+ - **Stale `dragOverNodeClass` highlights piling up across rows during drag**: Each `Node` tracked its own local `isDraggedOver` flag, toggled by the row's own `ondragover` / `ondragleave`. HTML5 `dragleave` is unreliable when crossing between sibling rows fast (it can fire with cursor coordinates still inside the leaving row's rect, or skip altogether when `dragenter` on the next row beats it) — so the dashed-green dragover highlight got stuck on rows the cursor had already left, leaking one stale highlight per missed leave. The class is now applied via direct `classList.add/remove` on the previous-and-new DOM nodes from a controller `$effect` that watches the centralized `hoveredNodeForDrop`, matching the existing touch-drag pattern. This guarantees exactly one highlighted row at a time and the cost stays O(1) per node-crossing regardless of tree size — no per-Node prop propagation. The dead per-node `isDraggedOver` state was removed.
70
+ - **`ltree-selected-brackets` invisible when checkboxes are enabled**: The adjacent-sibling rule that hides the `❯ ❮` pseudo-elements (because the checkbox already signals selection) left the class with zero visual effect — highlighted nodes looked identical to unhighlighted ones. The suppressed selector now also applies `font-weight: bold` and `color: var(--ltree-primary)` as a fallback so the highlight is always visible.
71
+ - **Independent checkbox mode auto-checking parents**: In `checkboxMode="independent"`, the post-toggle ancestor walk still ran and propagated `isSelected = true` up to parents when all descendants happened to be 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.
72
+ - **`Node.svelte` stale icon/highlight classes after `tree.update()`**: `expandIconClass`, `collapseIconClass`, `leafIconClass`, `highlightedNodeClass`, `focusedNodeClass`, `dragOverNodeClass`, and `allowCopy` were destructured from the shared `NodeConfig` `$state` proxy as primitive snapshots, so once Node mounted they never updated. Replaced with `$derived(config.x)` so runtime updates (including the new icon-set radios in the theming demo) actually propagate.
73
+
10
74
  ## [5.0.0-rc08] - 2026-05-27
11
75
 
12
76
  ### Added
package/README.md CHANGED
@@ -6,18 +6,27 @@ A high-performance, feature-rich hierarchical tree view component for Svelte 5 w
6
6
 
7
7
  Browse interactive code examples and the full API reference at **[svelte-treeview.keenmate.dev](https://svelte-treeview.keenmate.dev)**
8
8
 
9
- ## What's New in v5.0.0-rc08
9
+ ## What's New in v5.0.0-rc10
10
10
 
11
- - **`{ silent: true }` on highlight/selection methods**: Update tree state from URL params or other external sources without firing `onNodeClick` / `onHighlightChange` / `onSelectionChange` perfect for deep links where you don't want the change callback to re-trigger your form loader. Applies to `highlightNode`, `highlightNodes`, `clearHighlight`, and `deselectAll`.
12
- - **Array variants for expand/collapse**: `expandNodes`, `collapseNodes`, `expandAll`, and `collapseAll` now take `string | string[]`. Open or close several places in one call.
13
- - **Exclusive focus mode**: Pass `{ exclusive: true }` to `expandNodes` / `expandAll` to open the target path and collapse everything else in a single passno two-step flicker compared to `collapseAll() + expandNodes(path)`.
14
- - **`noEmit` option for batching**: Suppress the change emit on individual expand/collapse calls when chaining several operations; emit once at the end.
11
+ - **Built-in dark mode covering all four signals**: A new `dark-mode.css` partial flips the tree's surface, text, border, and accent palette when any of the canonical signals is active OS preference (`prefers-color-scheme: dark`), page `<html style="color-scheme: dark">` resolved via `light-dark()`, framework theme classes (`[data-theme="dark"]`, `[data-bs-theme="dark"]`, `.dark`), and the new per-instance `theme` prop. Symmetric `light` selectors let a single tree force light on an otherwise-dark page. Zero JavaScript — pure CSS resolution.
12
+ - **`theme` prop on `<Tree>`**: `'dark' | 'light' | null | undefined`. Forwarded to the root `.ltree-container` as `data-theme`, where per-instance CSS selectors take over and beat ambient signals. Leave `undefined` to inherit from the page.
13
+ - **CSS variables rescoped from `:root` to `.ltree-container`**: Mirrors the `:host`-scoped pattern from sibling `@keenmate/*` components and is the only way `--base-*` theming actually works at subtree scope. A wrapper around the tree that sets `--base-accent-color: red` now re-tints the tree (it previously had no effect because the substitution was frozen at `:root`). Multiple trees on the same page can be themed differently via wrapper-scoped `--base-*` overrides. Consumer note: setting `--ltree-*` directly on a wrapper no longer cascades use `--base-*` on the wrapper or target `.ltree-container` explicitly.
14
+ - **`--ltree-bg` the tree paints its own surface**: `.ltree-container` gets a `background: var(--ltree-bg)` so consumers don't need to wrap the tree in a colored container for a visible surface. Override to `transparent` to restore the pre-rc10 layered behavior. Companion `--ltree-elevated-bg` reads through the `--base-elevated-bg` chain for floating chrome (context menu).
15
+ - **CSS file layout aligned with the Bliss web-component guidelines**: All `_xxx.css` partials renamed to `xxx.css`, `main.css` now declares `@layer variables, component, overrides;` and imports each partial into the matching layer. Consumers' unlayered overrides automatically beat every rule in the library — no `!important` needed. Note: if your app ships an unlayered universal CSS reset (`* { padding: 0 }` from normalize / Bootstrap / Tailwind preflight / etc.), wrap it in a low-priority `@layer reset` or the tree's defaults won't apply.
16
+ - **`getIsDropAllowedCallback` prop + `getIsDraggableCallback` seeded at insert-time**: Callback variant for `isDropAllowedMember`, matching the pattern rc09 introduced for `getIsExpandedCallback` / `getIsSelectableCallback` / `getIsSelectedCallback`. Also, the existing `getIsDraggableCallback` prop is now actually applied during the seed walk — previously it was only consumed lazily in some paths.
17
+ - **Bug fixes**: Virtual scroll no longer gets stuck at bottom after a filter shrinks the tree. Double-click to expand in `clickBehavior='select'` mode finally works (the browser's native `dblclick` event couldn't fire because the first click destroyed the row via the focus re-render; the controller now detects the double manually).
15
18
 
16
- ## What's New in v5.0.0-rc07
19
+ ## What's New in v5.0.0-rc09
17
20
 
18
- - **`isSelectedMember` prop**: Seed `selectedPaths` directly from your data point the prop at a boolean field and every node where it's truthy lands pre-checked after `insertArray`.
19
- - **`isSelectableMember` fully wired through `Tree`**: The prop was already on the core types but wasn't reachable via the component. Now end-to-end usable to control checkbox rendering and clickability per node.
20
- - **Fix: filter race during async indexing**: `bind:searchText` no longer hides the entire tree if the FlexSearch index is still being built when the filter is applied. The filter now re-applies itself automatically once indexing completes, regardless of `indexerBatchSize` or dataset size.
21
+ - **Three-level selection model**: New `selectionMode: 'single' | 'multi'` prop (default `'single'`) cleanly separates the **focused** node (cursor), the **highlighted** set (Ctrl/Shift+click range), and **selected** checkboxes. When `showCheckboxes` is off, highlight mirrors into `selectedPaths` automatically — so consumers always have a single "what's selected" set to read regardless of UI style. Programmatic `highlightNode` / `highlightNodes` / `clearHighlight` respect `{ silent: true }`.
22
+ - **Multi-drag the whole highlight set**: With `selectionMode='multi'`, Ctrl/Shift+click several rows and drag any one of them the entire top-level-selected subset moves together. Descendants of highlighted ancestors ride along inside their subtree (not extracted). The set lands as siblings after/before/under `dropNode` in source order.
23
+ - **OS-explorer drag-start sync**: Grabbing a non-highlighted node now replaces the highlight with just that node before the drag begins, matching Windows Explorer / macOS Finder. Previously the prior highlight stayed visually stuck while the drag silently carried only the grabbed node.
24
+ - **Keyboard convention reshuffle**: **Enter** now toggles highlight on the focused node (in `multi` mode); **Space** is the universal expand/collapse toggle (when no checkbox). Ctrl/Shift+click no longer auto-toggles expand/collapse — modifier clicks are reserved for highlight management so multi-selecting folders doesn't open them.
25
+ - **`clickTogglesCheckbox` prop**: Opt-in flag that makes a plain row click toggle the checkbox instead of focusing/highlighting, for checkbox-first UIs. Modified clicks still build the multi-highlight.
26
+ - **Full CSS-variable theming surface**: ~50 new `--ltree-*` variables (typography, layout, toggle/checkbox/selection/drag-zone/context-menu state) on a `--base-*` token chain shared with other `@keenmate/*` web components. Set `--ltree-primary` once and the hover tint, drag-zone background, focus ring, and danger menu hover all derive from it via `color-mix()`. New `--ltree-rem` scales the whole component proportionally. Lucide SVG toggle icons replace the UTF-8 glyphs.
27
+ - **SCSS → pure CSS**: `main.scss` (1095 lines) split into eleven `_*.css` partials bundled by `lightningcss-cli` into `dist/styles.css`. `sass` removed from devDependencies. `./styles.scss` package export removed — switch to `./styles.css`.
28
+
29
+ > **Breaking changes** in this release include the `selectionMode='single'` default (Ctrl/Shift+click no longer auto-multi-selects without opt-in), `selectedPaths` becoming populated in no-checkbox trees (mirrored from highlight), removal of `lastHighlightedPath` / `isHighlightAnchor` / `--ltree-*-rgb` / SCSS variable overrides, and `--ltree-node-indent-per-level` now scaling with `--ltree-rem` instead of document `rem`. See CHANGELOG for migration notes.
21
30
 
22
31
  ## v5.0: Core/Renderer Split + Virtual Scroll
23
32
 
@@ -72,13 +81,13 @@ The component requires CSS to display correctly. Import the styles in your app:
72
81
 
73
82
  **JavaScript import** (in your main.js/main.ts or Vite/Webpack entry):
74
83
  ```javascript
75
- import '@keenmate/svelte-treeview/styles.scss';
84
+ import '@keenmate/svelte-treeview/styles.css';
76
85
  ```
77
86
 
78
87
  **Svelte component import:**
79
88
  ```svelte
80
89
  <style>
81
- @import '@keenmate/svelte-treeview/styles.scss';
90
+ @import '@keenmate/svelte-treeview/styles.css';
82
91
  </style>
83
92
  ```
84
93
 
@@ -500,34 +509,38 @@ The component uses CSS custom properties for easy theming:
500
509
 
501
510
  ```css
502
511
  :root {
503
- --tree-node-indent-per-level: 0.5rem; /* Controls indentation for each hierarchy level */
504
- --ltree-primary: #0d6efd;
505
- --ltree-primary-rgb: 13, 110, 253;
512
+ --ltree-rem: 10px; /* Base sizing unit scale all dimensions */
513
+ --ltree-node-indent-per-level: calc(0.8 * var(--ltree-rem)); /* Indent per nesting level */
514
+ --ltree-primary: #0d6efd; /* Tints (multi-select, dragover) derived
515
+ automatically via color-mix() */
506
516
  --ltree-success: #198754;
507
- --ltree-success-rgb: 25, 135, 84;
508
517
  --ltree-danger: #dc3545;
509
- --ltree-danger-rgb: 220, 53, 69;
510
518
  --ltree-light: #f8f9fa;
511
519
  --ltree-border: #dee2e6;
512
520
  --ltree-body-color: #212529;
513
521
  }
514
522
  ```
515
523
 
516
- **Note**: The `--tree-node-indent-per-level` variable controls the consistent indentation applied at each hierarchy level. Each nested level receives this fixed indent amount, creating proper visual hierarchy without exponential indentation growth.
517
-
518
- ### SCSS Variables (if using SCSS)
524
+ **Note**: All dimensions are `calc(N × var(--ltree-rem))`. Set `--ltree-rem` once (default `10px`) to scale every size proportionally, or set it to `1rem` to make the component follow document font-size.
519
525
 
520
- If you're building the styles from SCSS source, you can override these variables:
526
+ ### Scaling and Theme Integration
521
527
 
522
- ```scss
523
- // Import your overrides before the library styles
524
- $tree-node-indent-per-level: 1rem;
525
- $tree-node-font-family: 'Custom Font', sans-serif;
526
- $primary-color: #custom-color;
528
+ ```css
529
+ /* Scale everything 20% larger */
530
+ .my-bigger-tree {
531
+ --ltree-rem: 12px;
532
+ }
527
533
 
528
- @import '@keenmate/svelte-treeview/styles.scss';
534
+ /* Shared theme tokens — picked up by every @keenmate/* component */
535
+ :root {
536
+ --base-accent-color: #6366f1;
537
+ --base-font-family: 'Inter', system-ui, sans-serif;
538
+ --base-border-radius-sm: 0.4; /* unitless multiplier (× rem) */
539
+ }
529
540
  ```
530
541
 
542
+ See `/examples/theming` for the full CSS variable reference and a live demo.
543
+
531
544
  ### CSS Classes
532
545
 
533
546
  - `.ltree-tree` - Main tree container