@bimetal/tree-svelte-components 0.35.0 → 0.37.0

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.
package/dist/Tree.svelte CHANGED
@@ -2,8 +2,8 @@
2
2
  import { onDestroy, untrack, type Snippet } from 'svelte';
3
3
  import { createTreeControllerBinding } from '@bimetal/svelte';
4
4
  import type { TreeStore } from '@bimetal/tree-data';
5
- import type { TreeConfigInput, TreeController, TreeControllerSnapshot, TreeRowView } from '@bimetal/tree-headless';
6
- import { treeRowIndent, treeCssVars } from '@bimetal/tree-headless';
5
+ import type { TreeConfigInput, TreeController, TreeControllerSnapshot, TreeRowView , TreeControllerOptions, TreeScrollAnchor } from '@bimetal/tree-headless';
6
+ import { treeRowIndent, treeCssVars, reportUnroutedTreeError } from '@bimetal/tree-headless';
7
7
  import TreeDialog from './TreeDialog.svelte';
8
8
  import { isEditableTarget } from '@bimetal/a11y-dom';
9
9
 
@@ -29,6 +29,7 @@
29
29
  onError = undefined,
30
30
  onSelectionChange = undefined,
31
31
  onCheckedChange = undefined,
32
+ loadChildren = undefined,
32
33
  renderNode = undefined,
33
34
  }: {
34
35
  /**
@@ -45,7 +46,11 @@
45
46
  config?: TreeConfigInput;
46
47
  /** Apply the dark token set (`data-theme="dark"`). */
47
48
  darkMode?: boolean;
48
- /** Text/layout direction, applied as `dir` on the root (`ltr`/`rtl`). */
49
+ /** Text/layout direction, applied as `dir` on the root (`ltr`/`rtl`).
50
+ * Seit mcp-93 steuert sie zusaetzlich die TASTATUR: unter `rtl` sind die
51
+ * horizontalen Pfeiltasten getauscht, wie das ARIA-Tree-Pattern es verlangt.
52
+ * Sie speist `config.direction`; ein ausdruecklich gesetztes `config.direction`
53
+ * hat Vorrang. */
49
54
  dir?: 'ltr' | 'rtl';
50
55
  /** Optional toolbar heading. Omitted → no heading is rendered. */
51
56
  title?: string;
@@ -63,19 +68,77 @@
63
68
  /** OBSERVE hook (standalone only): the SECOND independent axis — the checkbox
64
69
  * axis, not a dedup of selection. Fires when the checked set changes. */
65
70
  onCheckedChange?: (checkedIds: readonly string[]) => void;
71
+ loadChildren?: TreeControllerOptions['loadChildren'];
66
72
  /** Replace ONLY a row's content cell (icon/label/badge). */
67
73
  renderNode?: Snippet<[TreeRowView]>;
68
74
  } = $props();
69
75
 
70
76
  // Dual-API: bound path uses the given controller (binding = null → never
71
77
  // created/destroyed/setConfig here); standalone creates one ONCE under `untrack`.
72
- const binding = untrack(() => (controller ? null : createTreeControllerBinding({ store: store!, treeId: treeId!, config, onError: (err) => onError?.(err), onSelectionChange, onCheckedChange })));
78
+ // bimetal-181: der GETTER ist die Aussage er liest die `$props()`-Bindungen bei
79
+ // jedem Callback-Aufruf erneut, statt den Stand des `untrack`-Aufbaus festzunageln.
80
+ // mcp-93: `dir` ist die SICHTBARE Schreibrichtung — unter RTL verlangt das
81
+ // ARIA-Tree-Pattern getauschte horizontale Pfeiltasten, und die Tastatur-Semantik
82
+ // sitzt im Controller. Deshalb speist `dir` `config.direction`; ein ausdruecklich
83
+ // gesetztes `config.direction` hat Vorrang. Gibt bei fehlendem `dir` die
84
+ // Original-Referenz zurueck — ein neues Objekt je Aufruf waere ein unnoetiger
85
+ // `setConfig`-Anlauf. Begruendung im Detail: tree-react.
86
+ const mitRichtung = (c: TreeConfigInput | undefined, d: 'ltr' | 'rtl' | undefined): TreeConfigInput | undefined =>
87
+ d && c?.direction === undefined ? { ...c, direction: d } : c;
88
+
89
+ const binding = untrack(() => (controller ? null : createTreeControllerBinding(() => ({ store: store!, treeId: treeId!, config: mitRichtung(config, dir), onError: (err) => (onError ?? reportUnroutedTreeError)(err), onSelectionChange, onCheckedChange, loadChildren }))));
73
90
  const ctrl = controller ?? binding!.ctrl;
91
+ // bimetal-225: der Mount wird gemeldet, sonst raeumt das Binding sich per Microtask
92
+ // selbst ab (der Controller entsteht im untrack-Aufbau und haelt ab da ein Store-Abo,
93
+ // und bei einer nie gemounteten Instanz laeuft KEIN Aufraeum-Haken). Ein $effect
94
+ // laeuft im Mount-Flush, also vor dem Microtask — gemessen.
95
+ binding?.erwarteMount(); // bimetal-225: Selbstabraeumung armieren (Opt-in)
96
+ $effect(() => { binding?.markMounted(); });
74
97
  // Snapshot store — sourced from `ctrl` so it works for both API halves (both are
75
98
  // a `SnapshotSource`); the bound path never touches the owned controller's lifecycle.
76
99
  const snapshotStore = { subscribe: (run: (s: TreeControllerSnapshot) => void) => { run(ctrl.getSnapshot()); return ctrl.subscribe(() => run(ctrl.getSnapshot())); } };
77
100
 
78
101
  let listEl: HTMLDivElement | null = $state(null);
102
+
103
+ // Scroll-Anker (bimetal-138): EINMAL je Anker-Objekt den absoluten
104
+ // Ziel-scrollTop setzen — `$effect` laeuft nach dem DOM-Update.
105
+ let angewandterAnker: TreeScrollAnchor | null = null;
106
+ $effect(() => {
107
+ const a = $snapshotStore.virtual?.anchor ?? null;
108
+ const el = listEl;
109
+ if (!a || !el || angewandterAnker === a) return;
110
+ angewandterAnker = a;
111
+ if (el.scrollTop !== a.scrollTop) el.scrollTop = a.scrollTop;
112
+ });
113
+
114
+ // Virtualization: REPORT the viewport; the controller owns the window math
115
+ // (mcp-60, list parity).
116
+ $effect(() => {
117
+ const el = listEl;
118
+ if (!el) return;
119
+ const report = () => ctrl.setViewport(el.scrollTop, el.clientHeight);
120
+ report();
121
+ el.addEventListener('scroll', report, { passive: true });
122
+ // M1 (mcp-60 review): a windowed-out FOCUSED row drops DOM focus to
123
+ // <body> mid-interaction; relatedTarget-null focusout is ambiguous at
124
+ // dispatch time — a microtask later the unmounted row is disconnected.
125
+ // Fall back onto the container (tabindex -1, the keydown host).
126
+ let focusInside = false;
127
+ const onFocusIn = () => { focusInside = true; };
128
+ const onFocusOut = (e: FocusEvent) => {
129
+ if (e.relatedTarget instanceof Node) { focusInside = el.contains(e.relatedTarget); return; }
130
+ const target = e.target as HTMLElement;
131
+ queueMicrotask(() => {
132
+ if (target.isConnected) { focusInside = false; return; }
133
+ if (focusInside && document.activeElement === document.body) el.focus({ preventScroll: true });
134
+ });
135
+ };
136
+ el.addEventListener('focusin', onFocusIn);
137
+ el.addEventListener('focusout', onFocusOut);
138
+ const ro = new ResizeObserver(report);
139
+ ro.observe(el);
140
+ return () => { el.removeEventListener('scroll', report); el.removeEventListener('focusin', onFocusIn); el.removeEventListener('focusout', onFocusOut); ro.disconnect(); };
141
+ });
79
142
  let suppressClick = false;
80
143
 
81
144
  const onKey = (e: KeyboardEvent) =>
@@ -138,10 +201,12 @@
138
201
  {#if $snapshotStore}
139
202
  {@const snap = $snapshotStore}
140
203
  {@const domFocus = snap.navAnchor}
141
- <div class="bm-tree" data-theme={darkMode ? 'dark' : undefined} dir={dir} style={cssVars(snap)}>
204
+ <div class={'bm-tree' + (snap.virtual ? ' bm-tree--virtual' : '')} data-theme={darkMode ? 'dark' : undefined} dir={dir} style={cssVars(snap)}>
142
205
  <div class="bm-tree__toolbar">
143
206
  {#if title}<h1 class="bm-tree__title">{title}</h1>{/if}
144
207
  {#if tag}<span class="bm-tree__tag">{tag}</span>{/if}
208
+ <!-- mcp-64: Mutations-Affordanzen fallen im View-Only-Modus (kein toter Knopf). -->
209
+ {#if !snap.config.readOnly}
145
210
  <button class="bm-tree__btn bm-tree__btn--primary" type="button" disabled={snap.focusId === null} onclick={() => ctrl.voidAddChild(snap.focusId)}>+ {snap.config.locale.addChild}</button>
146
211
  <button class="bm-tree__btn" type="button" disabled={snap.focusId === null} onclick={() => snap.focusId && ctrl.voidAddSibling(snap.focusId)}>+ {snap.config.locale.addSibling}</button>
147
212
  <button class="bm-tree__btn" type="button" disabled={snap.focusId === null} onclick={(e) => snap.focusId && ctrl.openEdit(snap.focusId, rect(e))}>{snap.config.locale.rename}</button>
@@ -151,15 +216,17 @@
151
216
  <button class="bm-tree__btn bm-tree__btn--undo" type="button" aria-label={snap.config.locale.undo} title={snap.config.locale.undo} disabled={!snap.canUndo} onclick={() => ctrl.voidUndo()}>↶</button>
152
217
  <button class="bm-tree__btn bm-tree__btn--redo" type="button" aria-label={snap.config.locale.redo} title={snap.config.locale.redo} disabled={!snap.canRedo} onclick={() => ctrl.voidRedo()}>↷</button>
153
218
  {/if}
219
+ {/if}
154
220
  <button class="bm-tree__btn" type="button" onclick={() => ctrl.expandAll()}>{snap.config.locale.expandAll}</button>
155
221
  <button class="bm-tree__btn" type="button" onclick={() => ctrl.collapseAll()}>{snap.config.locale.collapseAll}</button>
156
- <input class="bm-tree__search" type="search" placeholder={snap.config.locale.search} value={snap.filterQuery}
222
+ <input class="bm-tree__search" type="search" placeholder={(snap.config.locale.searchPlaceholder ?? snap.config.locale.search)} value={snap.filterQuery}
157
223
  oninput={(e) => ctrl.setFilter((e.target as HTMLInputElement).value)} />
158
224
  </div>
159
225
 
160
226
  <!-- svelte-ignore a11y_interactive_supports_focus -->
161
- <div class="bm-tree__list" role="tree" aria-label={ariaLabel ?? title ?? snap.config.locale.label} bind:this={listEl} onkeydown={onKey}>
162
- {#each snap.rows as r (r.node.id)}
227
+ <div class="bm-tree__list" tabindex="-1" role="tree" aria-label={ariaLabel ?? title ?? snap.config.locale.label} bind:this={listEl} onkeydown={onKey}>
228
+ {#if snap.virtual && snap.virtual.offsetTop > 0}<div class="bm-tree__spacer" aria-hidden="true" style="height: {snap.virtual.offsetTop}px"></div>{/if}
229
+ {#each (snap.virtual ? snap.virtual.rows : snap.rows) as r (r.node.id)}
163
230
  <!-- svelte-ignore a11y_click_events_have_key_events -->
164
231
  <div class={rowClass(snap, r)}
165
232
  data-node-id={r.node.id}
@@ -168,13 +235,14 @@
168
235
  aria-posinset={r.posinset}
169
236
  aria-setsize={r.setsize}
170
237
  aria-selected={r.selected}
238
+ aria-busy={r.loading || undefined}
171
239
  aria-expanded={r.hasChildren ? r.expanded : undefined}
172
240
  aria-disabled={r.meta.disabled || undefined}
173
241
  tabindex={r.node.id === domFocus ? 0 : -1}
174
- style="padding-left:{treeRowIndent(r.depth, snap) + 8}px"
242
+ style="padding-inline-start:{treeRowIndent(r.depth, snap) + 8}px"
175
243
  onpointerdown={(e) => onRowPointerDown(e, r.node.id)}
176
244
  onclick={(e) => onRowClick(e, r.node.id)}>
177
- <span class={'bm-tree__chevron' + (r.hasChildren ? (r.expanded ? ' bm-tree__chevron--expanded' : '') : ' bm-tree__chevron--leaf')}>▶</span>
245
+ <span class={'bm-tree__chevron' + (r.hasChildren ? (r.expanded ? ' bm-tree__chevron--expanded' : '') : ' bm-tree__chevron--leaf') + (r.loading ? ' bm-tree__chevron--loading' : '')}>▶</span>
178
246
  {#if snap.config.checkboxes}
179
247
  <span class={checkboxClass(r.checkState)} role="checkbox" aria-checked={r.checkState === 'indeterminate' ? 'mixed' : r.checkState === 'checked'} aria-label={snap.config.locale.check}></span>
180
248
  {/if}
@@ -185,6 +253,7 @@
185
253
  {/if}
186
254
  </div>
187
255
  {/each}
256
+ {#if snap.virtual && snap.virtual.offsetBottom > 0}<div class="bm-tree__spacer" aria-hidden="true" style="height: {snap.virtual.offsetBottom}px"></div>{/if}
188
257
  </div>
189
258
 
190
259
  {#if snap.dialog}
@@ -195,7 +264,7 @@
195
264
  {/if}
196
265
 
197
266
  {#if snap.lastAction}
198
- <div class="bm-tree__toast" role="status">
267
+ <div class="bm-tree__toast{snap.lastAction.kind === 'error' ? ' bm-tree__toast--error' : ''}" role={snap.lastAction.kind === 'error' ? 'alert' : 'status'}>
199
268
  <span class="bm-tree__toast-msg">{snap.lastAction.description}</span>
200
269
  <button class="bm-tree__toast-dismiss" type="button" onclick={() => ctrl.dismissUndo()}>×</button>
201
270
  </div>
@@ -1,6 +1,6 @@
1
1
  import { type Snippet } from 'svelte';
2
2
  import type { TreeStore } from '@bimetal/tree-data';
3
- import type { TreeConfigInput, TreeController, TreeRowView } from '@bimetal/tree-headless';
3
+ import type { TreeConfigInput, TreeController, TreeRowView, TreeControllerOptions } from '@bimetal/tree-headless';
4
4
  type $$ComponentProps = {
5
5
  /**
6
6
  * BOUND (composition contract, F1): an already-owned controller. The component
@@ -16,7 +16,11 @@ type $$ComponentProps = {
16
16
  config?: TreeConfigInput;
17
17
  /** Apply the dark token set (`data-theme="dark"`). */
18
18
  darkMode?: boolean;
19
- /** Text/layout direction, applied as `dir` on the root (`ltr`/`rtl`). */
19
+ /** Text/layout direction, applied as `dir` on the root (`ltr`/`rtl`).
20
+ * Seit mcp-93 steuert sie zusaetzlich die TASTATUR: unter `rtl` sind die
21
+ * horizontalen Pfeiltasten getauscht, wie das ARIA-Tree-Pattern es verlangt.
22
+ * Sie speist `config.direction`; ein ausdruecklich gesetztes `config.direction`
23
+ * hat Vorrang. */
20
24
  dir?: 'ltr' | 'rtl';
21
25
  /** Optional toolbar heading. Omitted → no heading is rendered. */
22
26
  title?: string;
@@ -34,6 +38,7 @@ type $$ComponentProps = {
34
38
  /** OBSERVE hook (standalone only): the SECOND independent axis — the checkbox
35
39
  * axis, not a dedup of selection. Fires when the checked set changes. */
36
40
  onCheckedChange?: (checkedIds: readonly string[]) => void;
41
+ loadChildren?: TreeControllerOptions['loadChildren'];
37
42
  /** Replace ONLY a row's content cell (icon/label/badge). */
38
43
  renderNode?: Snippet<[TreeRowView]>;
39
44
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Tree.svelte.d.ts","sourceRoot":"","sources":["../src/Tree.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAA0B,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAOlH,KAAK,gBAAgB,GAAI;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gGAAgG;IAChG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;mFAC+E;IAC/E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iFAAiF;IACjF,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7D;8EAC0E;IAC1E,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAC1D,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;CACrC,CAAC;AA8JJ,QAAA,MAAM,IAAI,sDAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"Tree.svelte.d.ts","sourceRoot":"","sources":["../src/Tree.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAA0B,WAAW,EAAG,qBAAqB,EAAoB,MAAM,wBAAwB,CAAC;AAO5J,KAAK,gBAAgB,GAAI;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;uBAImB;IACnB,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gGAAgG;IAChG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;mFAC+E;IAC/E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iFAAiF;IACjF,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7D;8EAC0E;IAC1E,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAC1D,YAAY,CAAC,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAC;IACrD,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;CACrC,CAAC;AA6NJ,QAAA,MAAM,IAAI,sDAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
@@ -0,0 +1,3 @@
1
+ // Generated by scripts/generate-css-stubs.mjs — DO NOT EDIT.
2
+ // Type-stub for side-effect CSS import (TS moduleResolution: nodenext/bundler).
3
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bimetal/tree-svelte-components",
3
- "version": "0.35.0",
3
+ "version": "0.37.0",
4
4
  "description": "Default Svelte 5 tree / treeview — multi-select, expand/collapse, tri-state checkboxes, drag-reorder, rename, undo, theming. A thin binding over the canonical tree controller.",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -12,7 +12,10 @@
12
12
  "svelte": "./dist/index.js",
13
13
  "import": "./dist/index.js"
14
14
  },
15
- "./styles": "./dist/styles/index.css"
15
+ "./styles": {
16
+ "types": "./dist/styles/index.css.d.ts",
17
+ "default": "./dist/styles/index.css"
18
+ }
16
19
  },
17
20
  "files": [
18
21
  "dist",
@@ -20,31 +23,33 @@
20
23
  "LICENSE"
21
24
  ],
22
25
  "scripts": {
23
- "build": "svelte-package -i src -o dist && node scripts/copy-css.mjs",
26
+ "build": "svelte-package -i src -o dist && node scripts/copy-css.mjs && node ../../../scripts/generate-css-stubs.mjs",
24
27
  "dev": "svelte-package -i src -o dist --watch",
25
- "typecheck": "svelte-check --tsconfig ./tsconfig.json",
28
+ "typecheck": "svelte-check --tsconfig ./tsconfig.test.json",
26
29
  "test": "vitest run",
27
30
  "prepublishOnly": "npm run build"
28
31
  },
29
32
  "dependencies": {
30
- "@bimetal/svelte": "^0.35.0",
31
- "@bimetal/tree-data": "^0.35.0",
32
- "@bimetal/tree-headless": "^0.35.0",
33
- "@bimetal/tree-themes": "^0.35.0",
34
- "@bimetal/a11y-dom": "^0.35.0"
33
+ "@bimetal/svelte": "^0.37.0",
34
+ "@bimetal/tree-data": "^0.37.0",
35
+ "@bimetal/tree-headless": "^0.37.0",
36
+ "@bimetal/tree-themes": "^0.37.0",
37
+ "@bimetal/a11y-dom": "^0.37.0"
35
38
  },
36
39
  "peerDependencies": {
37
40
  "svelte": "^5.46.4"
38
41
  },
39
42
  "devDependencies": {
40
43
  "@sveltejs/package": "^2.5.0",
41
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
44
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
42
45
  "@testing-library/svelte": "^5.2.7",
43
46
  "happy-dom": "^18.0.1",
44
47
  "svelte": "^5.46.4",
45
48
  "svelte-check": "^4.0.0",
46
49
  "typescript": "~5.9.3",
47
- "vitest": "^3.2.4"
50
+ "@types/node": "^25.8.0",
51
+ "vitest": "^5.0.0",
52
+ "vite": "^8.0.1"
48
53
  },
49
54
  "sideEffects": [
50
55
  "**/*.css"