Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/vue-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@dunky.dev/vue-state-machine': minor
---

Add the Vue 3 bindings package (`@dunky.dev/vue-state-machine`). It mirrors the React package's API one-for-one — `useMachine`, `useSelector`, `normalize`, `mergeProps`, and the `ComponentEffect` type — implemented with Vue's reactivity: `useMachine` builds the machine + connector once in `setup()`, runs `start`/`stop` on the mount lifecycle, pushes prop changes through `setProps`, runs each `ComponentEffect` as its own dep-keyed `watch`, and exposes the connector snapshot as a `ComputedRef`; `useSelector` returns a value-deduped readonly ref; `normalize` translates the agnostic bindings to Vue DOM/ARIA props; `mergeProps` merges consumer + component props with Vue's `class`/`style` conventions.

```vue
<script setup lang="ts">
import { useAttrs } from 'vue'
import { useMachine, normalize, mergeProps } from '@dunky.dev/vue-state-machine'
import { toggleMachineConfig, connectToggle, toggleEffects, type ToggleProps } from './toggle'

const props = defineProps<ToggleProps>()
const attrs = useAttrs()

const { api } = useMachine(toggleMachineConfig, connectToggle, toggleEffects, props)
</script>

<template>
<button v-bind="mergeProps(attrs, normalize(api.parts.trigger))">
{{ api.pressed ? 'On' : 'Off' }}
</button>
</template>
```
14 changes: 7 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repo. This file is the canonical entry point: read it first, every time.

This is Dunky's state-machine monorepo: UI behavior authored once as
plain TypeScript state machines (`packages/core`), rendered anywhere
through thin per-substrate targets (`react`, `native`, `opentui`), with
through thin per-substrate targets (`react`, `vue`, `native`, `opentui`), with
a benchmark suite, per-substrate sandboxes, and the docs website
alongside.

Expand All @@ -24,12 +24,12 @@ editing files in that scope — it overrides anything here for that scope

## Scopes

| Scope | Path | What it is |
| --------- | ------------- | ---------------------------------------------------------------------------------- |
| Packages | `packages/**` | The core machine, substrate targets (react, native, opentui), and shared internals |
| Benchmark | `benchmark/` | Perf suite comparing against competitor libraries |
| Sandbox | `sandbox/` | Per-substrate demo apps for manual verification |
| Website | `website/` | The docs site |
| Scope | Path | What it is |
| --------- | ------------- | --------------------------------------------------------------------------------------- |
| Packages | `packages/**` | The core machine, substrate targets (react, vue, native, opentui), and shared internals |
| Benchmark | `benchmark/` | Perf suite comparing against competitor libraries |
| Sandbox | `sandbox/` | Per-substrate demo apps for manual verification |
| Website | `website/` | The docs site |

Some changes are cross-scope: a change in `core/` may need follow-up in
the targets, sandboxes, and docs — and vice versa. Check what else your
Expand Down
22 changes: 11 additions & 11 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The host
| bridged per target
v
+------------------------------------------------------------------------+
| <target> (react, native, opentui, …) |
| <target> (react, vue, native, opentui, …) |
| Runtime-specific bridge |
| • lifecycle (build + start/stop) • normalize bindings -> props |
| • selector subscription |
Expand Down Expand Up @@ -58,7 +58,7 @@ actions. Nothing in `core/` knows that React or the DOM exists.
substrate-agnostic event and attr vocabulary (`onPress`, `role`, …); `shared/utils`
owns cross-target helpers (mergeProps, composeHandlers, positioning).

**`<target>/`** is the substrate side — `react`, `native`, `opentui`, and any
**`<target>/`** is the substrate side — `react`, `vue`, `native`, `opentui`, and any
future renderer. Each target is the runtime bridge for one environment: the
lifecycle bridge, the event normalization, and the selector subscription all
live here.
Expand Down Expand Up @@ -93,12 +93,12 @@ Zag, whose machines read props directly.)

## Project structure

| File / location | What it owns |
| --------------------------- | ------------------------------------------------------------- |
| `packages/core/` | State-machine engine (plain-mutation kernel) |
| `packages/shared/bindings/` | Substrate-agnostic event + attr vocabulary (onPress, role, …) |
| `packages/shared/utils/` | mergeProps, composeHandlers, positioning, memo |
| `packages/<target>/` | Hook + normalize per substrate (react, native, opentui, …) |
| File / location | What it owns |
| --------------------------- | --------------------------------------------------------------- |
| `packages/core/` | State-machine engine (plain-mutation kernel) |
| `packages/shared/bindings/` | Substrate-agnostic event + attr vocabulary (onPress, role, …) |
| `packages/shared/utils/` | mergeProps, composeHandlers, positioning, memo |
| `packages/<target>/` | Hook + normalize per substrate (react, vue, native, opentui, …) |

## The map

Expand All @@ -121,7 +121,7 @@ shared/bindings substrate-agnostic event + attr vocabulary
shared/utils cross-target, cross-component helpers
+-- (composeHandlers, positioning, memo, mergeProps)

<target> one substrate (react, native, opentui, …)
<target> one substrate (react, vue, native, opentui, …)
| runtime, hooks, and props translator
+-- use-machine lifecycle bridge (build + start/stop + useSyncExternalStore)
+-- use-selector fine-grained leaf subscription (O(readers))
Expand All @@ -136,7 +136,7 @@ Three package groups, three jobs:
event + attr vocabulary; `shared/utils` owns agnostic helpers (positioning,
prop merging, memoization).
- **`<target>/`** — _the substrate side_. One folder per renderer
(`react`, `native`, `opentui`). Owns its runtime bridge and its props translator.
(`react`, `vue`, `native`, `opentui`). Owns its runtime bridge and its props translator.

## The machine parts

Expand Down Expand Up @@ -177,7 +177,7 @@ whether it needs props/platform or not:
| Term | What it is |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **host** | The agnostic core — `packages/core/*`. Declares what behavior is. |
| **target** | A substrate-specific bridge package and its render environment — `packages/<target>/*` (`react`, `native`, `opentui`, …). |
| **target** | A substrate-specific bridge package and its render environment — `packages/<target>/*` (`react`, `vue`, `native`, `opentui`, …). |
| **machine** | A state-graph config consumed by `machine()`; returns a startable service. |
| **connect** | A function returning the logical surface a view spreads onto elements. |
| **bindings** | The substrate-agnostic event + attr vocabulary — lives in `shared/bindings`, consumed by every target's normalize. Each target's rename map and drop set are vocabulary-typed (`HandlerTargets`/`AttrTargets`, `HandlerKey`/`AttrKey`), so a typo'd or unknown key is a compile error. |
Expand Down
21 changes: 21 additions & 0 deletions packages/vue/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Ivan Banov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading