@xmachines/play-vue 1.0.0-beta.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +135 -208
- package/dist/actor-provider-context.d.ts +40 -0
- package/dist/actor-provider-context.d.ts.map +1 -0
- package/dist/define-registry.d.ts +79 -0
- package/dist/define-registry.d.ts.map +1 -0
- package/dist/index.d.ts +21 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +19 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/useActor.d.ts +50 -0
- package/dist/useActor.d.ts.map +1 -0
- package/package.json +38 -16
- package/dist/PlayRenderer.js +0 -9
- package/dist/PlayRenderer.js.map +0 -1
- package/dist/PlayRenderer.vue_vue_type_script_lang.js +0 -35
- package/dist/PlayRenderer.vue_vue_type_script_lang.js.map +0 -1
- package/dist/_virtual/_plugin-vue_export-helper.js +0 -8
- package/dist/index.css +0 -2
- package/dist/index.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikael Karon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,280 +1,207 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@xmachines/play-vue`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Vue 3 renderer for the XMachines Play Architecture — passively observes actor signals and renders UI via `@xmachines/json-render-vue`.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Part of the [XMachines Play monorepo](../../README.md).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://www.npmjs.com/package/@xmachines/play-vue)
|
|
9
|
+
|
|
10
|
+
---
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
## Overview
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
`@xmachines/play-vue` is the Vue 3 rendering layer for XMachines Play. It bridges TC39 Signals (actor state) to Vue reactivity and drives component rendering through `@xmachines/json-render-vue`.
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
- **Passive Infrastructure (INV-04):** Components observe signals, send events to actor
|
|
16
|
+
**Architecture invariants this package upholds:**
|
|
15
17
|
|
|
16
|
-
**
|
|
18
|
+
- **Passive Infrastructure** — Components observe actor signals; they never decide state transitions.
|
|
19
|
+
- **Signal-Only Reactivity** — TC39 Signals are the source of truth; Vue reactivity is used only to trigger re-renders.
|
|
20
|
+
- **Actor Authority** — The actor controls view selection; the renderer reflects it.
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
---
|
|
19
23
|
|
|
20
24
|
## Installation
|
|
21
25
|
|
|
22
26
|
```bash
|
|
23
|
-
|
|
24
|
-
npm install @xmachines/play-vue
|
|
27
|
+
pnpm add @xmachines/play-vue
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- `PlayRenderer` (Vue component)
|
|
30
|
-
- `PlayRendererProps` (TypeScript interface)
|
|
30
|
+
**Peer dependencies** (install alongside):
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
```bash
|
|
33
|
+
pnpm add vue@^3.5.0 xstate@^5.31.0 @xstate/store@^3.17.0 @xmachines/json-render-vue@^0.18.0 @xmachines/json-render-core@^0.18.0 @xmachines/json-render-xstate@^0.18.0
|
|
34
|
+
```
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
---
|
|
35
37
|
|
|
36
38
|
## Quick Start
|
|
37
39
|
|
|
38
40
|
```vue
|
|
39
41
|
<!-- App.vue -->
|
|
42
|
+
<template>
|
|
43
|
+
<PlayUIProvider :actor="actor" :registryResult="registryResult">
|
|
44
|
+
<PlayRenderer />
|
|
45
|
+
</PlayUIProvider>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
40
48
|
<script setup lang="ts">
|
|
49
|
+
import { defineRegistry, PlayUIProvider, PlayRenderer } from "@xmachines/play-vue";
|
|
41
50
|
import { definePlayer } from "@xmachines/play-xstate";
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import
|
|
45
|
-
import
|
|
46
|
-
import Dashboard from "./components/Dashboard.vue";
|
|
47
|
-
|
|
48
|
-
// 1. Define catalog (business logic layer)
|
|
49
|
-
const catalog = defineCatalog({
|
|
50
|
-
LoginForm: z.object({ error: z.string().optional() }),
|
|
51
|
-
Dashboard: z.object({
|
|
52
|
-
userId: z.string(),
|
|
53
|
-
username: z.string(),
|
|
54
|
-
}),
|
|
55
|
-
});
|
|
51
|
+
import { myMachine } from "./machine.js";
|
|
52
|
+
import { myCatalog } from "./catalog.js";
|
|
53
|
+
import HomeSFC from "./views/Home.vue";
|
|
54
|
+
import LoginSFC from "./views/Login.vue";
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
const components = {
|
|
59
|
-
LoginForm,
|
|
60
|
-
Dashboard,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// 3. Create player actor (business logic runtime)
|
|
64
|
-
const createPlayer = definePlayer({ machine: authMachine, catalog });
|
|
56
|
+
const createPlayer = definePlayer({ machine: myMachine });
|
|
65
57
|
const actor = createPlayer();
|
|
66
58
|
actor.start();
|
|
67
|
-
</script>
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
const registryResult = defineRegistry(myCatalog, {
|
|
61
|
+
components: {
|
|
62
|
+
Home: HomeSFC, // .vue SFCs are auto-wrapped
|
|
63
|
+
Login: LoginSFC,
|
|
64
|
+
},
|
|
65
|
+
actions: {
|
|
66
|
+
login: async (args) => actor.send({ type: "auth.login", ...args }),
|
|
67
|
+
logout: async () => actor.send({ type: "auth.logout" }),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
</script>
|
|
77
71
|
```
|
|
78
72
|
|
|
79
|
-
|
|
73
|
+
---
|
|
80
74
|
|
|
81
|
-
|
|
75
|
+
## API Summary
|
|
82
76
|
|
|
83
|
-
|
|
77
|
+
### Components
|
|
84
78
|
|
|
85
|
-
|
|
86
|
-
interface PlayRendererProps {
|
|
87
|
-
actor: AbstractActor<any>;
|
|
88
|
-
components: Record<string, Component>;
|
|
89
|
-
}
|
|
90
|
-
```
|
|
79
|
+
#### `<PlayUIProvider>`
|
|
91
80
|
|
|
92
|
-
**
|
|
81
|
+
Batteries-included composite provider. Wraps `<ActorProvider>` and `JSONUIProvider` in one component. **Recommended for most apps.**
|
|
93
82
|
|
|
94
|
-
|
|
95
|
-
|
|
83
|
+
| Prop | Type | Required | Description |
|
|
84
|
+
| --------------------- | -------------------------- | -------- | ------------------------------------------- |
|
|
85
|
+
| `actor` | `AbstractActor & Viewable` | ✅ | The XMachines actor instance |
|
|
86
|
+
| `registryResult` | `DefineRegistryResult` | ✅ | Result of `defineRegistry()` |
|
|
87
|
+
| `store` | `StateStore` | — | External controlled state store (optional) |
|
|
88
|
+
| `onRenderError` | `RenderErrorHandler` | — | Error handler for render failures |
|
|
89
|
+
| `navigate` | `(path: string) => void` | — | Link navigation function |
|
|
90
|
+
| `validationFunctions` | `Record<string, Function>` | — | Custom validation functions |
|
|
91
|
+
| `functions` | `Record<string, Function>` | — | Named functions for `$computed` expressions |
|
|
96
92
|
|
|
97
|
-
**Slots:**
|
|
93
|
+
**Slots:** `default` (rendered content), `fallback` (shown while actor view is `null`)
|
|
98
94
|
|
|
99
|
-
|
|
95
|
+
#### `<PlayRenderer>`
|
|
100
96
|
|
|
101
|
-
|
|
97
|
+
Zero-prop leaf component. Reads the current `spec` and `registry` from the nearest `<ActorProvider>` or `<PlayUIProvider>` context and renders via `<Renderer>`. Must be placed inside one of those providers.
|
|
102
98
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
```vue
|
|
100
|
+
<PlayUIProvider :actor="actor" :registryResult="registryResult">
|
|
101
|
+
<PlayRenderer />
|
|
102
|
+
</PlayUIProvider>
|
|
103
|
+
```
|
|
106
104
|
|
|
107
|
-
|
|
105
|
+
#### `<ActorProvider>`
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
<script setup lang="ts">
|
|
111
|
-
import type { AbstractActor } from "@xmachines/play-actor";
|
|
112
|
-
import { ref } from "vue";
|
|
113
|
-
|
|
114
|
-
const props = defineProps<{
|
|
115
|
-
error?: string;
|
|
116
|
-
send: AbstractActor<any>["send"];
|
|
117
|
-
}>();
|
|
118
|
-
|
|
119
|
-
const username = ref("");
|
|
120
|
-
|
|
121
|
-
function handleSubmit() {
|
|
122
|
-
props.send({
|
|
123
|
-
type: "auth.login",
|
|
124
|
-
username: username.value,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
</script>
|
|
107
|
+
Low-level escape hatch for custom provider composition. Owns the full actor lifecycle — signal subscription, per-view state store, handler resolution, and Vue context provision. Use `<PlayUIProvider>` unless you need fine-grained control.
|
|
128
108
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
</template>
|
|
136
|
-
```
|
|
109
|
+
| Prop | Type | Required | Description |
|
|
110
|
+
| ---------------- | -------------------------- | -------- | ------------------------------- |
|
|
111
|
+
| `actor` | `AbstractActor & Viewable` | ✅ | The XMachines actor instance |
|
|
112
|
+
| `registryResult` | `DefineRegistryResult` | ✅ | Result of `defineRegistry()` |
|
|
113
|
+
| `store` | `StateStore` | — | External controlled state store |
|
|
114
|
+
| `onRenderError` | `RenderErrorHandler` | — | Override render error handler |
|
|
137
115
|
|
|
138
|
-
|
|
116
|
+
---
|
|
139
117
|
|
|
140
|
-
###
|
|
118
|
+
### Functions
|
|
141
119
|
|
|
142
|
-
|
|
143
|
-
<!-- App.vue -->
|
|
144
|
-
<script setup lang="ts">
|
|
145
|
-
import { PlayVueRouterProvider } from "@xmachines/play-vue-router";
|
|
146
|
-
import { PlayRenderer } from "@xmachines/play-vue";
|
|
147
|
-
import { provide } from "vue";
|
|
148
|
-
import Header from "./components/Header.vue";
|
|
149
|
-
import Footer from "./components/Footer.vue";
|
|
150
|
-
|
|
151
|
-
// Provide actor to nested components like Header
|
|
152
|
-
provide("actor", actor);
|
|
153
|
-
</script>
|
|
120
|
+
#### `defineRegistry(catalog, options)`
|
|
154
121
|
|
|
155
|
-
|
|
156
|
-
<PlayVueRouterProvider :actor="actor" :router="router" :routeMap="routeMap">
|
|
157
|
-
<template #default="{ currentActor, currentRouter }">
|
|
158
|
-
<div>
|
|
159
|
-
<Header />
|
|
160
|
-
<PlayRenderer :actor="currentActor" :components="components" />
|
|
161
|
-
<Footer />
|
|
162
|
-
</div>
|
|
163
|
-
</template>
|
|
164
|
-
</PlayVueRouterProvider>
|
|
165
|
-
</template>
|
|
166
|
-
```
|
|
122
|
+
Drop-in replacement for `defineRegistry` from `@xmachines/json-render-vue`. **Always import from `@xmachines/play-vue`** rather than `@xmachines/json-render-vue` when working with Vue SFCs — this wrapper automatically detects `.vue` SFCs in the `components` map and wraps them via `h()` so Vue composables (including `inject`-based ones) work correctly inside `<script setup>`.
|
|
167
123
|
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
import
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
pending = false;
|
|
186
|
-
for (const s of watcher.getPending()) s.get();
|
|
187
|
-
route.value = actor.currentRoute.get();
|
|
188
|
-
watcher.watch(actor.currentRoute);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
route.value = actor.currentRoute.get();
|
|
193
|
-
watcher.watch(actor.currentRoute);
|
|
124
|
+
```typescript
|
|
125
|
+
import { defineRegistry } from "@xmachines/play-vue";
|
|
126
|
+
// NOT: import { defineRegistry } from "@xmachines/json-render-vue"
|
|
127
|
+
|
|
128
|
+
import LoginSFC from "./views/Login.vue";
|
|
129
|
+
import DashboardSFC from "./views/Dashboard.vue";
|
|
130
|
+
|
|
131
|
+
const registryResult = defineRegistry(catalog, {
|
|
132
|
+
components: {
|
|
133
|
+
Login: LoginSFC, // .vue SFC — auto-wrapped via h()
|
|
134
|
+
Dashboard: DashboardSFC,
|
|
135
|
+
},
|
|
136
|
+
actions: {
|
|
137
|
+
login: async (args, setState, getState) => {
|
|
138
|
+
/* ... */
|
|
139
|
+
},
|
|
140
|
+
},
|
|
194
141
|
});
|
|
142
|
+
```
|
|
195
143
|
|
|
196
|
-
|
|
197
|
-
if (watcher) watcher.unwatch(actor.currentRoute);
|
|
198
|
-
});
|
|
199
|
-
</script>
|
|
144
|
+
Plain `ComponentFn` functions (non-SFC) also work and are passed through unchanged. Mixing SFCs and plain functions in the same registry is supported.
|
|
200
145
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
146
|
+
#### `useActor()`
|
|
147
|
+
|
|
148
|
+
Vue composable for accessing the raw actor inside a `PlayRenderer` tree. Avoids prop drilling for deeply nested components.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { useActor } from "@xmachines/play-vue";
|
|
152
|
+
|
|
153
|
+
// Inside a component rendered by PlayRenderer:
|
|
154
|
+
const actor = useActor();
|
|
155
|
+
actor.send({ type: "SUBMIT" });
|
|
206
156
|
```
|
|
207
157
|
|
|
208
|
-
|
|
158
|
+
Throws if called outside an `<ActorProvider>` or `<PlayUIProvider>` tree.
|
|
209
159
|
|
|
210
|
-
|
|
160
|
+
#### `getPlayViewContext()`
|
|
211
161
|
|
|
212
|
-
|
|
213
|
-
- No ref/reactive for business state
|
|
214
|
-
- No watch/watchEffect for business side effects
|
|
215
|
-
- Vue only triggers renders, doesn't control state
|
|
162
|
+
Access the current `ViewContextValue` — `{ spec, handlers, registry, store }` — from inside an `<ActorProvider>` tree.
|
|
216
163
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
- `actor.currentRoute.get()` provides navigation state
|
|
220
|
-
- Components observe signals via explicit watcher patterns
|
|
164
|
+
```typescript
|
|
165
|
+
import { getPlayViewContext } from "@xmachines/play-vue";
|
|
221
166
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
167
|
+
// Inside setup() of a component within an ActorProvider tree:
|
|
168
|
+
const view = getPlayViewContext();
|
|
169
|
+
// view.spec, view.handlers, view.registry, view.store
|
|
170
|
+
```
|
|
226
171
|
|
|
227
|
-
|
|
228
|
-
- `Signal.subtle.Watcher` coalesces rapid signal changes
|
|
229
|
-
- Prevents Vue thrashing from multiple signal updates
|
|
230
|
-
- Single Vue render per microtask batch
|
|
172
|
+
---
|
|
231
173
|
|
|
232
|
-
|
|
233
|
-
- Component teardown calls watcher `unwatch` in `onUnmounted`
|
|
234
|
-
- Do not rely on GC-only cleanup
|
|
174
|
+
### Re-exported from `@xmachines/json-render-vue`
|
|
235
175
|
|
|
236
|
-
|
|
176
|
+
The following are re-exported so consumers import everything from `@xmachines/play-vue`:
|
|
237
177
|
|
|
238
|
-
|
|
239
|
-
- Enables composition with navigation, headers, footers
|
|
240
|
-
- Supports multiple renderers in same app
|
|
178
|
+
**Components:** `JSONUIProvider`, `StateProvider`, `ActionProvider`, `VisibilityProvider`, `ValidationProvider`, `Renderer`
|
|
241
179
|
|
|
242
|
-
**
|
|
180
|
+
**Composables:** `useBoundProp`
|
|
243
181
|
|
|
244
|
-
|
|
245
|
-
- **Passive Infrastructure (INV-04):** Components reflect, never decide
|
|
182
|
+
**Types:** `JSONUIProviderProps`, `StateProviderProps`, `ActionProviderProps`, `ValidationProviderProps`, `RendererProps`, `ComponentFn`, `ComponentContext`, `DefineRegistryResult`
|
|
246
183
|
|
|
247
|
-
|
|
184
|
+
---
|
|
248
185
|
|
|
249
|
-
|
|
186
|
+
## Testing
|
|
250
187
|
|
|
251
|
-
|
|
252
|
-
2. Schedule work with `queueMicrotask`
|
|
253
|
-
3. Drain `watcher.getPending()`
|
|
254
|
-
4. Read actor signals and update Vue-local ref state
|
|
255
|
-
5. Re-arm with `watch(...)` or `watch()`
|
|
188
|
+
Run tests for this package in isolation:
|
|
256
189
|
|
|
257
|
-
|
|
190
|
+
```bash
|
|
191
|
+
# From the monorepo root
|
|
192
|
+
pnpm --filter @xmachines/play-vue test
|
|
258
193
|
|
|
259
|
-
|
|
194
|
+
# Watch mode
|
|
195
|
+
pnpm --filter @xmachines/play-vue run test:watch
|
|
260
196
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
- **Performance:** Microtask batching reduces unnecessary renders
|
|
265
|
-
- **Composability:** Renderer prop enables complex layouts
|
|
197
|
+
# With coverage (80% threshold enforced on lines, functions, branches, statements)
|
|
198
|
+
pnpm exec vitest run --coverage --config packages/play-vue/vitest.config.ts
|
|
199
|
+
```
|
|
266
200
|
|
|
267
|
-
|
|
201
|
+
Tests use [Vitest](https://vitest.dev/) with `jsdom` environment and `@vue/test-utils` for component mounting.
|
|
268
202
|
|
|
269
|
-
|
|
270
|
-
- **[@xmachines/play-catalog](../play-catalog)** - UI schema validation
|
|
271
|
-
- **[@xmachines/play-vue-router](../play-vue-router)** - Vue Router integration
|
|
272
|
-
- **[@xmachines/play-actor](../play-actor)** - Actor base
|
|
273
|
-
- **[@xmachines/play-signals](../play-signals)** - TC39 Signals primitives
|
|
203
|
+
---
|
|
274
204
|
|
|
275
205
|
## License
|
|
276
206
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
This work is licensed under the terms of the MIT license.
|
|
280
|
-
For a copy, see <https://opensource.org/licenses/MIT>.
|
|
207
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViewContextValue and getPlayViewContext — shared context types for Vue provider architecture.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from ActorProvider.vue so TypeScript can re-export these types from index.ts.
|
|
5
|
+
* (The vue-shim.d.ts only declares a default export from *.vue files, so named exports
|
|
6
|
+
* from .vue SFCs are not visible to TypeScript's re-export resolution.)
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import type { InjectionKey } from "vue";
|
|
11
|
+
import type { ComponentRegistry } from "@xmachines/json-render-vue";
|
|
12
|
+
import type { BaseViewContextValue } from "@xmachines/play-actor";
|
|
13
|
+
/**
|
|
14
|
+
* Context value provided by ActorProvider and consumed by PlayRenderer (zero-prop leaf).
|
|
15
|
+
* Accessible via getPlayViewContext() inside any ActorProvider/PlayUIProvider tree.
|
|
16
|
+
*/
|
|
17
|
+
export interface ViewContextValue extends BaseViewContextValue<ComponentRegistry> {
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Injection key for the ViewContextValue. Exported so ActorProvider.vue can use it
|
|
21
|
+
* as the canonical key without re-declaring.
|
|
22
|
+
*
|
|
23
|
+
* @internal Use getPlayViewContext() as the public API; do not inject ViewKey directly.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ViewKey: InjectionKey<ViewContextValue>;
|
|
26
|
+
/**
|
|
27
|
+
* Access the current ViewContextValue from inside an ActorProvider tree.
|
|
28
|
+
*
|
|
29
|
+
* @throws {Error} If called outside an <ActorProvider> (or <PlayUIProvider>) tree
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { getPlayViewContext } from "@xmachines/play-vue";
|
|
34
|
+
*
|
|
35
|
+
* const view = getPlayViewContext();
|
|
36
|
+
* // view.spec, view.handlers, view.registry
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function getPlayViewContext(): ViewContextValue;
|
|
40
|
+
//# sourceMappingURL=actor-provider-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actor-provider-context.d.ts","sourceRoot":"","sources":["../src/actor-provider-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE;;;GAGG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;CAAG;AAEpF;;;;;GAKG;AACH,eAAO,MAAM,OAAO,EAAE,YAAY,CAAC,gBAAgB,CAA4B,CAAC;AAEhF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,IAAI,gBAAgB,CAErD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defineRegistry` wrapper for @xmachines/play-vue.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `defineRegistry` from `@xmachines/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
|
+
* `@xmachines/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 `@xmachines/json-render-vue`:
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { defineRegistry } from "@xmachines/play-vue";
|
|
41
|
+
* // not: import { defineRegistry } from "@xmachines/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 "@xmachines/json-render-vue";
|
|
59
|
+
import type { Catalog, InferCatalogComponents } from "@xmachines/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> = Omit<Parameters<typeof defineRegistryBase<C>>[1], "components"> & {
|
|
65
|
+
components?: ComponentsMap<C>;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Create a component registry, automatically wrapping `.vue` SFCs so they work
|
|
69
|
+
* correctly with `@xmachines/json-render-vue`'s rendering pipeline.
|
|
70
|
+
*
|
|
71
|
+
* Drop-in replacement for `defineRegistry` from `@xmachines/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,EAEhB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAwCnF,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,IAAI,CAC1D,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAC3C,YAAY,CACZ,GAAG;IACH,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,CAa1C"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
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 @xmachines/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
|
+
* `useBoundProp`, `ComponentFn`, `ComponentContext`, and all json-render providers
|
|
10
|
+
* so consumers import everything from `@xmachines/play-vue` rather than
|
|
11
|
+
* `@xmachines/json-render-vue` directly.
|
|
3
12
|
*
|
|
4
13
|
* @packageDocumentation
|
|
5
14
|
*/
|
|
6
15
|
export { default as PlayRenderer } from "./PlayRenderer.vue";
|
|
7
|
-
export
|
|
16
|
+
export { default as ActorProvider } from "./ActorProvider.vue";
|
|
17
|
+
export { default as PlayUIProvider } from "./PlayUIProvider.vue";
|
|
18
|
+
export type { ActorProviderProps, PlayUIProviderProps, RenderErrorHandler, VisibilityProviderProps, } from "./types.js";
|
|
19
|
+
export { getPlayViewContext } from "./actor-provider-context.js";
|
|
20
|
+
export type { ViewContextValue } from "./actor-provider-context.js";
|
|
21
|
+
export { useActor } from "./useActor.js";
|
|
22
|
+
export type { AnyPlayActor } from "./useActor.js";
|
|
23
|
+
export { defineRegistry } from "./define-registry.js";
|
|
24
|
+
export type { DefineRegistryOptions, ComponentsMap, ComponentEntry } from "./define-registry.js";
|
|
25
|
+
export { JSONUIProvider, StateProvider, ActionProvider, VisibilityProvider, ValidationProvider, Renderer, useBoundProp, } from "@xmachines/json-render-vue";
|
|
26
|
+
export type { JSONUIProviderProps, StateProviderProps, ActionProviderProps, ValidationProviderProps, RendererProps, ComponentFn, ComponentContext, DefineRegistryResult, } from "@xmachines/json-render-vue";
|
|
8
27
|
//# 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;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGjE,YAAY,EACX,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,qBAAqB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGjG,OAAO,EACN,cAAc,EACd,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,QAAQ,EACR,YAAY,GACZ,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACX,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,oBAAoB,GACpB,MAAM,4BAA4B,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -3,19 +3,26 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
8
|
-
import type { AnyActorLogic } from "xstate";
|
|
6
|
+
import type { DefineRegistryResult } from "@xmachines/json-render-vue";
|
|
7
|
+
import type { BaseActorProviderProps } from "@xmachines/play-actor";
|
|
9
8
|
/**
|
|
10
|
-
* Props for
|
|
11
|
-
*
|
|
12
|
-
* @property actor - Actor instance with currentView signal (requires Viewable capability)
|
|
13
|
-
* @property components - Map of component names to Vue components
|
|
9
|
+
* Props for the ActorProvider component.
|
|
10
|
+
* Extracted to types.ts so TypeScript can re-export without the vue-shim limitation.
|
|
14
11
|
*/
|
|
15
|
-
export interface
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
export interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Props for PlayUIProvider — all ActorProvider props plus JSONUIProvider's own props.
|
|
16
|
+
*/
|
|
17
|
+
export type { RenderErrorHandler } from "@xmachines/json-render-vue";
|
|
18
|
+
export interface VisibilityProviderProps {
|
|
19
|
+
}
|
|
20
|
+
export interface PlayUIProviderProps extends ActorProviderProps {
|
|
21
|
+
/** Navigate function forwarded to JSONUIProvider for link resolution */
|
|
22
|
+
navigate?: (path: string) => void;
|
|
23
|
+
/** Validation functions forwarded to JSONUIProvider */
|
|
24
|
+
validationFunctions?: Record<string, (value: unknown, args?: Record<string, unknown>) => boolean>;
|
|
25
|
+
/** Named functions for $computed expressions in props */
|
|
26
|
+
functions?: Record<string, (args?: Record<string, unknown>, state?: Record<string, unknown>) => unknown>;
|
|
20
27
|
}
|
|
21
28
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEpE;;;GAGG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB,CAAC,oBAAoB,CAAC;CAAG;AAE3F;;GAEG;AACH,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIrE,MAAM,WAAW,uBAAuB;CAAG;AAE3C,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC9D,wEAAwE;IACxE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,MAAM,CAC3B,MAAM,EACN,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAC3D,CAAC;IACF,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CACjB,MAAM,EACN,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAC5E,CAAC;CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useActor — Vue composable for accessing the provided actor inside a PlayRenderer tree.
|
|
3
|
+
*
|
|
4
|
+
* Components rendered inside PlayRenderer can call useActor() to reach the actor
|
|
5
|
+
* instance without prop drilling.
|
|
6
|
+
*
|
|
7
|
+
* What is returned is a **swap-following proxy**, not the raw actor object:
|
|
8
|
+
* - It is a stable reference for the lifetime of the injection, yet forwards every
|
|
9
|
+
* operation (property reads/writes, `in`, `Object.keys` / spread, `instanceof`)
|
|
10
|
+
* to whichever actor is CURRENTLY provided — so it transparently follows a
|
|
11
|
+
* `props.actor` swap on `<ActorProvider>` without consumers re-injecting.
|
|
12
|
+
* - Method identity is stable per underlying method: `actor.send === actor.send`
|
|
13
|
+
* across repeated reads (allocation-free), and rebinds only when the underlying
|
|
14
|
+
* method changes or the actor is swapped.
|
|
15
|
+
* - It is NOT `===` the `actor` prop passed to `<ActorProvider>` (it is a distinct
|
|
16
|
+
* Proxy object). Any consumer keyed on actor identity — most notably a router
|
|
17
|
+
* bridge extending `RouterBridgeBase`, whose one-bridge-per-actor guard keys a
|
|
18
|
+
* WeakMap on the actor instance — must be given the actor prop itself, not the
|
|
19
|
+
* value returned by `useActor()`.
|
|
20
|
+
*
|
|
21
|
+
* @throws {Error} If called outside a PlayRenderer tree
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { useActor } from "@xmachines/play-vue";
|
|
26
|
+
*
|
|
27
|
+
* const actor = useActor();
|
|
28
|
+
* actor.send({ type: "SUBMIT" });
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
*/
|
|
33
|
+
import type { InjectionKey } from "vue";
|
|
34
|
+
import type { AbstractActor } from "@xmachines/play-actor";
|
|
35
|
+
import type { AnyActorLogic } from "xstate";
|
|
36
|
+
/** Bare actor type accepted by Vue context providers. For the full routing + view shape, use `PlayActor` from `@xmachines/play-router`. */
|
|
37
|
+
export type AnyPlayActor = AbstractActor<AnyActorLogic>;
|
|
38
|
+
export declare const ActorKey: InjectionKey<AnyPlayActor>;
|
|
39
|
+
/**
|
|
40
|
+
* Provide the actor to all descendant components via Vue's inject/provide mechanism.
|
|
41
|
+
*
|
|
42
|
+
* Called inside `PlayRenderer.vue`'s `setup()` to make the actor available to any
|
|
43
|
+
* child component that calls `useActor()`. Not typically needed outside framework
|
|
44
|
+
* internals unless building a custom renderer wrapper.
|
|
45
|
+
*
|
|
46
|
+
* @param actor - The actor instance to inject into the component tree.
|
|
47
|
+
*/
|
|
48
|
+
export declare function provideActor(actor: AnyPlayActor): void;
|
|
49
|
+
export declare function useActor(): AnyPlayActor;
|
|
50
|
+
//# sourceMappingURL=useActor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useActor.d.ts","sourceRoot":"","sources":["../src/useActor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AAExC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE5C,2IAA2I;AAC3I,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;AAExD,eAAO,MAAM,QAAQ,EAAE,YAAY,CAAC,YAAY,CAA6B,CAAC;AAE9E;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAEtD;AAED,wBAAgB,QAAQ,IAAI,YAAY,CAEvC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-vue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Vue renderer for XMachines Play architecture",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
"xmachines"
|
|
11
11
|
],
|
|
12
12
|
"license": "MIT",
|
|
13
|
+
"author": "Mikael Karon <mikael@karon.se>",
|
|
13
14
|
"repository": {
|
|
14
15
|
"type": "git",
|
|
15
|
-
"url": "git@gitlab.com
|
|
16
|
+
"url": "git+ssh://git@gitlab.com/xmachin-es/xmachines-js.git",
|
|
17
|
+
"directory": "packages/play-vue"
|
|
16
18
|
},
|
|
17
19
|
"files": [
|
|
18
20
|
"dist",
|
|
@@ -20,6 +22,7 @@
|
|
|
20
22
|
"LICENSE"
|
|
21
23
|
],
|
|
22
24
|
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
23
26
|
"main": "./dist/index.js",
|
|
24
27
|
"types": "./dist/index.d.ts",
|
|
25
28
|
"exports": {
|
|
@@ -33,27 +36,46 @@
|
|
|
33
36
|
},
|
|
34
37
|
"scripts": {
|
|
35
38
|
"build": "vite build && tsc --build --force",
|
|
36
|
-
"clean": "rm -rf dist node_modules/.
|
|
37
|
-
"
|
|
39
|
+
"clean": "rm -rf dist *.tsbuildinfo coverage .vitest-attachments test/browser/__screenshots__ node_modules/.svelte2tsx-* node_modules/.vite*",
|
|
40
|
+
"lint": "oxlint .",
|
|
41
|
+
"format": "oxfmt .",
|
|
38
42
|
"test": "vitest",
|
|
39
|
-
"test:watch": "vitest"
|
|
40
|
-
"prepublishOnly": "npm run build"
|
|
43
|
+
"test:watch": "vitest"
|
|
41
44
|
},
|
|
42
45
|
"dependencies": {
|
|
43
|
-
"@xmachines/play
|
|
44
|
-
"@xmachines/play-
|
|
45
|
-
"@xmachines/play-signals": "1.
|
|
46
|
+
"@xmachines/play": "1.1.0",
|
|
47
|
+
"@xmachines/play-actor": "1.1.0",
|
|
48
|
+
"@xmachines/play-signals": "1.1.0"
|
|
46
49
|
},
|
|
47
50
|
"devDependencies": {
|
|
48
|
-
"@
|
|
51
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
52
|
+
"@types/node": "^26.2.0",
|
|
49
53
|
"@vitejs/plugin-vue": "^6.0.5",
|
|
50
|
-
"@
|
|
51
|
-
"@
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
54
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
55
|
+
"@vue/test-utils": "^2.4.9",
|
|
56
|
+
"@xmachines/json-render-core": "^0.19.0-xm.2",
|
|
57
|
+
"@xmachines/json-render-vue": "^0.19.0-xm.2",
|
|
58
|
+
"@xmachines/json-render-xstate": "^0.19.0-xm.2",
|
|
59
|
+
"@xstate/store": "^3.17.0",
|
|
60
|
+
"oxfmt": "^0.64.0",
|
|
61
|
+
"oxlint": "^1.79.0",
|
|
62
|
+
"typescript": "^5.9.3 || ^6.0.3",
|
|
63
|
+
"vite": "^8.0.10",
|
|
64
|
+
"vite-plugin-dts": "^4.5.4",
|
|
65
|
+
"vitest": "^4.1.11",
|
|
66
|
+
"vue": "^3.5.33",
|
|
67
|
+
"xstate": "^5.31.0",
|
|
68
|
+
"zod": "^4.4.1"
|
|
55
69
|
},
|
|
56
70
|
"peerDependencies": {
|
|
57
|
-
"
|
|
71
|
+
"@xmachines/json-render-core": "^0.19.0-xm.2",
|
|
72
|
+
"@xmachines/json-render-vue": "^0.19.0-xm.2",
|
|
73
|
+
"@xmachines/json-render-xstate": "^0.19.0-xm.2",
|
|
74
|
+
"@xstate/store": "^3.17.0",
|
|
75
|
+
"vue": "^3.5.0",
|
|
76
|
+
"xstate": "^5.31.0"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=22.0.0"
|
|
58
80
|
}
|
|
59
81
|
}
|
package/dist/PlayRenderer.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import e from "./PlayRenderer.vue_vue_type_script_lang.js";
|
|
2
|
-
/* empty css */
|
|
3
|
-
import t from "./_virtual/_plugin-vue_export-helper.js";
|
|
4
|
-
//#region src/PlayRenderer.vue
|
|
5
|
-
var n = /* @__PURE__ */ t(e, [["__scopeId", "data-v-275eda6e"]]);
|
|
6
|
-
//#endregion
|
|
7
|
-
export { n as default };
|
|
8
|
-
|
|
9
|
-
//# sourceMappingURL=PlayRenderer.js.map
|
package/dist/PlayRenderer.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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 * - Dynamically renders catalog components based on view.component string\n * - Forwards user events to actor via actor.send()\n * - Vue ref only for triggering renders, NOT business logic\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, computed, toRaw, onUnmounted, h, type PropType } from \"vue\";\nimport { Signal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { AbstractActor, Viewable } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type { Component } from \"vue\";\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\tcomponents: {\n\t\t\ttype: Object as PropType<Record<string, Component>>,\n\t\t\trequired: true,\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// 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<{ component: string; props: Record<string, unknown> } | null>(initialView);\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity\n\t\t// Created immediately (not in onMounted) so signal changes during the\n\t\t// synchronous mount phase are captured.\n\t\t//\n\t\t// The notify callback runs synchronously during the signal graph's\n\t\t// dirty-propagation phase. NEVER read (.get()) or write (.set()) signals\n\t\t// inside it. Only schedule a microtask to do the actual work.\n\t\tconst watcher = new Signal.subtle.Watcher(() => {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\t// Step 1: Acknowledge notification (clears watcher's dirty flags)\n\t\t\t\twatcher.getPending();\n\n\t\t\t\t// Step 2: Read signal value (safe — notification phase is over)\n\t\t\t\tview.value = actor.currentView.get();\n\n\t\t\t\t// Step 3: Re-watch for next notification (one-shot pattern)\n\t\t\t\t// TC39 watchers stop notifying after first notification until re-armed\n\t\t\t\twatcher.watch(actor.currentView);\n\t\t\t});\n\t\t});\n\n\t\t// Start watching the signal\n\t\twatcher.watch(actor.currentView);\n\n\t\tonUnmounted(() => {\n\t\t\t// Unwatch to stop receiving notifications\n\t\t\twatcher.unwatch(actor.currentView);\n\t\t});\n\n\t\t// Bind send function to actor for correct 'this' context\n\t\tconst sendBound = actor.send.bind(actor);\n\n\t\t// Compute Component from view.component lookup\n\t\tconst ResolvedComponent = computed(() => {\n\t\t\tif (!view.value) return null;\n\n\t\t\t// Handle null/undefined components catalog gracefully\n\t\t\tif (!props.components) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Components catalog is ${props.components === null ? \"null\" : \"undefined\"}. ` +\n\t\t\t\t\t\t`Cannot render component \"${view.value.component}\".`,\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Look up component from catalog\n\t\t\tconst comp = toRaw(props.components[view.value.component]);\n\n\t\t\tif (!comp) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Component \"${view.value.component}\" not found in catalog. ` +\n\t\t\t\t\t\t`Available components: ${Object.keys(props.components).join(\", \")}`,\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn comp;\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\t// View exists but component not found\n\t\t\tif (!ResolvedComponent.value) {\n\t\t\t\treturn h(\n\t\t\t\t\t\"div\",\n\t\t\t\t\t{ class: \"play-renderer-error\" },\n\t\t\t\t\t`Component \"${view.value.component}\" not found in catalog`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Render matched component dynamically\n\t\t\treturn h(ResolvedComponent.value, {\n\t\t\t\t...view.value.props,\n\t\t\t\tsend: sendBound,\n\t\t\t});\n\t\t};\n\t},\n});\n</script>\n\n<style scoped>\n.play-renderer-error {\n\tpadding: 1rem;\n\tbackground-color: #fee;\n\tborder: 1px solid #fcc;\n\tborder-radius: 4px;\n\tcolor: #c00;\n\tfont-family: monospace;\n}\n</style>\n"],"mappings":""}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { computed as e, defineComponent as t, h as n, onUnmounted as r, ref as i, toRaw as a } from "vue";
|
|
2
|
-
import { Signal as o } from "@xmachines/play-signals";
|
|
3
|
-
//#region src/PlayRenderer.vue?vue&type=script&lang.ts
|
|
4
|
-
var s = t({
|
|
5
|
-
name: "PlayRenderer",
|
|
6
|
-
props: {
|
|
7
|
-
actor: {
|
|
8
|
-
type: Object,
|
|
9
|
-
required: !0
|
|
10
|
-
},
|
|
11
|
-
components: {
|
|
12
|
-
type: Object,
|
|
13
|
-
required: !0
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
setup(t, { slots: s }) {
|
|
17
|
-
let c = a(t.actor), l = i(c.currentView.get()), u = new o.subtle.Watcher(() => {
|
|
18
|
-
queueMicrotask(() => {
|
|
19
|
-
u.getPending(), l.value = c.currentView.get(), u.watch(c.currentView);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
u.watch(c.currentView), r(() => {
|
|
23
|
-
u.unwatch(c.currentView);
|
|
24
|
-
});
|
|
25
|
-
let d = c.send.bind(c), f = e(() => l.value ? t.components ? a(t.components[l.value.component]) || (console.error(`Component "${l.value.component}" not found in catalog. Available components: ${Object.keys(t.components).join(", ")}`), null) : (console.error(`Components catalog is ${t.components === null ? "null" : "undefined"}. Cannot render component "${l.value.component}".`), null) : null);
|
|
26
|
-
return () => l.value ? f.value ? n(f.value, {
|
|
27
|
-
...l.value.props,
|
|
28
|
-
send: d
|
|
29
|
-
}) : n("div", { class: "play-renderer-error" }, `Component "${l.value.component}" not found in catalog`) : s.fallback ? s.fallback() : null;
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
//#endregion
|
|
33
|
-
export { s as default };
|
|
34
|
-
|
|
35
|
-
//# sourceMappingURL=PlayRenderer.vue_vue_type_script_lang.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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 * - Dynamically renders catalog components based on view.component string\n * - Forwards user events to actor via actor.send()\n * - Vue ref only for triggering renders, NOT business logic\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, computed, toRaw, onUnmounted, h, type PropType } from \"vue\";\nimport { Signal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { AbstractActor, Viewable } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type { Component } from \"vue\";\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\tcomponents: {\n\t\t\ttype: Object as PropType<Record<string, Component>>,\n\t\t\trequired: true,\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// 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<{ component: string; props: Record<string, unknown> } | null>(initialView);\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity\n\t\t// Created immediately (not in onMounted) so signal changes during the\n\t\t// synchronous mount phase are captured.\n\t\t//\n\t\t// The notify callback runs synchronously during the signal graph's\n\t\t// dirty-propagation phase. NEVER read (.get()) or write (.set()) signals\n\t\t// inside it. Only schedule a microtask to do the actual work.\n\t\tconst watcher = new Signal.subtle.Watcher(() => {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\t// Step 1: Acknowledge notification (clears watcher's dirty flags)\n\t\t\t\twatcher.getPending();\n\n\t\t\t\t// Step 2: Read signal value (safe — notification phase is over)\n\t\t\t\tview.value = actor.currentView.get();\n\n\t\t\t\t// Step 3: Re-watch for next notification (one-shot pattern)\n\t\t\t\t// TC39 watchers stop notifying after first notification until re-armed\n\t\t\t\twatcher.watch(actor.currentView);\n\t\t\t});\n\t\t});\n\n\t\t// Start watching the signal\n\t\twatcher.watch(actor.currentView);\n\n\t\tonUnmounted(() => {\n\t\t\t// Unwatch to stop receiving notifications\n\t\t\twatcher.unwatch(actor.currentView);\n\t\t});\n\n\t\t// Bind send function to actor for correct 'this' context\n\t\tconst sendBound = actor.send.bind(actor);\n\n\t\t// Compute Component from view.component lookup\n\t\tconst ResolvedComponent = computed(() => {\n\t\t\tif (!view.value) return null;\n\n\t\t\t// Handle null/undefined components catalog gracefully\n\t\t\tif (!props.components) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Components catalog is ${props.components === null ? \"null\" : \"undefined\"}. ` +\n\t\t\t\t\t\t`Cannot render component \"${view.value.component}\".`,\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Look up component from catalog\n\t\t\tconst comp = toRaw(props.components[view.value.component]);\n\n\t\t\tif (!comp) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Component \"${view.value.component}\" not found in catalog. ` +\n\t\t\t\t\t\t`Available components: ${Object.keys(props.components).join(\", \")}`,\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn comp;\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\t// View exists but component not found\n\t\t\tif (!ResolvedComponent.value) {\n\t\t\t\treturn h(\n\t\t\t\t\t\"div\",\n\t\t\t\t\t{ class: \"play-renderer-error\" },\n\t\t\t\t\t`Component \"${view.value.component}\" not found in catalog`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Render matched component dynamically\n\t\t\treturn h(ResolvedComponent.value, {\n\t\t\t\t...view.value.props,\n\t\t\t\tsend: sendBound,\n\t\t\t});\n\t\t};\n\t},\n});\n</script>\n\n<style scoped>\n.play-renderer-error {\n\tpadding: 1rem;\n\tbackground-color: #fee;\n\tborder: 1px solid #fcc;\n\tborder-radius: 4px;\n\tcolor: #c00;\n\tfont-family: monospace;\n}\n</style>\n"],"mappings":";;;AA+BA,IAAA,IAAe,EAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;GACV;EACD,YAAY;GACX,MAAM;GACN,UAAU;GACV;EACD;CACD,MAAM,GAAO,EAAE,YAAS;EAGvB,IAAM,IAAQ,EAAM,EAAM,MAAM,EAO1B,IAAO,EAJO,EAAM,YAAY,KAAK,CAIgD,EASrF,IAAU,IAAI,EAAO,OAAO,cAAc;AAC/C,wBAAqB;AASpB,IAPA,EAAQ,YAAY,EAGpB,EAAK,QAAQ,EAAM,YAAY,KAAK,EAIpC,EAAQ,MAAM,EAAM,YAAY;KAC/B;IACD;AAKF,EAFA,EAAQ,MAAM,EAAM,YAAY,EAEhC,QAAkB;AAEjB,KAAQ,QAAQ,EAAM,YAAY;IACjC;EAGF,IAAM,IAAY,EAAM,KAAK,KAAK,EAAM,EAGlC,IAAoB,QACpB,EAAK,QAGL,EAAM,aASE,EAAM,EAAM,WAAW,EAAK,MAAM,WAAW,KAGzD,QAAQ,MACP,cAAc,EAAK,MAAM,UAAU,gDACT,OAAO,KAAK,EAAM,WAAW,CAAC,KAAK,KAAK,GAClE,EACM,SAfP,QAAQ,MACP,yBAAyB,EAAM,eAAe,OAAO,SAAS,YAAY,6BAC7C,EAAK,MAAM,UAAU,IAClD,EACM,QARgB,KAuBvB;AAGF,eAEM,EAAK,QAKL,EAAkB,QAShB,EAAE,EAAkB,OAAO;GACjC,GAAG,EAAK,MAAM;GACd,MAAM;GACN,CAAC,GAXM,EACN,OACA,EAAE,OAAO,uBAAuB,EAChC,cAAc,EAAK,MAAM,UAAU,wBACnC,GATM,EAAM,WAAW,EAAM,UAAS,GAAI;;CAmB9C,CAAC"}
|
package/dist/index.css
DELETED
package/dist/index.js
DELETED