@xmachines/play-vue 1.0.0-beta.2 → 1.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -84
- package/dist/PlayRenderer.js +7 -0
- package/dist/PlayRenderer.js.map +1 -0
- package/dist/PlayRenderer.vue_vue_type_script_lang.js +57 -0
- package/dist/PlayRenderer.vue_vue_type_script_lang.js.map +1 -0
- package/dist/define-registry.d.ts +79 -0
- package/dist/define-registry.d.ts.map +1 -0
- package/dist/define-registry.js +21 -0
- package/dist/define-registry.js.map +1 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js +111 -0
- package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js.map +1 -0
- package/dist/node_modules/@json-render/core/dist/index.js +956 -0
- package/dist/node_modules/@json-render/core/dist/index.js.map +1 -0
- package/dist/node_modules/@json-render/core/dist/store-utils.js +1 -0
- package/dist/node_modules/@json-render/vue/dist/chunk-WIPZLAF7.js +57 -0
- package/dist/node_modules/@json-render/vue/dist/chunk-WIPZLAF7.js.map +1 -0
- package/dist/node_modules/@json-render/vue/dist/index.js +796 -0
- package/dist/node_modules/@json-render/vue/dist/index.js.map +1 -0
- package/dist/node_modules/@json-render/xstate/dist/index.js +20 -0
- package/dist/node_modules/@json-render/xstate/dist/index.js.map +1 -0
- package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js +227 -0
- package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/errors.js +25 -0
- package/dist/node_modules/zod/v4/classic/errors.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/iso.js +33 -0
- package/dist/node_modules/zod/v4/classic/iso.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/parse.js +8 -0
- package/dist/node_modules/zod/v4/classic/parse.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/schemas.js +362 -0
- package/dist/node_modules/zod/v4/classic/schemas.js.map +1 -0
- package/dist/node_modules/zod/v4/core/api.js +530 -0
- package/dist/node_modules/zod/v4/core/api.js.map +1 -0
- package/dist/node_modules/zod/v4/core/checks.js +285 -0
- package/dist/node_modules/zod/v4/core/checks.js.map +1 -0
- package/dist/node_modules/zod/v4/core/core.js +46 -0
- package/dist/node_modules/zod/v4/core/core.js.map +1 -0
- package/dist/node_modules/zod/v4/core/doc.js +25 -0
- package/dist/node_modules/zod/v4/core/doc.js.map +1 -0
- package/dist/node_modules/zod/v4/core/errors.js +43 -0
- package/dist/node_modules/zod/v4/core/errors.js.map +1 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.js +183 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +1 -0
- package/dist/node_modules/zod/v4/core/parse.js +70 -0
- package/dist/node_modules/zod/v4/core/parse.js.map +1 -0
- package/dist/node_modules/zod/v4/core/regexes.js +27 -0
- package/dist/node_modules/zod/v4/core/regexes.js.map +1 -0
- package/dist/node_modules/zod/v4/core/registries.js +42 -0
- package/dist/node_modules/zod/v4/core/registries.js.map +1 -0
- package/dist/node_modules/zod/v4/core/schemas.js +823 -0
- package/dist/node_modules/zod/v4/core/schemas.js.map +1 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.js +224 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.js.map +1 -0
- package/dist/node_modules/zod/v4/core/util.js +268 -0
- package/dist/node_modules/zod/v4/core/util.js.map +1 -0
- package/dist/node_modules/zod/v4/core/versions.js +10 -0
- package/dist/node_modules/zod/v4/core/versions.js.map +1 -0
- package/dist/types.d.ts +31 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/useActor.d.ts +35 -0
- package/dist/useActor.d.ts.map +1 -0
- package/dist/useActor.js +15 -0
- package/dist/useActor.js.map +1 -0
- package/package.json +31 -23
- package/dist/PlayRenderer.vue.js +0 -8
- package/dist/PlayRenderer.vue.js.map +0 -1
- package/dist/PlayRenderer.vue2.js +0 -48
- package/dist/PlayRenderer.vue2.js.map +0 -1
- package/dist/_virtual/_plugin-vue_export-helper.js +0 -10
- package/dist/_virtual/_plugin-vue_export-helper.js.map +0 -1
- package/dist/index.css +0 -1
- 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
|
|
3
|
+
**Vue 3 renderer for XMachines Play Architecture**
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
41
|
+
## Quick Start
|
|
19
42
|
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
actor.start();
|
|
55
|
+
export type Catalog = typeof catalog;
|
|
56
|
+
```
|
|
36
57
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
+
export const { registry } = defineRegistry(catalog, {
|
|
88
|
+
components: { Login: LoginSFC, Dashboard: DashboardSFC },
|
|
89
|
+
actions: { login: async () => {}, logout: async () => {} },
|
|
90
|
+
});
|
|
91
|
+
```
|
|
57
92
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
172
|
+
const createPlayer = definePlayer({ machine });
|
|
173
|
+
const actor = createPlayer();
|
|
174
|
+
actor.start();
|
|
175
|
+
</script>
|
|
65
176
|
|
|
66
|
-
|
|
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
|
-
|
|
186
|
+
## API Reference
|
|
69
187
|
|
|
70
|
-
|
|
71
|
-
- `send` function for sending events to actor
|
|
188
|
+
### `PlayRenderer`
|
|
72
189
|
|
|
73
|
-
|
|
190
|
+
Main Vue component. Subscribes to `actor.currentView` and renders the spec.
|
|
74
191
|
|
|
75
192
|
```vue
|
|
76
|
-
<
|
|
77
|
-
|
|
193
|
+
<PlayRenderer
|
|
194
|
+
:actor="actor"
|
|
195
|
+
:registry="registry"
|
|
196
|
+
:actions="{ login: 'auth.login' }"
|
|
197
|
+
:store="myStore"
|
|
198
|
+
/>
|
|
199
|
+
```
|
|
78
200
|
|
|
79
|
-
|
|
80
|
-
userId: string;
|
|
81
|
-
send: AbstractActor<any>["send"];
|
|
82
|
-
}>();
|
|
201
|
+
**`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
|
|
83
202
|
|
|
84
|
-
|
|
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
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
220
|
+
```vue
|
|
221
|
+
<PlayRenderer
|
|
222
|
+
:actor="actor"
|
|
223
|
+
:registry="registry"
|
|
224
|
+
:store="store"
|
|
225
|
+
:actions="{ login: 'auth.login' }"
|
|
226
|
+
/>
|
|
227
|
+
```
|
|
98
228
|
|
|
99
|
-
|
|
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
|
-
|
|
231
|
+
### `useActor`
|
|
106
232
|
|
|
107
|
-
|
|
233
|
+
Vue composable for accessing the actor from inside any component rendered by `PlayRenderer`.
|
|
108
234
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
238
|
+
// Inside any component rendered inside PlayRenderer:
|
|
239
|
+
const actor = useActor();
|
|
240
|
+
actor.send({ type: "auth.logout" });
|
|
241
|
+
```
|
|
116
242
|
|
|
117
|
-
|
|
243
|
+
Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
|
|
118
244
|
|
|
119
|
-
|
|
245
|
+
---
|
|
120
246
|
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
259
|
+
---
|
|
134
260
|
|
|
135
|
-
##
|
|
261
|
+
## Architecture Notes
|
|
136
262
|
|
|
137
|
-
|
|
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 @@
|
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
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 };
|