@xmachines/play-vue 1.0.0-beta.2 → 1.0.0-beta.20

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 (74) hide show
  1. package/README.md +214 -84
  2. package/dist/PlayRenderer.js +7 -0
  3. package/dist/PlayRenderer.js.map +1 -0
  4. package/dist/PlayRenderer.vue_vue_type_script_lang.js +57 -0
  5. package/dist/PlayRenderer.vue_vue_type_script_lang.js.map +1 -0
  6. package/dist/define-registry.d.ts +79 -0
  7. package/dist/define-registry.d.ts.map +1 -0
  8. package/dist/define-registry.js +21 -0
  9. package/dist/define-registry.js.map +1 -0
  10. package/dist/index.d.ts +15 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +5 -5
  13. package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js +111 -0
  14. package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js.map +1 -0
  15. package/dist/node_modules/@json-render/core/dist/index.js +956 -0
  16. package/dist/node_modules/@json-render/core/dist/index.js.map +1 -0
  17. package/dist/node_modules/@json-render/core/dist/store-utils.js +1 -0
  18. package/dist/node_modules/@json-render/vue/dist/chunk-WIPZLAF7.js +57 -0
  19. package/dist/node_modules/@json-render/vue/dist/chunk-WIPZLAF7.js.map +1 -0
  20. package/dist/node_modules/@json-render/vue/dist/index.js +796 -0
  21. package/dist/node_modules/@json-render/vue/dist/index.js.map +1 -0
  22. package/dist/node_modules/@json-render/xstate/dist/index.js +20 -0
  23. package/dist/node_modules/@json-render/xstate/dist/index.js.map +1 -0
  24. package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js +227 -0
  25. package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js.map +1 -0
  26. package/dist/node_modules/zod/v4/classic/errors.js +25 -0
  27. package/dist/node_modules/zod/v4/classic/errors.js.map +1 -0
  28. package/dist/node_modules/zod/v4/classic/iso.js +33 -0
  29. package/dist/node_modules/zod/v4/classic/iso.js.map +1 -0
  30. package/dist/node_modules/zod/v4/classic/parse.js +8 -0
  31. package/dist/node_modules/zod/v4/classic/parse.js.map +1 -0
  32. package/dist/node_modules/zod/v4/classic/schemas.js +362 -0
  33. package/dist/node_modules/zod/v4/classic/schemas.js.map +1 -0
  34. package/dist/node_modules/zod/v4/core/api.js +530 -0
  35. package/dist/node_modules/zod/v4/core/api.js.map +1 -0
  36. package/dist/node_modules/zod/v4/core/checks.js +285 -0
  37. package/dist/node_modules/zod/v4/core/checks.js.map +1 -0
  38. package/dist/node_modules/zod/v4/core/core.js +46 -0
  39. package/dist/node_modules/zod/v4/core/core.js.map +1 -0
  40. package/dist/node_modules/zod/v4/core/doc.js +25 -0
  41. package/dist/node_modules/zod/v4/core/doc.js.map +1 -0
  42. package/dist/node_modules/zod/v4/core/errors.js +43 -0
  43. package/dist/node_modules/zod/v4/core/errors.js.map +1 -0
  44. package/dist/node_modules/zod/v4/core/json-schema-processors.js +183 -0
  45. package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +1 -0
  46. package/dist/node_modules/zod/v4/core/parse.js +70 -0
  47. package/dist/node_modules/zod/v4/core/parse.js.map +1 -0
  48. package/dist/node_modules/zod/v4/core/regexes.js +27 -0
  49. package/dist/node_modules/zod/v4/core/regexes.js.map +1 -0
  50. package/dist/node_modules/zod/v4/core/registries.js +42 -0
  51. package/dist/node_modules/zod/v4/core/registries.js.map +1 -0
  52. package/dist/node_modules/zod/v4/core/schemas.js +823 -0
  53. package/dist/node_modules/zod/v4/core/schemas.js.map +1 -0
  54. package/dist/node_modules/zod/v4/core/to-json-schema.js +224 -0
  55. package/dist/node_modules/zod/v4/core/to-json-schema.js.map +1 -0
  56. package/dist/node_modules/zod/v4/core/util.js +268 -0
  57. package/dist/node_modules/zod/v4/core/util.js.map +1 -0
  58. package/dist/node_modules/zod/v4/core/versions.js +10 -0
  59. package/dist/node_modules/zod/v4/core/versions.js.map +1 -0
  60. package/dist/types.d.ts +31 -7
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/useActor.d.ts +35 -0
  63. package/dist/useActor.d.ts.map +1 -0
  64. package/dist/useActor.js +15 -0
  65. package/dist/useActor.js.map +1 -0
  66. package/package.json +31 -23
  67. package/dist/PlayRenderer.vue.js +0 -8
  68. package/dist/PlayRenderer.vue.js.map +0 -1
  69. package/dist/PlayRenderer.vue2.js +0 -48
  70. package/dist/PlayRenderer.vue2.js.map +0 -1
  71. package/dist/_virtual/_plugin-vue_export-helper.js +0 -10
  72. package/dist/_virtual/_plugin-vue_export-helper.js.map +0 -1
  73. package/dist/index.css +0 -1
  74. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -1,137 +1,267 @@
1
1
  # @xmachines/play-vue
2
2
 
3
- Vue renderer component for XMachines Play architecture.
3
+ **Vue 3 renderer for XMachines Play Architecture**
4
4
 
5
- This package keeps Vue as passive infrastructure: it observes actor signals and forwards events, but does not enforce business policy.
5
+ Bridges TC39 Signal-driven actors to Vue's reactivity. Business logic stays in the actor; Vue is purely a rendering target.
6
+
7
+ ## Overview
8
+
9
+ `@xmachines/play-vue` provides `PlayRenderer`, a Vue component that:
10
+
11
+ - Subscribes to `actor.currentView` (TC39 Signal) and re-renders on every state transition
12
+ - Renders the current view's JSON spec via `@json-render/vue`
13
+ - Routes action names from spec elements to `actor.send()` via the `actions` prop
14
+ - Manages per-view UI state in an `@xstate/store` atom (automatic or caller-supplied)
15
+
16
+ Per [Play RFC](../docs/rfc/play.md):
17
+
18
+ - **Actor Authority (INV-01):** Guards in the machine decide all state transitions
19
+ - **Passive Infrastructure (INV-04):** Vue observes signals and dispatches events — never decides
20
+ - **Signal-Only Reactivity (INV-05):** `actor.currentView` signal is the sole render trigger
6
21
 
