diff --git a/apps/docs/app/(trees)/_docs/DocsPage.tsx b/apps/docs/app/(trees)/_docs/DocsPage.tsx
index 5f9ecf94e..a1673e619 100644
--- a/apps/docs/app/(trees)/_docs/DocsPage.tsx
+++ b/apps/docs/app/(trees)/_docs/DocsPage.tsx
@@ -8,6 +8,7 @@ import * as chooseYourIntegrationConstants from '../docs/Guides/ChooseYourIntegr
import * as customizeIconsConstants from '../docs/Guides/CustomizeIcons/constants';
import * as getStartedWithReactConstants from '../docs/Guides/GetStartedWithReact/constants';
import * as getStartedWithVanillaConstants from '../docs/Guides/GetStartedWithVanilla/constants';
+import * as getStartedWithVueConstants from '../docs/Guides/GetStartedWithVue/constants';
import * as handleLargeTreesEfficientlyConstants from '../docs/Guides/HandleLargeTreesEfficiently/constants';
import * as navigateSelectionFocusAndSearchConstants from '../docs/Guides/NavigateSelectionFocusAndSearch/constants';
import * as renameDragAndTriggerItemActionsConstants from '../docs/Guides/RenameDragAndTriggerItemActions/constants';
@@ -24,6 +25,7 @@ import * as reactApiConstants from '../docs/Reference/ReactAPI/constants';
import * as ssrApiConstants from '../docs/Reference/SSRAPI/constants';
import * as stylingAndThemingConstants from '../docs/Reference/StylingAndTheming/constants';
import * as vanillaApiConstants from '../docs/Reference/VanillaAPI/constants';
+import * as vueApiConstants from '../docs/Reference/VueAPI/constants';
import { DocsLayout } from '@/components/docs/DocsLayout';
import { HeadingAnchors } from '@/components/docs/HeadingAnchors';
import { ProseWrapper } from '@/components/docs/ProseWrapper';
@@ -45,6 +47,10 @@ const GUIDE_SECTIONS: readonly DocsSection[] = [
filePath: '(trees)/docs/Guides/GetStartedWithReact/content.mdx',
constants: getStartedWithReactConstants,
},
+ {
+ filePath: '(trees)/docs/Guides/GetStartedWithVue/content.mdx',
+ constants: getStartedWithVueConstants,
+ },
{
filePath: '(trees)/docs/Guides/GetStartedWithVanilla/content.mdx',
constants: getStartedWithVanillaConstants,
@@ -89,6 +95,10 @@ const REFERENCE_SECTIONS: readonly DocsSection[] = [
filePath: '(trees)/docs/Reference/ReactAPI/content.mdx',
constants: reactApiConstants,
},
+ {
+ filePath: '(trees)/docs/Reference/VueAPI/content.mdx',
+ constants: vueApiConstants,
+ },
{
filePath: '(trees)/docs/Reference/VanillaAPI/content.mdx',
constants: vanillaApiConstants,
@@ -106,7 +116,7 @@ const REFERENCE_SECTIONS: readonly DocsSection[] = [
const treesDocsTitle = 'Trees, from Pierre';
const treesDocsDescription =
- 'Guide-first documentation for @pierre/trees, covering React, vanilla, prepared input, styling, icons, Git status, large trees, and SSR hydration.';
+ 'Guide-first documentation for @pierre/trees, covering React, Vue, vanilla, prepared input, styling, icons, Git status, large trees, and SSR hydration.';
// Next.js replaces (does not deep-merge) nested metadata objects like
// `openGraph` and `twitter` from parent segments. Re-declare `images` here
diff --git a/apps/docs/app/(trees)/docs/Guides/ChooseYourIntegration/constants.ts b/apps/docs/app/(trees)/docs/Guides/ChooseYourIntegration/constants.ts
index f841bccba..9264b5b6d 100644
--- a/apps/docs/app/(trees)/docs/Guides/ChooseYourIntegration/constants.ts
+++ b/apps/docs/app/(trees)/docs/Guides/ChooseYourIntegration/constants.ts
@@ -11,6 +11,20 @@ export function ProjectTree({ paths }: { paths: readonly string[] }) {
}`
);
+export const CHOOSE_INTEGRATION_VUE_EXAMPLE = docsCodeSnippet(
+ 'project-tree.vue',
+ `
+
+
+
+`
+);
+
export const CHOOSE_INTEGRATION_VANILLA_EXAMPLE = docsCodeSnippet(
'mount-tree.ts',
`import { FileTree } from '@pierre/trees';
diff --git a/apps/docs/app/(trees)/docs/Guides/ChooseYourIntegration/content.mdx b/apps/docs/app/(trees)/docs/Guides/ChooseYourIntegration/content.mdx
index 75ae1665f..a7b72ce6f 100644
--- a/apps/docs/app/(trees)/docs/Guides/ChooseYourIntegration/content.mdx
+++ b/apps/docs/app/(trees)/docs/Guides/ChooseYourIntegration/content.mdx
@@ -1,6 +1,7 @@
-`@pierre/trees` exposes one path-first model and two primary runtime entries: a
-thin React layer in [`@pierre/trees/react`](#get-started-with-react) and the
-vanilla class in [`@pierre/trees`](#get-started-with-vanilla).
+`@pierre/trees` exposes one path-first model and three primary runtime entries:
+a thin React layer in [`@pierre/trees/react`](#get-started-with-react), a Vue 3
+layer in [`@pierre/trees/vue`](#get-started-with-vue), and the vanilla class in
+[`@pierre/trees`](#get-started-with-vanilla).
### Choose your integration
@@ -14,7 +15,7 @@ and the target you rename or move later.
For the shared vocabulary behind that rule, jump to
[Shared concepts](#shared-concepts).
-#### 2. React vs Vanilla JS
+#### 2. React, Vue, or Vanilla JS
Pick the React entry point when the surrounding UI already lives in React. Jump
to the [getting started with React](#get-started-with-react) guide for more
@@ -22,7 +23,12 @@ info.
-Pick the vanilla class when your app is not React-based, or when another
+Pick the Vue entry point when the surrounding UI already lives in Vue 3. Jump to
+the [getting started with Vue](#get-started-with-vue) guide for more.
+
+
+
+Pick the vanilla class when your app is not React- or Vue-based, or when another
framework will own lifecycle around an imperative model. Jump to the
[getting started with vanilla JS](#get-started-with-vanilla) for more.
@@ -30,9 +36,8 @@ framework will own lifecycle around an imperative model. Jump to the
#### 3. Understand tree-shape
-Both runtimes consume the same tree data. Small examples can start with raw
+All runtimes consume the same tree data. Small examples can start with raw
`paths`, but real application trees should move to prepared input before the
-client pays that shaping work on every load. Regardless of your runtime being
-React or vanilla JS, be sure to read
+client pays that shaping work on every load. Regardless of your runtime, read
[Shape tree data for fast rendering](#shape-tree-data-for-fast-rendering) after
your runtime quickstart.
diff --git a/apps/docs/app/(trees)/docs/Guides/GetStartedWithVanilla/content.mdx b/apps/docs/app/(trees)/docs/Guides/GetStartedWithVanilla/content.mdx
index b5cb1a59f..f8f269df7 100644
--- a/apps/docs/app/(trees)/docs/Guides/GetStartedWithVanilla/content.mdx
+++ b/apps/docs/app/(trees)/docs/Guides/GetStartedWithVanilla/content.mdx
@@ -1,7 +1,7 @@
### Get started with vanilla
-Use the vanilla runtime when your app is not React-based, or when another
-framework will own lifecycle around an imperative tree model.
+Use the vanilla runtime when your app is not React- or Vue-based, or when
+another framework will own lifecycle around an imperative tree model.
`new FileTree(...)` creates the model, and `render(...)` or `hydrate(...)`
attaches it to the DOM.
@@ -72,5 +72,5 @@ the same:
- mount or unmount around the instance
- keep all tree reads and writes on the model itself
-That keeps React as the only first-class wrapper surface without changing how
-the underlying model works.
+That keeps framework wrappers thin without changing how the underlying model
+works.
diff --git a/apps/docs/app/(trees)/docs/Guides/GetStartedWithVue/constants.ts b/apps/docs/app/(trees)/docs/Guides/GetStartedWithVue/constants.ts
new file mode 100644
index 000000000..4293b7f47
--- /dev/null
+++ b/apps/docs/app/(trees)/docs/Guides/GetStartedWithVue/constants.ts
@@ -0,0 +1,75 @@
+import { docsCodeSnippet } from '@/lib/docsCodeSnippet';
+
+export const VUE_QUICKSTART_INSTALL = docsCodeSnippet(
+ 'install.sh',
+ `bun add @pierre/trees
+# npm: npm install @pierre/trees
+# pnpm: pnpm add @pierre/trees`
+);
+
+export const VUE_QUICKSTART_PROJECT_TREE = docsCodeSnippet(
+ 'project-tree.vue',
+ `
+
+
+
+`
+);
+
+export const VUE_QUICKSTART_SEARCHABLE_TREE = docsCodeSnippet(
+ 'searchable-tree.vue',
+ `
+
+
+
+
+
{{ selectedPaths.length }} item(s) selected.
+
+
+`
+);
diff --git a/apps/docs/app/(trees)/docs/Guides/GetStartedWithVue/content.mdx b/apps/docs/app/(trees)/docs/Guides/GetStartedWithVue/content.mdx
new file mode 100644
index 000000000..105e480c2
--- /dev/null
+++ b/apps/docs/app/(trees)/docs/Guides/GetStartedWithVue/content.mdx
@@ -0,0 +1,77 @@
+### Get started with Vue
+
+Use the Vue entry point when your surrounding UI already lives in Vue 3. The
+composable creates one stable tree model, and the component mounts that model
+into the host element.
+
+#### Install `@pierre/trees`
+
+Use the package root for the vanilla runtime, and the `/vue` entry point for the
+Vue wrapper.
+
+
+
+#### Create the model with `useFileTree(...)`
+
+`useFileTree(...)` from `@pierre/trees/vue` creates the model exactly once for
+the component lifetime. Later option changes are not a controlled update path.
+If the tree data or runtime behavior changes after mount, update the model
+through methods such as `resetPaths(...)`, `setComposition(...)`,
+`setGitStatus(...)`, or `setIcons(...)`.
+
+For small trees, you can pass raw `paths`. For scalable trees, prefer
+`preparedInput` produced on the server or another non-UI boundary.
+
+
+
+If you are still deciding how to shape that input, read
+[Shape tree data for fast rendering](#shape-tree-data-for-fast-rendering) after
+this quickstart.
+
+#### Render with ``
+
+`` is a thin Vue wrapper over the model. It mounts the tree into the
+host element, forwards normal host attributes such as `class` and `style`, and
+hydrates existing server output when you pass `preloadedData` later.
+
+Keep the mental model simple: the model owns the tree state, and the Vue
+component renders it.
+
+#### Read and update tree state through the model
+
+Vue code reads snapshots from the model through composables, and writes back
+through model methods.
+
+
+
+Use `useFileTreeSelector(model, selector, equality?)` when sibling UI needs a
+custom derived ref without updating on unrelated tree changes. For the shared
+interaction vocabulary, read
+[Navigate selection, focus, and search](#navigate-selection-focus-and-search)
+and [Vue API](#vue-api).
+
+#### Use simple `paths` input only when the tree is small
+
+Raw `paths` is the low-ceremony path for demos, tests, and very small static
+trees. It is not the scalable default. Once the tree becomes expensive to shape
+or sort on the client, move that work out of the UI.
+
+#### Move to prepared input before the tree gets expensive
+
+The recommended scale path is:
+
+1. Load canonical paths on the server or another non-UI boundary.
+2. Prepare the tree input once.
+3. Pass `preparedInput` into `useFileTree(...)`.
+
+If the server already knows the final order,
+`preparePresortedFileTreeInput(...)` is the highest-performance prepared-input
+variant because the client can skip both shaping and extra sorting work.
+
+#### Add SSR later when first paint matters
+
+Hydration layers on top of the same model-first Vue story. The client still
+calls `useFileTree(...)`, and the Vue wrapper still renders the same model. The
+only difference is that the tree starts from preloaded server output.
+
+When you need that flow, continue with [SSR](#ssr) and [SSR API](#ssr-api).
diff --git a/apps/docs/app/(trees)/docs/Guides/HandleLargeTreesEfficiently/content.mdx b/apps/docs/app/(trees)/docs/Guides/HandleLargeTreesEfficiently/content.mdx
index 33a7da86c..0850f896f 100644
--- a/apps/docs/app/(trees)/docs/Guides/HandleLargeTreesEfficiently/content.mdx
+++ b/apps/docs/app/(trees)/docs/Guides/HandleLargeTreesEfficiently/content.mdx
@@ -24,7 +24,7 @@ scale-oriented public path.
If you only have an unsorted path list, use `prepareFileTreeInput(...)` instead.
-Either way, pass the prepared result into React, vanilla, or SSR hydration.
+Either way, pass the prepared result into React, Vue, vanilla, or SSR hydration.
#### Keep rendering work small
diff --git a/apps/docs/app/(trees)/docs/Guides/NavigateSelectionFocusAndSearch/content.mdx b/apps/docs/app/(trees)/docs/Guides/NavigateSelectionFocusAndSearch/content.mdx
index 227a3a321..2457ace65 100644
--- a/apps/docs/app/(trees)/docs/Guides/NavigateSelectionFocusAndSearch/content.mdx
+++ b/apps/docs/app/(trees)/docs/Guides/NavigateSelectionFocusAndSearch/content.mdx
@@ -69,8 +69,8 @@ The DOM host is not the source of truth. The model is.
#### What to read next
-- For runtime-specific lookup, jump to [React API](#react-api) or
- [Vanilla API](#vanilla-api).
+- For runtime-specific lookup, jump to [React API](#react-api),
+ [Vue API](#vue-api), or [Vanilla API](#vanilla-api).
- For shared search-mode semantics, read
[Shared concepts](#shared-concepts-search-mode-semantics).
- For row-level editing workflows that build on this same focus model, continue
diff --git a/apps/docs/app/(trees)/docs/Guides/RenameDragAndTriggerItemActions/content.mdx b/apps/docs/app/(trees)/docs/Guides/RenameDragAndTriggerItemActions/content.mdx
index 222d8df24..05bf99212 100644
--- a/apps/docs/app/(trees)/docs/Guides/RenameDragAndTriggerItemActions/content.mdx
+++ b/apps/docs/app/(trees)/docs/Guides/RenameDragAndTriggerItemActions/content.mdx
@@ -83,4 +83,5 @@ separation clear.
If you need the shared vocabulary behind those events, read
[Shared concepts](#shared-concepts-mutation-vocabulary). For runtime lookup,
-jump to [React API](#react-api) or [Vanilla API](#vanilla-api).
+jump to [React API](#react-api), [Vue API](#vue-api), or
+[Vanilla API](#vanilla-api).
diff --git a/apps/docs/app/(trees)/docs/Guides/SSR/constants.ts b/apps/docs/app/(trees)/docs/Guides/SSR/constants.ts
index 58957ca8a..7f8f91928 100644
--- a/apps/docs/app/(trees)/docs/Guides/SSR/constants.ts
+++ b/apps/docs/app/(trees)/docs/Guides/SSR/constants.ts
@@ -38,6 +38,32 @@ export function ProjectTreeClient({
}`
);
+export const SSR_GUIDE_VUE_HYDRATION = docsCodeSnippet(
+ 'project-tree-client.vue',
+ `
+
+
+
+`
+);
+
export const SSR_GUIDE_VANILLA_HYDRATION = docsCodeSnippet(
'vanilla-hydrate.ts',
`import { FileTree } from '@pierre/trees';
diff --git a/apps/docs/app/(trees)/docs/Guides/SSR/content.mdx b/apps/docs/app/(trees)/docs/Guides/SSR/content.mdx
index 3c4faa5a9..ceaccf6b6 100644
--- a/apps/docs/app/(trees)/docs/Guides/SSR/content.mdx
+++ b/apps/docs/app/(trees)/docs/Guides/SSR/content.mdx
@@ -2,7 +2,8 @@
Use SSR when you want the tree to arrive from the server with a fast first
paint, then become interactive on the client. This is not a third primary
-runtime. It is a preload-and-hydrate layer over the same React or vanilla model.
+runtime. It is a preload-and-hydrate layer over the same React, Vue, or vanilla
+model.
#### Start with the server step
@@ -36,6 +37,15 @@ the wrapper receives the opaque handoff object as `preloadedData`.
The model stays primary. `preloadedData` only activates hydration.
+#### Vue flow
+
+Vue still creates the model with `useFileTree(...)`. The difference is that the
+component receives the opaque handoff object as `preloadedData`.
+
+
+
+The model stays primary. `preloadedData` only activates hydration.
+
#### Vanilla flow
Vanilla uses the same preload step, but the client hydrates the server-rendered
@@ -49,9 +59,9 @@ hydrating.
#### Keep the payload opaque
In docs and application code, refer to the preload result as an SSR payload or
-handoff object. React consumes it as `preloadedData`. Vanilla hydrates existing
-server markup. Both flows reuse the same server work without teaching payload
-internals as the main story.
+handoff object. React and Vue consume it as `preloadedData`. Vanilla hydrates
+existing server markup. Every flow reuses the same server work without teaching
+payload internals as the main story.
#### Pair SSR with prepared input for large trees
@@ -61,8 +71,8 @@ client hydrate that same result.
#### Advanced note: declarative shadow DOM
-The preloaded path uses declarative shadow DOM under the hood. In React, the
-packaged wrapper already handles the host ownership details it needs for
+The preloaded path uses declarative shadow DOM under the hood. In React and Vue,
+the packaged wrappers already handle the host ownership details they need for
hydration. Use the runtime behavior instead of inventing custom DOM diffing or
raw payload plumbing.
diff --git a/apps/docs/app/(trees)/docs/Guides/ShapeTreeDataForFastRendering/constants.ts b/apps/docs/app/(trees)/docs/Guides/ShapeTreeDataForFastRendering/constants.ts
index 795913767..dd6791dea 100644
--- a/apps/docs/app/(trees)/docs/Guides/ShapeTreeDataForFastRendering/constants.ts
+++ b/apps/docs/app/(trees)/docs/Guides/ShapeTreeDataForFastRendering/constants.ts
@@ -28,6 +28,24 @@ export function ReactTree({
}`
);
+export const SHAPE_TREE_DATA_VUE_TREE = docsCodeSnippet(
+ 'vue-tree.vue',
+ `
+
+
+
+`
+);
+
export const SHAPE_TREE_DATA_VANILLA_MOUNT = docsCodeSnippet(
'mount-vanilla-tree.ts',
`import { FileTree, type FileTreePreparedInput } from '@pierre/trees';
diff --git a/apps/docs/app/(trees)/docs/Guides/ShapeTreeDataForFastRendering/content.mdx b/apps/docs/app/(trees)/docs/Guides/ShapeTreeDataForFastRendering/content.mdx
index e97802963..f93f536bb 100644
--- a/apps/docs/app/(trees)/docs/Guides/ShapeTreeDataForFastRendering/content.mdx
+++ b/apps/docs/app/(trees)/docs/Guides/ShapeTreeDataForFastRendering/content.mdx
@@ -17,10 +17,12 @@ preparation step moves earlier.
#### Pass `preparedInput` into the runtime
-Both primary runtimes consume the same prepared payload shape.
+All primary runtimes consume the same prepared payload shape.
+
+
#### Use simple `paths` input only for small trees
@@ -51,7 +53,7 @@ The recommended split is simple:
1. Load canonical paths outside the UI.
2. Prepare the input once.
-3. Pass `preparedInput` into React, vanilla, or SSR hydration.
+3. Pass `preparedInput` into React, Vue, vanilla, or SSR hydration.
That reduces client CPU work, makes startup cost more predictable, and lets the
same prepared payload feed every runtime.
diff --git a/apps/docs/app/(trees)/docs/Guides/StyleAndThemeTheTree/content.mdx b/apps/docs/app/(trees)/docs/Guides/StyleAndThemeTheTree/content.mdx
index f324eac88..f95a6b57d 100644
--- a/apps/docs/app/(trees)/docs/Guides/StyleAndThemeTheTree/content.mdx
+++ b/apps/docs/app/(trees)/docs/Guides/StyleAndThemeTheTree/content.mdx
@@ -52,7 +52,7 @@ Pass `density` to `useFileTree` (or `preloadFileTree`, or the vanilla `FileTree`
constructor) to bundle row height and spacing into one option. The keyword form
(`'compact'`, `'default'`, `'relaxed'`) resolves both at once; a numeric form
keeps the default row height and supplies a custom spacing factor. Every runtime
-— vanilla CSR, vanilla SSR, React CSR, and React SSR — paints
+— vanilla CSR, vanilla SSR, React CSR, React SSR, Vue CSR, and Vue SSR — paints
`--trees-item-height` and `--trees-density-override` onto the host from the
resolved density so the virtualized and painted row heights stay aligned.
Caller-set inline values on the host still win, so you can override either
diff --git a/apps/docs/app/(trees)/docs/Overview/content.mdx b/apps/docs/app/(trees)/docs/Overview/content.mdx
index fb05889fa..06e79cf45 100644
--- a/apps/docs/app/(trees)/docs/Overview/content.mdx
+++ b/apps/docs/app/(trees)/docs/Overview/content.mdx
@@ -1,13 +1,13 @@
## Overview
}>
- **Trees is in beta.** Start from the public React, vanilla, and SSR entry
+ **Trees is in beta.** Start from the public React, Vue, vanilla, and SSR entry
points on this page. Expect polish and small API shifts between beta releases.
-**Trees** keeps one path-first model across React, vanilla, and SSR hydration.
-Selection, focus, search, rename, drag and drop, Git status, and row annotations
-all work in terms of canonical paths.
+**Trees** keeps one path-first model across React, Vue, vanilla, and SSR
+hydration. Selection, focus, search, rename, drag and drop, Git status, and row
+annotations all work in terms of canonical paths.
`.
For the full runtime surface, read [React API](#react-api). For the workflow
guide, read [SSR](#ssr).
+#### Vue handoff
+
+Vue still creates the model with `useFileTree(...)`, then passes the payload to
+``.
+
+For the full runtime surface, read [Vue API](#vue-api). For the workflow guide,
+read [SSR](#ssr).
+
#### Vanilla handoff
Vanilla emits the server-rendered tree into the page first, creates
diff --git a/apps/docs/app/(trees)/docs/Reference/SharedConcepts/content.mdx b/apps/docs/app/(trees)/docs/Reference/SharedConcepts/content.mdx
index b4aa9c94e..586e7c60c 100644
--- a/apps/docs/app/(trees)/docs/Reference/SharedConcepts/content.mdx
+++ b/apps/docs/app/(trees)/docs/Reference/SharedConcepts/content.mdx
@@ -1,8 +1,8 @@
### Shared concepts
-This page owns the cross-runtime language for Trees. React, vanilla, and SSR all
-use the same path-first identity model, the same tree-defining inputs, and the
-same mutation vocabulary.
+This page owns the cross-runtime language for Trees. React, Vue, vanilla, and
+SSR all use the same path-first identity model, the same tree-defining inputs,
+and the same mutation vocabulary.
#### Path-first identity
@@ -89,8 +89,8 @@ Trees supports three search modes:
- `expand-matches`: expands matching branches into surrounding tree context and
keeps non-matching rows visible.
-React and vanilla use the same search modes. Runtime pages should not redefine
-them.
+React, Vue, and vanilla use the same search modes. Runtime pages should not
+redefine them.
#### Selection, focus, and item handles
@@ -106,14 +106,14 @@ Common lookup topics are:
Use
[Navigate selection, focus, and search](#navigate-selection-focus-and-search)
-for workflows, [React API](#react-api) for selector hooks, and
-[Vanilla API](#vanilla-api) for direct class methods.
+for workflows, [React API](#react-api) and [Vue API](#vue-api) for reactive
+selector helpers, and [Vanilla API](#vanilla-api) for direct class methods.
#### Interaction and editing options
`dragAndDrop`, `renaming`, and `composition` are the runtime-agnostic editing
-surfaces. They share the same path-first event model even though the React and
-vanilla integration points differ.
+surfaces. They share the same path-first event model even though each runtime's
+integration points differ.
Use
[Rename, drag, and trigger item actions](#rename-drag-and-trigger-item-actions)
@@ -141,7 +141,7 @@ virtualized row window.
For density, prefer the `density` keyword (`'compact' | 'default' | 'relaxed'`)
or a numeric factor — it resolves both row height and spacing in one place, and
-the React `` wrapper paints `--trees-item-height` and
+the React and Vue `` wrappers paint `--trees-item-height` and
`--trees-density-override` for you. Reach for `itemHeight` only when you need a
row height that doesn't match a preset.
@@ -171,8 +171,8 @@ turn them into a filesystem-sync tutorial.
Server preload returns one handoff object. Pass it forward unchanged.
-React consumes that handoff as `preloadedData`. Vanilla hydrates existing server
-markup through `hydrate({ fileTreeContainer })`. Neither runtime should teach
-field-by-field payload choreography as the main story.
+React and Vue consume that handoff as `preloadedData`. Vanilla hydrates existing
+server markup through `hydrate({ fileTreeContainer })`. Runtime pages should not
+teach field-by-field payload choreography as the main story.
For the preload and hydration contract, read [SSR API](#ssr-api).
diff --git a/apps/docs/app/(trees)/docs/Reference/StylingAndTheming/content.mdx b/apps/docs/app/(trees)/docs/Reference/StylingAndTheming/content.mdx
index c38ad1cf9..ae19f844c 100644
--- a/apps/docs/app/(trees)/docs/Reference/StylingAndTheming/content.mdx
+++ b/apps/docs/app/(trees)/docs/Reference/StylingAndTheming/content.mdx
@@ -9,6 +9,8 @@ Host styling comes first.
- React: use normal host `className` and `style` props on
``
+- Vue: use normal host `class` and `style` attributes on
+ ``
- Vanilla: style the mounted host returned by `getFileTreeContainer()`
Host styles own width, height, borders, background, and panel placement. CSS
@@ -51,7 +53,8 @@ Input shape:
Output shape:
- `TreeThemeStyles`
-- compatible with React inline styles and vanilla host-style assignment
+- compatible with React inline styles, Vue style bindings, and vanilla
+ host-style assignment
@@ -65,6 +68,12 @@ React:
- styling stays on the host element through normal `className` and `style` props
- there is no separate React-only styling API beyond shared tree options
+Vue:
+
+- styling stays on the host element through normal `class` and `style`
+ attributes
+- there is no separate Vue-only styling API beyond shared tree options
+
Vanilla:
- apply styles through the host or container you already own
diff --git a/apps/docs/app/(trees)/docs/Reference/VueAPI/constants.ts b/apps/docs/app/(trees)/docs/Reference/VueAPI/constants.ts
new file mode 100644
index 000000000..0f679af1a
--- /dev/null
+++ b/apps/docs/app/(trees)/docs/Reference/VueAPI/constants.ts
@@ -0,0 +1,25 @@
+import { docsCodeSnippet } from '@/lib/docsCodeSnippet';
+
+export const VUE_API_EXAMPLE = docsCodeSnippet(
+ 'project-tree.vue',
+ `
+
+
+
+`
+);
+
+export const VUE_API_SELECTOR_COMPOSABLES = docsCodeSnippet(
+ 'selector-composables.ts',
+ `const { model } = useFileTree({ paths, search: true });
+const selectedPaths = useFileTreeSelection(model);
+const search = useFileTreeSearch(model);
+const focusedPath = useFileTreeSelector(model, (currentModel) =>
+ currentModel.getFocusedPath()
+);`
+);
diff --git a/apps/docs/app/(trees)/docs/Reference/VueAPI/content.mdx b/apps/docs/app/(trees)/docs/Reference/VueAPI/content.mdx
new file mode 100644
index 000000000..b93feea54
--- /dev/null
+++ b/apps/docs/app/(trees)/docs/Reference/VueAPI/content.mdx
@@ -0,0 +1,132 @@
+### Vue API
+
+The Vue entry point lives in `@pierre/trees/vue`. It is a thin Vue integration
+layer over the same imperative model the vanilla runtime uses.
+
+#### Vue model-first story
+
+Start with `useFileTree(options)`, not with a giant controlled component. The
+composable creates one stable model for the component lifetime. Later option
+changes are not a controlled reconfiguration path. Update the model through
+model methods instead.
+
+
+
+For the shared option meanings behind that call, read
+[Shared concepts](#shared-concepts-shared-option-groups).
+
+#### `useFileTree(...)`
+
+`useFileTree(...)`:
+
+- accepts the shared tree options surface
+- creates the model exactly once
+- cleans the model up when the Vue owner unmounts
+- returns `UseFileTreeResult`, which currently exposes `model`
+
+Keep using model methods such as `resetPaths(...)`, `setComposition(...)`,
+`setGitStatus(...)`, `setIcons(...)`, and the search or mutation methods when
+the tree changes after mount.
+
+#### ``
+
+The Vue component mounts the model into a host element.
+
+Primary props:
+
+- `model`
+- normal host attributes such as `class`, `style`, and `id`
+
+Vue-only props and slots:
+
+- `preloadedData`
+- `#header`
+- `#context-menu="{ item, context }"`
+
+Behavior notes:
+
+- it renders the model into the host element
+- it hydrates instead of rendering fresh when matching preloaded content is
+ already present
+- it uses `id ?? preloadedData?.id` for host identity when hydration is involved
+- `#header` and `#context-menu` layer Vue rendering onto the model's composition
+ surface
+
+#### Reading tree state from Vue
+
+Vue components read from the model through composables and write back through
+model methods.
+
+Common read patterns include:
+
+- selected paths for sibling panels or action bars
+- search state for search inputs and result counts
+- custom derived snapshots such as "is this path focused?"
+
+#### Selector composables
+
+`useFileTreeSelector(model, selector, equality?)`
+
+- generic bridge from the imperative model into Vue
+- returns a `ShallowRef` selected from the current model
+- accepts an optional equality function when the selected value needs custom
+ comparison
+
+`useFileTreeSelection(model)`
+
+- convenience composable for a `ShallowRef` of selected paths
+
+`useFileTreeSearch(model)`
+
+- returns computed search refs plus model-backed actions
+- refs: `isOpen`, `matchingPaths`, `value`
+- actions: `open(initialValue?)`, `close()`, `setValue(value)`,
+ `focusNextMatch()`, `focusPreviousMatch()`
+
+
+
+#### Writing to the model from Vue
+
+Vue writes through the same imperative model methods the vanilla runtime
+exposes.
+
+Typical write paths are:
+
+- focus and selection methods on the model or item handles
+- search methods such as `setSearch(...)` and `openSearch(...)`
+- mutation methods such as `add(...)`, `remove(...)`, `move(...)`, `batch(...)`,
+ and `resetPaths(...)`
+- runtime reconfiguration methods such as `setComposition(...)`,
+ `setGitStatus(...)`, and `setIcons(...)`
+
+#### Vue-specific composition surface
+
+Vue adds two high-level slots on top of the model:
+
+- `#header` for Vue-rendered header content
+- `#context-menu="{ item, context }"` for Vue-rendered context menus
+
+Use them when the menu or header belongs in Vue. The shared meaning of the
+underlying composition options still lives in
+[Shared concepts](#shared-concepts-interaction-and-editing-options).
+
+#### Mutation and subscriptions from Vue
+
+Use composables when Vue UI needs reactive reads. Use `model.onMutation(...)`
+when you need semantic side effects such as persistence, logging, or analytics
+around add, remove, move, reset, or batch operations.
+
+#### Hydration boundary
+
+The Vue runtime consumes server preload through `preloadedData` on
+``. The model still comes from `useFileTree(...)`, and the client
+must use matching tree-defining options.
+
+For the preload contract, read [SSR API](#ssr-api). For workflow guidance, read
+[SSR](#ssr).
+
+#### Appearance boundary
+
+Style the Vue host with normal host attributes such as `class` and `style`. For
+deeper lookup, use [Styling and theming](#styling-and-theming) and
+[Icons](#icons).
diff --git a/apps/docs/lib/product-config.ts b/apps/docs/lib/product-config.ts
index 3825d04bf..3f888a676 100644
--- a/apps/docs/lib/product-config.ts
+++ b/apps/docs/lib/product-config.ts
@@ -40,7 +40,7 @@ export const PRODUCTS: Record = {
description:
"@pierre/trees is an open source file tree rendering library. It's built for performance and flexibility, is super customizable, and comes packed with features.",
llmsDescription:
- 'An open source file tree rendering library for the web. Built for extreme performance on large trees, with React and vanilla JS APIs, SSR support, and customizable styling.',
+ 'An open source file tree rendering library for the web. Built for extreme performance on large trees, with React, Vue, and vanilla JS APIs, SSR support, and customizable styling.',
basePath: '',
docsPath: '/docs',
packageName: '@pierre/trees',
diff --git a/apps/docs/scripts/generate-llms-txt.ts b/apps/docs/scripts/generate-llms-txt.ts
index 364949c2a..bfd56e8ee 100644
--- a/apps/docs/scripts/generate-llms-txt.ts
+++ b/apps/docs/scripts/generate-llms-txt.ts
@@ -54,6 +54,7 @@ const DIFFS_SECTIONS = [
const TREES_SECTIONS = [
'Guides/ChooseYourIntegration',
'Guides/GetStartedWithReact',
+ 'Guides/GetStartedWithVue',
'Guides/GetStartedWithVanilla',
'Guides/ShapeTreeDataForFastRendering',
'Guides/NavigateSelectionFocusAndSearch',
@@ -65,6 +66,7 @@ const TREES_SECTIONS = [
'Guides/SSR',
'Reference/SharedConcepts',
'Reference/ReactAPI',
+ 'Reference/VueAPI',
'Reference/VanillaAPI',
'Reference/SSRAPI',
'Reference/StylingAndTheming',
@@ -95,9 +97,11 @@ const SECTION_DESCRIPTIONS: Record> = {
},
trees: {
'Guides/ChooseYourIntegration':
- 'Choosing between React and vanilla, with the shared path-first model',
+ 'Choosing between React, Vue, and vanilla, with the shared path-first model',
'Guides/GetStartedWithReact':
'React quickstart with useFileTree, FileTree, selectors, and prepared input',
+ 'Guides/GetStartedWithVue':
+ 'Vue quickstart with useFileTree, FileTree, selectors, and prepared input',
'Guides/GetStartedWithVanilla':
'Vanilla quickstart with new FileTree, render, model methods, and prepared input',
'Guides/ShapeTreeDataForFastRendering':
@@ -115,11 +119,13 @@ const SECTION_DESCRIPTIONS: Record> = {
'Guides/HandleLargeTreesEfficiently':
'Prepared input, virtualization settings, and SSR guidance for large trees',
'Guides/SSR':
- 'Server preload, React and vanilla hydration, and opaque SSR handoff guidance',
+ 'Server preload, React, Vue, and vanilla hydration, and opaque SSR handoff guidance',
'Reference/SharedConcepts':
'Path-first identity, shared options, search modes, mutation vocabulary, and SSR framing',
'Reference/ReactAPI':
'useFileTree, FileTree, selector hooks, and React-specific composition lookup',
+ 'Reference/VueAPI':
+ 'useFileTree, FileTree, selector composables, and Vue-specific slots',
'Reference/VanillaAPI':
'FileTree construction, lifecycle, imperative methods, and subscriptions',
'Reference/SSRAPI':
@@ -178,6 +184,7 @@ function extToLang(filename: string): string {
const map: Record = {
'.ts': 'typescript',
'.tsx': 'tsx',
+ '.vue': 'vue',
'.js': 'javascript',
'.jsx': 'jsx',
'.css': 'css',
diff --git a/bun.lock b/bun.lock
index 8811217ce..f420c75e7 100644
--- a/bun.lock
+++ b/bun.lock
@@ -104,7 +104,7 @@
},
"packages/diffs": {
"name": "@pierre/diffs",
- "version": "1.1.19",
+ "version": "1.1.20",
"dependencies": {
"@pierre/theme": "catalog:",
"@shikijs/transformers": "^3.0.0",
@@ -209,6 +209,8 @@
"@types/jsdom": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
+ "@vitejs/plugin-vue": "catalog:",
+ "@vue/server-renderer": "catalog:",
"autoprefixer": "catalog:",
"jsdom": "catalog:",
"postcss": "catalog:",
@@ -217,11 +219,16 @@
"tsdown": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
+ "vue": "catalog:",
},
"peerDependencies": {
"react": "^18.3.1 || ^19.0.0",
"react-dom": "^18.3.1 || ^19.0.0",
+ "vue": "^3.5.0",
},
+ "optionalPeers": [
+ "vue",
+ ],
},
"packages/truncate": {
"name": "@pierre/truncate",
@@ -290,7 +297,9 @@
"@types/react-dom": "19.2.3",
"@typescript/native-preview": "7.0.0-dev.20260128.1",
"@vitejs/plugin-react": "5.0.3",
+ "@vitejs/plugin-vue": "6.0.6",
"@vscode/web-custom-data": "0.6.3",
+ "@vue/server-renderer": "3.5.33",
"autoprefixer": "10.4.22",
"babel-plugin-react-compiler": "1.0.0",
"baseline-browser-mapping": "^2.9.14",
@@ -338,6 +347,7 @@
"typescript": "5.9.2",
"unist-util-visit": "5.0.0",
"vite": "npm:rolldown-vite@7.1.12",
+ "vue": "3.5.33",
"zod": "4.1.11",
},
"packages": {
@@ -889,8 +899,28 @@
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.0.3", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.35", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-PFVHhosKkofGH0Yzrw1BipSedTH68BFF8ZWy1kfUpCtJcouXXY0+racG8sExw7hw0HoX36813ga5o3LTWZ4FUg=="],
+ "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.6", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.13" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg=="],
+
"@vscode/web-custom-data": ["@vscode/web-custom-data@0.6.3", "", {}, "sha512-3pDUAPGVkra1KjR2L5m3b7BgzLTlWdep4ijsRoqeLcrp+e7cJcyjnae8IkAdF/xS6Zo3B1YZSmIBIhRAEYBIog=="],
+ "@vue/compiler-core": ["@vue/compiler-core@3.5.33", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/shared": "3.5.33", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw=="],
+
+ "@vue/compiler-dom": ["@vue/compiler-dom@3.5.33", "", { "dependencies": { "@vue/compiler-core": "3.5.33", "@vue/shared": "3.5.33" } }, "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA=="],
+
+ "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.33", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/compiler-core": "3.5.33", "@vue/compiler-dom": "3.5.33", "@vue/compiler-ssr": "3.5.33", "@vue/shared": "3.5.33", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.10", "source-map-js": "^1.2.1" } }, "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA=="],
+
+ "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.33", "", { "dependencies": { "@vue/compiler-dom": "3.5.33", "@vue/shared": "3.5.33" } }, "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A=="],
+
+ "@vue/reactivity": ["@vue/reactivity@3.5.33", "", { "dependencies": { "@vue/shared": "3.5.33" } }, "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A=="],
+
+ "@vue/runtime-core": ["@vue/runtime-core@3.5.33", "", { "dependencies": { "@vue/reactivity": "3.5.33", "@vue/shared": "3.5.33" } }, "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ=="],
+
+ "@vue/runtime-dom": ["@vue/runtime-dom@3.5.33", "", { "dependencies": { "@vue/reactivity": "3.5.33", "@vue/runtime-core": "3.5.33", "@vue/shared": "3.5.33", "csstype": "^3.2.3" } }, "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw=="],
+
+ "@vue/server-renderer": ["@vue/server-renderer@3.5.33", "", { "dependencies": { "@vue/compiler-ssr": "3.5.33", "@vue/shared": "3.5.33" }, "peerDependencies": { "vue": "3.5.33" } }, "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw=="],
+
+ "@vue/shared": ["@vue/shared@3.5.33", "", {}, "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ=="],
+
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
@@ -1995,6 +2025,8 @@
"vite": ["rolldown-vite@7.1.12", "", { "dependencies": { "@oxc-project/runtime": "0.90.0", "fdir": "^6.5.0", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.39", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-JREtUS+Lpa3s5Ha3ajf2F4LMS4BFxlVjpGz0k0ZR8rV3ZO3tzk5hukqyi9yRBcrvnTUg/BEForyCDahALFYAZA=="],
+ "vue": ["vue@3.5.33", "", { "dependencies": { "@vue/compiler-dom": "3.5.33", "@vue/compiler-sfc": "3.5.33", "@vue/runtime-dom": "3.5.33", "@vue/server-renderer": "3.5.33", "@vue/shared": "3.5.33" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ=="],
+
"w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
@@ -2081,6 +2113,20 @@
"@types/jsdom/undici-types": ["undici-types@7.24.7", "", {}, "sha512-XA+gOBkzYD3C74sZowtCLTpgtaCdqZhqCvR6y9LXvrKTt/IVU6bz49T4D+BPi475scshCCkb0IklJRw6T1ZlgQ=="],
+ "@vitejs/plugin-vue/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.13", "", {}, "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA=="],
+
+ "@vue/compiler-core/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
+
+ "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
+
+ "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+
+ "@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
+
+ "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+
+ "@vue/compiler-sfc/postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="],
+
"body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"brace-expansion/balanced-match": ["balanced-match@4.0.3", "", {}, "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g=="],
diff --git a/package.json b/package.json
index 0b891242e..ead1c6f59 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,8 @@
"@typescript/native-preview": "7.0.0-dev.20260128.1",
"@vscode/web-custom-data": "0.6.3",
"@vitejs/plugin-react": "5.0.3",
+ "@vitejs/plugin-vue": "6.0.6",
+ "@vue/server-renderer": "3.5.33",
"autoprefixer": "10.4.22",
"babel-plugin-react-compiler": "1.0.0",
"baseline-browser-mapping": "^2.9.14",
@@ -93,6 +95,7 @@
"typescript": "5.9.2",
"unist-util-visit": "5.0.0",
"vite": "npm:rolldown-vite@7.1.12",
+ "vue": "3.5.33",
"zod": "4.1.11"
}
},
diff --git a/packages/trees/PUBLISHING.md b/packages/trees/PUBLISHING.md
index 92f8b003e..3e9ee4e9d 100644
--- a/packages/trees/PUBLISHING.md
+++ b/packages/trees/PUBLISHING.md
@@ -62,6 +62,7 @@ not mask packaging bugs. Do this against a beta publish (step 4 with
- React `18.3.1`
- React `19`
+- Vue `3.5`
In each:
@@ -71,7 +72,7 @@ In each:
4. Run a production build.
5. Render a simple tree in a real browser.
6. Exercise each subpath: `@pierre/trees`, `@pierre/trees/react`,
- `@pierre/trees/ssr`, `@pierre/trees/web-components`.
+ `@pierre/trees/vue`, `@pierre/trees/ssr`, `@pierre/trees/web-components`.
**Bun note.** Bun's `minimum-release-age` protection can block fresh installs
right after a publish. Use:
diff --git a/packages/trees/README.md b/packages/trees/README.md
index 4225e3c24..29d297bb8 100644
--- a/packages/trees/README.md
+++ b/packages/trees/README.md
@@ -2,11 +2,12 @@
Path-first file tree UI for the web.
-`@pierre/trees` ships one implementation through four public entry points:
+`@pierre/trees` ships one implementation through five public entry points:
- `@pierre/trees` — vanilla model, mounting API, prepared input helpers, icons,
theming, and core types
- `@pierre/trees/react` — React hooks and ``
+- `@pierre/trees/vue` — Vue 3 component and composables
- `@pierre/trees/ssr` — preload helpers for declarative-shadow-DOM SSR
- `@pierre/trees/web-components` — custom-element registration side effect
@@ -91,6 +92,35 @@ export function Example({ paths }: { paths: string[] }) {
`@pierre/trees/react` exports `FileTree`, `useFileTree`, `useFileTreeSearch`,
`useFileTreeSelection`, and `useFileTreeSelector`.
+## Vue usage
+
+```vue
+
+
+
+
+
+ Project files
+
+
+