7
22
  ## Installation
8
23
 
9
24
  ```bash
10
- npm install @xmachines/play-vue vue
25
+ npm install @xmachines/play-vue
26
+ npm install @json-render/vue @json-render/core # peer deps
27
+ npm install @json-render/xstate @xstate/store # store integration
11
28
  ```
12
29
 
13
30
  ## Current Exports
14
31
 
15
- - `PlayRenderer` (Vue component)
32
+ - `PlayRenderer` — main renderer component (Vue SFC)
33
+ - `useActor` — composable for accessing the actor inside a `PlayRenderer` tree
34
+ - `defineRegistry` — SFC-aware wrapper; auto-wraps `.vue` SFCs via `h(SFC, ctx)`
35
+ - `useStateBinding` — re-exported from `@json-render/vue`
36
+ - `ComponentFn` (type) — re-exported from `@json-render/vue`
37
+ - `ComponentContext` (type) — re-exported from `@json-render/vue`
16
38
  - `PlayRendererProps` (type)
39
+ - `PlayActor` (type)
17
40
 
18
- ## Usage
41
+ ## Quick Start
19
42
 
20
- ```vue
21
- <script setup lang="ts">
22
- import { PlayRenderer } from "@xmachines/play-vue";
23
- import { definePlayer } from "@xmachines/play-xstate";
24
- import { defineCatalog } from "@xmachines/play-catalog";
43
+ ```ts
44
+ // catalog.ts — shared contract
45
+ import { defineCatalog } from "@json-render/core";
46
+ import { z } from "zod";
25
47
 
26
- // Define catalog
27
- const catalog = defineCatalog({
28
- home: { component: "Home", props: { title: "string" } },
29
- login: { component: "Login", props: { error: "string?" } },
30
- dashboard: { component: "Dashboard", props: { userId: "string" } },
48
+ export const catalog = defineCatalog({
49
+ elements: {
50
+ Login: { props: z.object({ title: z.string() }), description: "Login form" },
51
+ Dashboard: { props: z.object({ username: z.string() }), description: "Dashboard" },
52
+ },
31
53
  });
32
54
 
33
- // Create actor
34
- const actor = definePlayer({ machine: authMachine, catalog })();
35
- actor.start();
55
+ export type Catalog = typeof catalog;
56
+ ```
36
57
 
37
- // Define component map
38
- const components = {
39
- Home: HomeComponent,
40
- Login: LoginComponent,
41
- Dashboard: DashboardComponent,
42
- };
58
+ ```vue
59
+ <!-- Login.vue Vue SFC; useStateBinding works in <script setup> -->
60
+ <script setup lang="ts">
61
+ import { useStateBinding } from "@xmachines/play-vue";
62
+ import type { ComponentContext } from "@xmachines/play-vue";
63
+ import type { Catalog } from "./catalog.js";
64
+
65
+ const { props, emit, bindings } = defineProps<ComponentContext<Catalog, "Login">>();
66
+ const [username, setUsername] = useStateBinding<string>(bindings?.username ?? "/username");
43
67
  </script>
44
68
 
45
69
  <template>
46
- <PlayRenderer :actor="actor" :components="components">
47
- <template #fallback>
48
- <div>Loading...</div>
49
- </template>
50
- </PlayRenderer>
70
+ <div class="view">
71
+ <h2>{{ props.title }}</h2>
72
+ <form @submit.prevent="emit('submit')">
73
+ <input id="username" v-model="username" @input="setUsername(username)" />
74
+ <button type="submit">Log In</button>
75
+ </form>
76
+ </div>
51
77
  </template>
52
78
  ```
53
79
 
54
- ## API
80
+ ```ts
81
+ // registry.ts — pass SFCs directly; defineRegistry auto-wraps them
82
+ import { defineRegistry } from "@xmachines/play-vue";
83
+ import { catalog } from "./catalog.js";
84
+ import LoginSFC from "./Login.vue";
85
+ import DashboardSFC from "./Dashboard.vue";
55
86
 
56
- ### PlayRenderer
87
+ export const { registry } = defineRegistry(catalog, {
88
+ components: { Login: LoginSFC, Dashboard: DashboardSFC },
89
+ actions: { login: async () => {}, logout: async () => {} },
90
+ });
91
+ ```
57
92
 
58
- Main renderer component that observes `actor.currentView` signal and dynamically renders catalog components.
93
+ ```ts
94
+ // machine.ts
95
+ import { setup, assign } from "xstate";
96
+ import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
97
+
98
+ export const machine = setup({
99
+ types: {
100
+ context: {} as {
101
+ isAuthenticated: boolean;
102
+ username: string | null;
103
+ routeParams: Record<string, string>;
104
+ queryParams: Record<string, string>;
105
+ },
106
+ events: {} as
107
+ | { type: "auth.login"; username: string }
108
+ | { type: "auth.logout" }
109
+ | { type: "play.route"; to: string; params?: Record<string, string> },
110
+ },
111
+ }).createMachine(
112
+ formatPlayRouteTransitions({
113
+ id: "app",
114
+ initial: "login",
115
+ context: { isAuthenticated: false, username: null, routeParams: {}, queryParams: {} },
116
+ states: {
117
+ login: {
118
+ id: "login",
119
+ meta: {
120
+ route: "/login",
121
+ view: {
122
+ component: "Login",
123
+ spec: {
124
+ root: "root",
125
+ elements: {
126
+ root: { type: "Login", props: { title: "Sign In" }, children: [] },
127
+ },
128
+ },
129
+ },
130
+ },
131
+ },
132
+ dashboard: {
133
+ id: "dashboard",
134
+ meta: {
135
+ route: "/dashboard",
136
+ view: {
137
+ component: "Dashboard",
138
+ spec: {
139
+ root: "root",
140
+ elements: {
141
+ root: { type: "Dashboard", props: { username: "" }, children: [] },
142
+ },
143
+ },
144
+ },
145
+ },
146
+ },
147
+ },
148
+ on: {
149
+ "auth.login": {
150
+ target: ".dashboard",
151
+ guard: ({ context }) => !context.isAuthenticated,
152
+ actions: assign({ isAuthenticated: true, username: ({ event }) => event.username }),
153
+ },
154
+ "auth.logout": {
155
+ target: ".login",
156
+ guard: ({ context }) => context.isAuthenticated,
157
+ actions: assign({ isAuthenticated: false, username: null }),
158
+ },
159
+ },
160
+ }),
161
+ );
162
+ ```
59
163
 
60
- **Props:**
164
+ ```vue
165
+ <!-- App.vue -->
166
+ <script setup lang="ts">
167
+ import { definePlayer } from "@xmachines/play-xstate";
168
+ import { PlayRenderer } from "@xmachines/play-vue";
169
+ import { machine } from "./machine.js";
170
+ import { registry } from "./registry.js";
61
171
 
62
- - `actor` (required) - Actor instance with `currentView` signal
63
- - `components` (required) - Map of component names to Vue components
64
- - `fallback` (optional) - Slot shown when `currentView` is null
172
+ const createPlayer = definePlayer({ machine });
173
+ const actor = createPlayer();
174
+ actor.start();
175
+ </script>
65
176
 
66
- **Component Props:**
177
+ <template>
178
+ <PlayRenderer
179
+ :actor="actor"
180
+ :registry="registry"
181
+ :actions="{ login: 'auth.login', logout: 'auth.logout' }"
182
+ />
183
+ </template>
184
+ ```
67
185
 
68
- Each rendered component receives:
186
+ ## API Reference
69
187
 
70
- - All props from `view.props` (spread via `v-bind`)
71
- - `send` function for sending events to actor
188
+ ### `PlayRenderer`
72
189
 
73
- **Example Component:**
190
+ Main Vue component. Subscribes to `actor.currentView` and renders the spec.
74
191
 
75
192
  ```vue
76
- <script setup lang="ts">
77
- import type { AbstractActor } from "@xmachines/play-actor";
193
+ <PlayRenderer
194
+ :actor="actor"
195
+ :registry="registry"
196
+ :actions="{ login: 'auth.login' }"
197
+ :store="myStore"
198
+ />
199
+ ```
78
200
 
79
- defineProps<{
80
- userId: string;
81
- send: AbstractActor<any>["send"];
82
- }>();
201
+ **`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
83
202
 
84
- function handleClick() {
85
- send({ type: "user.click", payload: { action: "details" } });
86
- }
87
- </script>
203
+ **`registry`** — Built with `defineRegistry(catalog, { components, actions })` from `@xmachines/play-vue`. Pass `.vue` SFCs directly — they are auto-wrapped via `h(SFC, ctx)` so `useStateBinding` and other composables work inside `<script setup>`.
88
204
 
89
- <template>
90
- <div>
91
- <h1>User: {{ userId }}</h1>
92
- <button @click="handleClick">View Details</button>
93
- </div>
94
- </template>
205
+ **`actions`** — Maps json-render action names (from spec `on` bindings) to XState event type strings. Type-checked against `EventFromLogic<TLogic>["type"]` when `TLogic` is specified.
206
+
207
+ **`store`** (optional) Controls per-view UI state (`$state` bindings, form values):
208
+
209
+ - **Omitted (uncontrolled, default):** A fresh `@xstate/store` atom is created per view transition, seeded from `view.spec.state`.
210
+ - **Provided (controlled):** The caller owns the store; `spec.state` is ignored.
211
+
212
+ ```ts
213
+ import { createAtom } from "@xstate/store";
214
+ import { xstateStoreStateStore } from "@json-render/xstate";
215
+ import type { StateStore } from "@json-render/core";
216
+
217
+ const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
95
218
  ```
96
219
 
97
- ## Features
220
+ ```vue
221
+ <PlayRenderer
222
+ :actor="actor"
223
+ :registry="registry"
224
+ :store="store"
225
+ :actions="{ login: 'auth.login' }"
226
+ />
227
+ ```
98
228
 
99
- - **Signal Bridge:** Uses TC39 Signal.subtle.Watcher to bridge actor signals to Vue reactivity
100
- - **Dynamic Rendering:** Renders components based on `actor.currentView.component` string
101
- - **Type Safe:** Full TypeScript support with generic type inference
102
- - **Error Handling:** Gracefully handles missing components and null catalogs
103
- - **One-Shot Re-Watch:** Implements proper signal watcher pattern with microtask batching
229
+ ---
104
230
 
105
- ## Canonical Watcher Lifecycle
231
+ ### `useActor`
106
232
 
107
- Use the same watcher flow as other adapters/packages:
233
+ Vue composable for accessing the actor from inside any component rendered by `PlayRenderer`.
108
234
 
109
- 1. `notify`
110
- 2. `queueMicrotask`
111
- 3. `getPending()`
112
- 4. read actor signals and update framework-local state
113
- 5. re-arm with `watch(...)` or `watch()`
235
+ ```ts
236
+ import { useActor } from "@xmachines/play-vue";
114
237
 
115
- Watcher notify is one-shot. Re-arm is required for continuous observation.
238
+ // Inside any component rendered inside PlayRenderer:
239
+ const actor = useActor();
240
+ actor.send({ type: "auth.logout" });
241
+ ```
116
242
 
117
- ## Cleanup Contract
243
+ Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
118
244
 
119
- Cleanup must be explicit:
245
+ ---
120
246
 
121
- - Call `unwatch(...)` during Vue lifecycle teardown (`onUnmounted`).
122
- - Treat renderer/provider disposal as deterministic teardown, not GC-only cleanup.
123
- - Keep actor-driven routing and view decisions in the actor layer.
247
+ ## Route Parameters in Props
124
248
 
125
- ## Architecture
249
+ When using `formatPlayRouteTransitions`, URL path parameters flow automatically into component props. Declare an `undefined` slot in the spec to opt in:
126
250
 
127
- PlayRenderer follows the XMachines Play architecture principles:
251
+ ```ts
252
+ // spec: { section: undefined, user: "alice" }
253
+ // After play.route to /settings/profile → context.routeParams = { section: "profile" }
254
+ // Component receives: { section: "profile", user: "alice" }
255
+ ```
128
256
 
129
- 1. **Actor Authority:** Actor decides all state transitions
130
- 2. **Passive Infrastructure:** Renderer observes signals, sends events
131
- 3. **Signal-Only Reactivity:** Business logic state lives in actor signals
257
+ Priority: **route param fills `undefined` slots; explicit non-`undefined` spec props always win.**
132
258
 
133
- Signals are reactivity substrate only. They are not a business mutation channel.
259
+ ---
134
260
 
135
- ## License
261
+ ## Architecture Notes
136
262
 
137
- MIT
263
+ - Vue reactivity is only used to trigger re-renders — not for business logic
264
+ - `actor.currentView` (TC39 Signal) is bridged to Vue's reactive system inside `PlayRenderer`
265
+ - Per-view UI state lives in an `@xstate/store` atom, not in Vue reactive state
266
+ - `@json-render/vue` drives rendering; `PlayRenderer` is the signal bridge — import `defineRegistry`, `ComponentFn`, `ComponentContext`, and `useStateBinding` from `@xmachines/play-vue`
267
+ - Vue views should be `.vue` SFCs using `ComponentContext<MyCatalog, "X">` — `defineRegistry` from `@xmachines/play-vue` auto-wraps them via `h(SFC, ctx)`, giving each SFC its own `setup()` context where `useStateBinding` and Vue composables work correctly
@@ -0,0 +1,7 @@
1
+ import e from "./PlayRenderer.vue_vue_type_script_lang.js";
2
+ //#region src/PlayRenderer.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
6
+
7
+ //# sourceMappingURL=PlayRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlayRenderer.js","names":[],"sources":["../src/PlayRenderer.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayRenderer - Main Vue renderer component for XMachines Play architecture\n *\n * Architecture (per RESEARCH.md Pattern 1):\n * - Subscribes to actor.currentView signal via Signal.subtle.Watcher\n * - Renders view.spec via @json-render/vue Renderer + StateProvider + ActionProvider\n * - Routes actor.send() calls via ActionProvider handlers\n * - Vue ref only for triggering renders, NOT business logic\n *\n * State store: uses external `store` prop if provided (controlled mode); otherwise\n * creates a fresh @xstate/store atom per view transition seeded from spec.state.\n * The atom resets automatically when the actor transitions to a new view.\n *\n * Signal bridge uses one-shot re-watch pattern:\n * TC39 Signal watchers stop watching after notification, so watcher.watch()\n * must be called inside a microtask after getPending() to re-arm for the\n * next notification.\n *\n * CRITICAL: Never call signal.get() or signal.set() inside the Watcher's\n * notify callback. The callback runs synchronously during the signal graph's\n * dirty-propagation phase. All reads must be deferred to a queueMicrotask.\n *\n * @invariant Actor Authority - Actor decides all state transitions via guards\n * @invariant Passive Infrastructure - Component observes signals, sends events\n * @invariant Signal-Only Reactivity - Business logic state lives in actor signals\n */\n\nimport { defineComponent, ref, toRaw, onUnmounted, h } from \"vue\";\nimport type { PropType } from \"vue\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { AbstractActor, Viewable, ViewMetadata } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type { ComponentRegistry } from \"@json-render/vue\";\nimport type { StateStore } from \"@json-render/core\";\nimport { Renderer, StateProvider, ActionProvider, VisibilityProvider } from \"@json-render/vue\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@json-render/xstate\";\nimport { provideActor, type PlayActor } from \"./useActor.js\";\n\nexport default defineComponent({\n\tname: \"PlayRenderer\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\n\t\t\trequired: true,\n\t\t},\n\t\tregistry: {\n\t\t\ttype: Object as PropType<ComponentRegistry>,\n\t\t\trequired: true,\n\t\t},\n\t\tstore: {\n\t\t\ttype: Object as PropType<StateStore>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tactions: {\n\t\t\ttype: Object as PropType<Record<string, string>>,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// Unwrap actor from Vue's reactive proxy to access raw Signal objects\n\t\t// CRITICAL: toRaw() preserves Signal's 'this' binding for .get() and watcher operations\n\t\tconst actor = toRaw(props.actor);\n\n\t\t// Provide the raw actor to all descendants via Vue's provide/inject mechanism\n\t\tprovideActor(actor as PlayActor);\n\n\t\t// Get initial value from unwrapped signal\n\t\tconst initialView = actor.currentView.get();\n\n\t\t// Vue ref for triggering re-renders (NOT business logic state)\n\t\t// Signal is source of truth, ref is just Vue's render trigger\n\t\tconst view = ref<ViewMetadata | null>(initialView);\n\n\t\t// Internal per-view store — recreated on each view transition when no external store.\n\t\tlet internalStore: StateStore | null = null;\n\t\tlet lastView: ViewMetadata | null = null;\n\t\t// Key counter: forces Vue to remount StateProvider (and all descendants) when the\n\t\t// store changes. Without this, StateProvider's setup() only runs once — its provide()\n\t\t// call captures the FIRST store and never updates, leaving ActionProvider and child\n\t\t// components referencing a stale store.\n\t\tlet storeKey = 0;\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity\n\t\tconst unwatch = watchSignal(actor.currentView, (nextView) => {\n\t\t\tview.value = nextView;\n\t\t});\n\n\t\tonUnmounted(() => {\n\t\t\tunwatch();\n\t\t});\n\n\t\t// Use render function to avoid Vue 3.5 SFC template <slot> + jsdom renderSlot crash\n\t\treturn () => {\n\t\t\t// No view — show fallback slot\n\t\t\tif (!view.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\tconst spec = view.value.spec;\n\n\t\t\t// Resolve the store: external (controlled) or internal per-view atom\n\t\t\tlet store: StateStore;\n\t\t\tif (props.store) {\n\t\t\t\tstore = props.store;\n\t\t\t} else {\n\t\t\t\tif (internalStore === null || lastView !== view.value) {\n\t\t\t\t\tconst initialState = (spec.state as Record<string, unknown>) ?? {};\n\t\t\t\t\tinternalStore = xstateStoreStateStore({ atom: createAtom(initialState) });\n\t\t\t\t\tlastView = view.value;\n\t\t\t\t\tstoreKey++;\n\t\t\t\t}\n\t\t\t\tstore = internalStore;\n\t\t\t}\n\n\t\t\t// Map json-render action names to actor.send() calls\n\t\t\tconst handlers = Object.fromEntries(\n\t\t\t\tObject.entries(props.actions ?? {}).map(([actionName, eventType]) => [\n\t\t\t\t\tactionName,\n\t\t\t\t\tasync (params: Record<string, unknown> = {}) =>\n\t\t\t\t\t\tactor.send({ type: eventType, ...params }),\n\t\t\t\t]),\n\t\t\t);\n\n\t\t\treturn h(StateProvider, { store, key: storeKey }, () =>\n\t\t\t\th(ActionProvider, { handlers }, () =>\n\t\t\t\t\th(VisibilityProvider, {}, () =>\n\t\t\t\t\t\th(Renderer, { spec, registry: props.registry }),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t};\n\t},\n});\n</script>\n"],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { ActionProvider as e, Renderer as t, StateProvider as n, VisibilityProvider as r } from "./node_modules/@json-render/vue/dist/index.js";
2
+ import { createAtom as i } from "./node_modules/@xstate/store/dist/store-69e7e2d5.esm.js";
3
+ import { xstateStoreStateStore as a } from "./node_modules/@json-render/xstate/dist/index.js";
4
+ import { provideActor as o } from "./useActor.js";
5
+ import { defineComponent as s, h as c, onUnmounted as l, ref as u, toRaw as d } from "vue";
6
+ import { watchSignal as f } from "@xmachines/play-signals";
7
+ //#region src/PlayRenderer.vue?vue&type=script&lang.ts
8
+ var p = s({
9
+ name: "PlayRenderer",
10
+ props: {
11
+ actor: {
12
+ type: Object,
13
+ required: !0
14
+ },
15
+ registry: {
16
+ type: Object,
17
+ required: !0
18
+ },
19
+ store: {
20
+ type: Object,
21
+ default: void 0
22
+ },
23
+ actions: {
24
+ type: Object,
25
+ default: () => ({})
26
+ }
27
+ },
28
+ setup(s, { slots: p }) {
29
+ let m = d(s.actor);
30
+ o(m);
31
+ let h = u(m.currentView.get()), g = null, _ = null, v = 0, y = f(m.currentView, (e) => {
32
+ h.value = e;
33
+ });
34
+ return l(() => {
35
+ y();
36
+ }), () => {
37
+ if (!h.value) return p.fallback ? p.fallback() : null;
38
+ let o = h.value.spec, l;
39
+ s.store ? l = s.store : ((g === null || _ !== h.value) && (g = a({ atom: i(o.state ?? {}) }), _ = h.value, v++), l = g);
40
+ let u = Object.fromEntries(Object.entries(s.actions ?? {}).map(([e, t]) => [e, async (e = {}) => m.send({
41
+ type: t,
42
+ ...e
43
+ })]));
44
+ return c(n, {
45
+ store: l,
46
+ key: v
47
+ }, () => c(e, { handlers: u }, () => c(r, {}, () => c(t, {
48
+ spec: o,
49
+ registry: s.registry
50
+ }))));
51
+ };
52
+ }
53
+ });
54
+ //#endregion
55
+ export { p as default };
56
+
57
+ //# sourceMappingURL=PlayRenderer.vue_vue_type_script_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlayRenderer.vue_vue_type_script_lang.js","names":[],"sources":["../src/PlayRenderer.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayRenderer - Main Vue renderer component for XMachines Play architecture\n *\n * Architecture (per RESEARCH.md Pattern 1):\n * - Subscribes to actor.currentView signal via Signal.subtle.Watcher\n * - Renders view.spec via @json-render/vue Renderer + StateProvider + ActionProvider\n * - Routes actor.send() calls via ActionProvider handlers\n * - Vue ref only for triggering renders, NOT business logic\n *\n * State store: uses external `store` prop if provided (controlled mode); otherwise\n * creates a fresh @xstate/store atom per view transition seeded from spec.state.\n * The atom resets automatically when the actor transitions to a new view.\n *\n * Signal bridge uses one-shot re-watch pattern:\n * TC39 Signal watchers stop watching after notification, so watcher.watch()\n * must be called inside a microtask after getPending() to re-arm for the\n * next notification.\n *\n * CRITICAL: Never call signal.get() or signal.set() inside the Watcher's\n * notify callback. The callback runs synchronously during the signal graph's\n * dirty-propagation phase. All reads must be deferred to a queueMicrotask.\n *\n * @invariant Actor Authority - Actor decides all state transitions via guards\n * @invariant Passive Infrastructure - Component observes signals, sends events\n * @invariant Signal-Only Reactivity - Business logic state lives in actor signals\n */\n\nimport { defineComponent, ref, toRaw, onUnmounted, h } from \"vue\";\nimport type { PropType } from \"vue\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { AbstractActor, Viewable, ViewMetadata } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type { ComponentRegistry } from \"@json-render/vue\";\nimport type { StateStore } from \"@json-render/core\";\nimport { Renderer, StateProvider, ActionProvider, VisibilityProvider } from \"@json-render/vue\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@json-render/xstate\";\nimport { provideActor, type PlayActor } from \"./useActor.js\";\n\nexport default defineComponent({\n\tname: \"PlayRenderer\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\n\t\t\trequired: true,\n\t\t},\n\t\tregistry: {\n\t\t\ttype: Object as PropType<ComponentRegistry>,\n\t\t\trequired: true,\n\t\t},\n\t\tstore: {\n\t\t\ttype: Object as PropType<StateStore>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tactions: {\n\t\t\ttype: Object as PropType<Record<string, string>>,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// Unwrap actor from Vue's reactive proxy to access raw Signal objects\n\t\t// CRITICAL: toRaw() preserves Signal's 'this' binding for .get() and watcher operations\n\t\tconst actor = toRaw(props.actor);\n\n\t\t// Provide the raw actor to all descendants via Vue's provide/inject mechanism\n\t\tprovideActor(actor as PlayActor);\n\n\t\t// Get initial value from unwrapped signal\n\t\tconst initialView = actor.currentView.get();\n\n\t\t// Vue ref for triggering re-renders (NOT business logic state)\n\t\t// Signal is source of truth, ref is just Vue's render trigger\n\t\tconst view = ref<ViewMetadata | null>(initialView);\n\n\t\t// Internal per-view store — recreated on each view transition when no external store.\n\t\tlet internalStore: StateStore | null = null;\n\t\tlet lastView: ViewMetadata | null = null;\n\t\t// Key counter: forces Vue to remount StateProvider (and all descendants) when the\n\t\t// store changes. Without this, StateProvider's setup() only runs once — its provide()\n\t\t// call captures the FIRST store and never updates, leaving ActionProvider and child\n\t\t// components referencing a stale store.\n\t\tlet storeKey = 0;\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity\n\t\tconst unwatch = watchSignal(actor.currentView, (nextView) => {\n\t\t\tview.value = nextView;\n\t\t});\n\n\t\tonUnmounted(() => {\n\t\t\tunwatch();\n\t\t});\n\n\t\t// Use render function to avoid Vue 3.5 SFC template <slot> + jsdom renderSlot crash\n\t\treturn () => {\n\t\t\t// No view — show fallback slot\n\t\t\tif (!view.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\tconst spec = view.value.spec;\n\n\t\t\t// Resolve the store: external (controlled) or internal per-view atom\n\t\t\tlet store: StateStore;\n\t\t\tif (props.store) {\n\t\t\t\tstore = props.store;\n\t\t\t} else {\n\t\t\t\tif (internalStore === null || lastView !== view.value) {\n\t\t\t\t\tconst initialState = (spec.state as Record<string, unknown>) ?? {};\n\t\t\t\t\tinternalStore = xstateStoreStateStore({ atom: createAtom(initialState) });\n\t\t\t\t\tlastView = view.value;\n\t\t\t\t\tstoreKey++;\n\t\t\t\t}\n\t\t\t\tstore = internalStore;\n\t\t\t}\n\n\t\t\t// Map json-render action names to actor.send() calls\n\t\t\tconst handlers = Object.fromEntries(\n\t\t\t\tObject.entries(props.actions ?? {}).map(([actionName, eventType]) => [\n\t\t\t\t\tactionName,\n\t\t\t\t\tasync (params: Record<string, unknown> = {}) =>\n\t\t\t\t\t\tactor.send({ type: eventType, ...params }),\n\t\t\t\t]),\n\t\t\t);\n\n\t\t\treturn h(StateProvider, { store, key: storeKey }, () =>\n\t\t\t\th(ActionProvider, { handlers }, () =>\n\t\t\t\t\th(VisibilityProvider, {}, () =>\n\t\t\t\t\t\th(Renderer, { spec, registry: props.registry }),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t};\n\t},\n});\n</script>\n"],"mappings":";;;;;;;AAyCA,IAAA,IAAe,EAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;GACV;EACD,UAAU;GACT,MAAM;GACN,UAAU;GACV;EACD,OAAO;GACN,MAAM;GACN,SAAS,KAAA;GACT;EACD,SAAS;GACR,MAAM;GACN,gBAAgB,EAAE;GAClB;EACD;CACD,MAAM,GAAO,EAAE,YAAS;EAGvB,IAAM,IAAQ,EAAM,EAAM,MAAM;AAGhC,IAAa,EAAmB;EAOhC,IAAM,IAAO,EAJO,EAAM,YAAY,KAAK,CAIO,EAG9C,IAAmC,MACnC,IAAgC,MAKhC,IAAW,GAGT,IAAU,EAAY,EAAM,cAAc,MAAa;AAC5D,KAAK,QAAQ;IACZ;AAOF,SALA,QAAkB;AACjB,MAAS;IACR,QAGW;AAEZ,OAAI,CAAC,EAAK,MACT,QAAO,EAAM,WAAW,EAAM,UAAS,GAAI;GAG5C,IAAM,IAAO,EAAK,MAAM,MAGpB;AACJ,GAAI,EAAM,QACT,IAAQ,EAAM,UAEV,MAAkB,QAAQ,MAAa,EAAK,WAE/C,IAAgB,EAAsB,EAAE,MAAM,EADxB,EAAK,SAAqC,EAAE,CACG,EAAG,CAAC,EACzE,IAAW,EAAK,OAChB,MAED,IAAQ;GAIT,IAAM,IAAW,OAAO,YACvB,OAAO,QAAQ,EAAM,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,GAAY,OAAe,CACpE,GACA,OAAO,IAAkC,EAAE,KAC1C,EAAM,KAAK;IAAE,MAAM;IAAW,GAAG;IAAQ,CAAC,CAC3C,CAAC,CACF;AAED,UAAO,EAAE,GAAe;IAAE;IAAO,KAAK;IAAU,QAC/C,EAAE,GAAgB,EAAE,aAAU,QAC7B,EAAE,GAAoB,EAAE,QACvB,EAAE,GAAU;IAAE;IAAM,UAAU,EAAM;IAAU,CAAC,CAC/C,CACD,CACD;;;CAGH,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `defineRegistry` wrapper for @xmachines/play-vue.
3
+ *
4
+ * Wraps `defineRegistry` from `@json-render/vue` with automatic SFC support.
5
+ *
6
+ * ## Vue-specific — React and Solid do not need this
7
+ *
8
+ * In React, `useContext()` works anywhere inside the component call tree.
9
+ * In Solid, `useContext()` works inside reactive computations and component renders.
10
+ * Neither has the strict "synchronous setup() only" constraint that Vue's `inject()`
11
+ * imposes — so their `defineRegistry` implementations call `componentFn(ctx)` directly
12
+ * with no wrapping needed.
13
+ *
14
+ * For the DOM renderer, there is no component system at all — just render functions
15
+ * returning `HTMLElement` — so injection context is not applicable.
16
+ *
17
+ * Only Vue requires this adapter.
18
+ *
19
+ * ## Why this wrapper exists
20
+ *
21
+ * `@json-render/vue`'s `defineRegistry` calls each registered component as a plain
22
+ * function: `componentFn(ctx)`. A `.vue` SFC (output of `defineComponent` or
23
+ * `<script setup>`) is an **object**, not a function — calling it throws.
24
+ *
25
+ * More fundamentally, `defineRegistry` calls components inside its own render
26
+ * function (the return value of `setup()`). Vue's `inject()` — and composables built
27
+ * on it: `useStateBinding`, `useStateStore` — only work during synchronous `setup()`
28
+ * execution, not inside render functions. Plain `.ts` `ComponentFn` files cannot
29
+ * call any Vue composable for this reason.
30
+ *
31
+ * This wrapper auto-detects Vue SFCs in the `components` map and wraps them via
32
+ * `h(SFC, ctx)`. The SFC renders as a child component with its own `setup()`,
33
+ * giving full access to composables inside `<script setup>`.
34
+ *
35
+ * ## Usage
36
+ *
37
+ * Import `defineRegistry` from `@xmachines/play-vue` instead of `@json-render/vue`:
38
+ *
39
+ * ```ts
40
+ * import { defineRegistry } from "@xmachines/play-vue";
41
+ * // not: import { defineRegistry } from "@json-render/vue";
42
+ *
43
+ * import LoginSFC from "./views/Login.vue";
44
+ * import DashboardSFC from "./views/Dashboard.vue";
45
+ *
46
+ * const { registry } = defineRegistry(catalog, {
47
+ * components: {
48
+ * Login: LoginSFC, // .vue SFC — auto-wrapped
49
+ * Dashboard: DashboardSFC, // .vue SFC — auto-wrapped
50
+ * },
51
+ * });
52
+ * ```
53
+ *
54
+ * Plain `ComponentFn` functions still work and are passed through unchanged.
55
+ * Mixing SFCs and plain functions in the same registry is supported.
56
+ */
57
+ import { type Component } from "vue";
58
+ import { defineRegistry as defineRegistryBase, type ComponentFn } from "@json-render/vue";
59
+ import type { Catalog, InferCatalogComponents } from "@json-render/core";
60
+ export type ComponentEntry<C extends Catalog, K extends keyof InferCatalogComponents<C>> = ComponentFn<C, K> | Component;
61
+ export type ComponentsMap<C extends Catalog> = {
62
+ [K in keyof InferCatalogComponents<C>]?: ComponentEntry<C, K>;
63
+ };
64
+ export type DefineRegistryOptions<C extends Catalog> = Parameters<typeof defineRegistryBase<C>>[1] & {
65
+ components?: ComponentsMap<C>;
66
+ };
67
+ /**
68
+ * Create a component registry, automatically wrapping `.vue` SFCs so they work
69
+ * correctly with `@json-render/vue`'s rendering pipeline.
70
+ *
71
+ * Drop-in replacement for `defineRegistry` from `@json-render/vue`. Import from
72
+ * `@xmachines/play-vue` to get SFC support for free.
73
+ *
74
+ * @param catalog - The json-render catalog defining component prop shapes.
75
+ * @param options - Registry options. `components` entries may be `.vue` SFCs
76
+ * (objects) or plain `ComponentFn` functions — both are handled automatically.
77
+ */
78
+ export declare function defineRegistry<C extends Catalog>(catalog: C, options: DefineRegistryOptions<C>): ReturnType<typeof defineRegistryBase<C>>;
79
+ //# sourceMappingURL=define-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-registry.d.ts","sourceRoot":"","sources":["../src/define-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,EAAK,KAAK,SAAS,EAAE,MAAM,KAAK,CAAC;AACxC,OAAO,EACN,cAAc,IAAI,kBAAkB,EAEpC,KAAK,WAAW,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAyBzE,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,MAAM,sBAAsB,CAAC,CAAC,CAAC,IACpF,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GACjB,SAAS,CAAC;AAEb,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,OAAO,IAAI;KAC7C,CAAC,IAAI,MAAM,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,OAAO,IAAI,UAAU,CAChE,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAC5B,CAAC,CAAC,CAAC,GAAG;IACN,UAAU,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAC/C,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC/B,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAc1C"}
@@ -0,0 +1,21 @@
1
+ import { defineRegistry as e } from "./node_modules/@json-render/vue/dist/index.js";
2
+ import { h as t } from "vue";
3
+ //#region src/define-registry.ts
4
+ function n(e) {
5
+ return typeof e == "object" && !!e;
6
+ }
7
+ function r(e) {
8
+ return (n) => t(e, n);
9
+ }
10
+ function i(t, i) {
11
+ let a = {};
12
+ for (let [e, t] of Object.entries(i.components ?? {})) t !== void 0 && (a[e] = n(t) ? r(t) : t);
13
+ return e(t, {
14
+ ...i,
15
+ components: a
16
+ });
17
+ }
18
+ //#endregion
19
+ export { i as defineRegistry };
20
+
21
+ //# sourceMappingURL=define-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-registry.js","names":[],"sources":["../src/define-registry.ts"],"sourcesContent":["/**\n * `defineRegistry` wrapper for @xmachines/play-vue.\n *\n * Wraps `defineRegistry` from `@json-render/vue` with automatic SFC support.\n *\n * ## Vue-specific — React and Solid do not need this\n *\n * In React, `useContext()` works anywhere inside the component call tree.\n * In Solid, `useContext()` works inside reactive computations and component renders.\n * Neither has the strict \"synchronous setup() only\" constraint that Vue's `inject()`\n * imposes — so their `defineRegistry` implementations call `componentFn(ctx)` directly\n * with no wrapping needed.\n *\n * For the DOM renderer, there is no component system at all — just render functions\n * returning `HTMLElement` — so injection context is not applicable.\n *\n * Only Vue requires this adapter.\n *\n * ## Why this wrapper exists\n *\n * `@json-render/vue`'s `defineRegistry` calls each registered component as a plain\n * function: `componentFn(ctx)`. A `.vue` SFC (output of `defineComponent` or\n * `<script setup>`) is an **object**, not a function — calling it throws.\n *\n * More fundamentally, `defineRegistry` calls components inside its own render\n * function (the return value of `setup()`). Vue's `inject()` — and composables built\n * on it: `useStateBinding`, `useStateStore` — only work during synchronous `setup()`\n * execution, not inside render functions. Plain `.ts` `ComponentFn` files cannot\n * call any Vue composable for this reason.\n *\n * This wrapper auto-detects Vue SFCs in the `components` map and wraps them via\n * `h(SFC, ctx)`. The SFC renders as a child component with its own `setup()`,\n * giving full access to composables inside `<script setup>`.\n *\n * ## Usage\n *\n * Import `defineRegistry` from `@xmachines/play-vue` instead of `@json-render/vue`:\n *\n * ```ts\n * import { defineRegistry } from \"@xmachines/play-vue\";\n * // not: import { defineRegistry } from \"@json-render/vue\";\n *\n * import LoginSFC from \"./views/Login.vue\";\n * import DashboardSFC from \"./views/Dashboard.vue\";\n *\n * const { registry } = defineRegistry(catalog, {\n * components: {\n * Login: LoginSFC, // .vue SFC — auto-wrapped\n * Dashboard: DashboardSFC, // .vue SFC — auto-wrapped\n * },\n * });\n * ```\n *\n * Plain `ComponentFn` functions still work and are passed through unchanged.\n * Mixing SFCs and plain functions in the same registry is supported.\n */\n\nimport { h, type Component } from \"vue\";\nimport {\n\tdefineRegistry as defineRegistryBase,\n\ttype ComponentContext,\n\ttype ComponentFn,\n} from \"@json-render/vue\";\nimport type { Catalog, InferCatalogComponents } from \"@json-render/core\";\n\n/**\n * Detect whether a value is a Vue component object (SFC) rather than a plain\n * `ComponentFn` function.\n *\n * Vue SFCs produced by `defineComponent` or `<script setup>` compilation are\n * plain objects (not callable). A `ComponentFn` is always a plain function.\n */\nfunction isVueSFC(value: unknown): value is Component {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/**\n * Wrap a Vue SFC as a `ComponentFn` by rendering it via `h()`.\n *\n * The SFC receives the full `ComponentContext` as its props and renders in its\n * own child component `setup()`, where Vue composables work correctly.\n */\nfunction wrapSFC<C extends Catalog, K extends keyof InferCatalogComponents<C>>(\n\tcomponent: Component,\n): ComponentFn<C, K> {\n\treturn (ctx: ComponentContext<C, K>) => h(component, ctx);\n}\n\nexport type ComponentEntry<C extends Catalog, K extends keyof InferCatalogComponents<C>> =\n\t| ComponentFn<C, K>\n\t| Component;\n\nexport type ComponentsMap<C extends Catalog> = {\n\t[K in keyof InferCatalogComponents<C>]?: ComponentEntry<C, K>;\n};\n\nexport type DefineRegistryOptions<C extends Catalog> = Parameters<\n\ttypeof defineRegistryBase<C>\n>[1] & {\n\tcomponents?: ComponentsMap<C>;\n};\n\n/**\n * Create a component registry, automatically wrapping `.vue` SFCs so they work\n * correctly with `@json-render/vue`'s rendering pipeline.\n *\n * Drop-in replacement for `defineRegistry` from `@json-render/vue`. Import from\n * `@xmachines/play-vue` to get SFC support for free.\n *\n * @param catalog - The json-render catalog defining component prop shapes.\n * @param options - Registry options. `components` entries may be `.vue` SFCs\n * (objects) or plain `ComponentFn` functions — both are handled automatically.\n */\nexport function defineRegistry<C extends Catalog>(\n\tcatalog: C,\n\toptions: DefineRegistryOptions<C>,\n): ReturnType<typeof defineRegistryBase<C>> {\n\tconst wrappedComponents: Record<string, ComponentFn<C, keyof InferCatalogComponents<C>>> = {};\n\n\tfor (const [key, component] of Object.entries(options.components ?? {})) {\n\t\tif (component === undefined) continue;\n\t\twrappedComponents[key] = isVueSFC(component)\n\t\t\t? wrapSFC(component as Component)\n\t\t\t: (component as ComponentFn<C, keyof InferCatalogComponents<C>>);\n\t}\n\n\treturn defineRegistryBase(catalog, {\n\t\t...options,\n\t\tcomponents: wrappedComponents,\n\t});\n}\n"],"mappings":";;;AAwEA,SAAS,EAAS,GAAoC;AACrD,QAAO,OAAO,KAAU,cAAY;;AASrC,SAAS,EACR,GACoB;AACpB,SAAQ,MAAgC,EAAE,GAAW,EAAI;;AA4B1D,SAAgB,EACf,GACA,GAC2C;CAC3C,IAAM,IAAqF,EAAE;AAE7F,MAAK,IAAM,CAAC,GAAK,MAAc,OAAO,QAAQ,EAAQ,cAAc,EAAE,CAAC,CAClE,OAAc,KAAA,MAClB,EAAkB,KAAO,EAAS,EAAU,GACzC,EAAQ,EAAuB,GAC9B;AAGL,QAAO,EAAmB,GAAS;EAClC,GAAG;EACH,YAAY;EACZ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,22 @@
1
1
  /**
2
- * @xmachines/play-vue - Vue renderer for XMachines Play architecture
2
+ * @xmachines/play-vue - Vue 3 renderer for XMachines Play architecture
3
+ *
4
+ * Provides a thin Vue rendering layer that passively observes actor signals
5
+ * and renders UI components via @json-render/vue. Vue reactivity is only used
6
+ * to trigger re-renders — signals are the source of truth.
7
+ *
8
+ * Re-exports `defineRegistry` (SFC-aware — auto-wraps `.vue` SFCs via `h()`),
9
+ * `useStateBinding`, `ComponentFn`, and `ComponentContext` so consumers import
10
+ * everything from `@xmachines/play-vue` rather than `@json-render/vue` directly.
3
11
  *
4
12
  * @packageDocumentation
5
13
  */
6
14
  export { default as PlayRenderer } from "./PlayRenderer.vue";
15
+ export { useActor } from "./useActor.js";
16
+ export { defineRegistry } from "./define-registry.js";
17
+ export type { DefineRegistryOptions, ComponentsMap, ComponentEntry } from "./define-registry.js";
18
+ export { useStateBinding } from "@json-render/vue";
19
+ export type { ComponentFn, ComponentContext } from "@json-render/vue";
7
20
  export type { PlayRendererProps } from "./types.js";
21
+ export type { PlayActor } from "./useActor.js";
8
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,qBAAqB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEjG,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { default as a } from "./PlayRenderer.vue.js";
2
- export {
3
- a as PlayRenderer
4
- };
5
- //# sourceMappingURL=index.js.map
1
+ import { useStateBinding as e } from "./node_modules/@json-render/vue/dist/index.js";
2
+ import { useActor as t } from "./useActor.js";
3
+ import n from "./PlayRenderer.js";
4
+ import { defineRegistry as r } from "./define-registry.js";
5
+ export { n as PlayRenderer, r as defineRegistry, t as useActor, e as useStateBinding };