@koordinates/xstate-tree 4.5.0 → 5.1.0-next.2
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/lib/builders.js +13 -129
- package/lib/index.js +1 -4
- package/lib/lazy.js +23 -23
- package/lib/routing/Link.js +5 -6
- package/lib/routing/createRoute/createRoute.js +9 -10
- package/lib/routing/handleLocationChange/handleLocationChange.js +2 -2
- package/lib/routing/matchRoute/matchRoute.js +2 -3
- package/lib/routing/providers.js +2 -3
- package/lib/routing/useRouteArgsIfActive.js +1 -1
- package/lib/testingUtilities.js +7 -145
- package/lib/useService.js +9 -49
- package/lib/utils.js +19 -1
- package/lib/xstate-tree.d.ts +31 -217
- package/lib/xstateTree.js +66 -87
- package/package.json +7 -8
package/lib/xstateTree.js
CHANGED
|
@@ -74,14 +74,13 @@ const getViewForInterpreter = (0, fast_memoize_1.default)((interpreter) => {
|
|
|
74
74
|
(0, react_2.useEffect)(() => {
|
|
75
75
|
if (activeRouteEvents) {
|
|
76
76
|
activeRouteEvents.forEach((event) => {
|
|
77
|
-
|
|
78
|
-
if (interpreter.state.nextEvents.includes(event.type)) {
|
|
77
|
+
if (interpreter.getSnapshot().can(event)) {
|
|
79
78
|
interpreter.send(event);
|
|
80
79
|
}
|
|
81
80
|
});
|
|
82
81
|
}
|
|
83
82
|
}, []);
|
|
84
|
-
return react_2.default.createElement(XstateTreeView, {
|
|
83
|
+
return react_2.default.createElement(XstateTreeView, { actor: interpreter });
|
|
85
84
|
});
|
|
86
85
|
},
|
|
87
86
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -92,10 +91,10 @@ const getViewForInterpreter = (0, fast_memoize_1.default)((interpreter) => {
|
|
|
92
91
|
exports.getMultiSlotViewForChildren = (0, fast_memoize_1.default)((parent, slot) => {
|
|
93
92
|
return react_2.default.memo(function MultiSlotView() {
|
|
94
93
|
const [_, children] = (0, useService_1.useService)(parent);
|
|
95
|
-
const interpreters =
|
|
94
|
+
const interpreters = Object.values(children);
|
|
96
95
|
// Once the interpreter is stopped, initialized gets set to false
|
|
97
96
|
// We don't want to render stopped interpreters
|
|
98
|
-
const interpretersWeCareAbout = interpreters.filter((i) => i.id.includes(slot)
|
|
97
|
+
const interpretersWeCareAbout = interpreters.filter((i) => i.id.includes(slot));
|
|
99
98
|
return (react_2.default.createElement(XstateTreeMultiSlotView, { childInterpreters: interpretersWeCareAbout }));
|
|
100
99
|
});
|
|
101
100
|
}, {
|
|
@@ -114,7 +113,7 @@ function useSlots(interpreter, slots) {
|
|
|
114
113
|
return react_2.default.createElement(MultiView, null);
|
|
115
114
|
}
|
|
116
115
|
else {
|
|
117
|
-
const interpreterForSlot = children
|
|
116
|
+
const interpreterForSlot = children[`${slot.toLowerCase()}-slot`];
|
|
118
117
|
if (interpreterForSlot) {
|
|
119
118
|
const View = getViewForInterpreter(interpreterForSlot);
|
|
120
119
|
return react_2.default.createElement(View, null);
|
|
@@ -130,93 +129,67 @@ function useSlots(interpreter, slots) {
|
|
|
130
129
|
});
|
|
131
130
|
}
|
|
132
131
|
function XstateTreeMultiSlotView({ childInterpreters, }) {
|
|
133
|
-
|
|
132
|
+
console.log("XstateTreeMultiSlotView", childInterpreters);
|
|
133
|
+
return (react_2.default.createElement(react_2.default.Fragment, null, childInterpreters.map((i) => (react_2.default.createElement(XstateTreeView, { key: i.id, actor: i })))));
|
|
134
134
|
}
|
|
135
135
|
/**
|
|
136
136
|
* @internal
|
|
137
137
|
*/
|
|
138
|
-
function XstateTreeView({
|
|
139
|
-
|
|
140
|
-
const [current] = (0, useService_1.useService)(interpreter);
|
|
138
|
+
function XstateTreeView({ actor }) {
|
|
139
|
+
const [current] = (0, useService_1.useService)(actor);
|
|
141
140
|
const currentRef = (0, react_2.useRef)(current);
|
|
142
141
|
currentRef.current = current;
|
|
143
142
|
const selectorsRef = (0, react_2.useRef)(undefined);
|
|
144
|
-
const { slots: interpreterSlots } =
|
|
145
|
-
const slots = useSlots(
|
|
143
|
+
const { slots: interpreterSlots, View, actions: actionsFactory, selectors: selectorsFactory, } = actor.logic._xstateTree;
|
|
144
|
+
const slots = useSlots(actor, interpreterSlots.map((x) => x.name));
|
|
146
145
|
const canHandleEvent = (0, react_2.useCallback)((e) => {
|
|
147
|
-
return
|
|
148
|
-
}, [
|
|
146
|
+
return actor.getSnapshot().can(e);
|
|
147
|
+
}, [actor]);
|
|
149
148
|
const inState = (0, react_2.useCallback)((state) => {
|
|
150
|
-
|
|
151
|
-
return (_b = (_a = currentRef.current) === null || _a === void 0 ? void 0 : _a.matches(state)) !== null && _b !== void 0 ? _b : false;
|
|
149
|
+
return currentRef.current?.matches(state) ?? false;
|
|
152
150
|
},
|
|
153
151
|
// This is needed because the inState function needs to be recreated if the
|
|
154
152
|
// current state the machine is in changes. But _only_ then
|
|
155
153
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
|
-
[current
|
|
154
|
+
[current?.value]);
|
|
157
155
|
const selectorsProxy = (0, useConstant_1.useConstant)(() => {
|
|
158
156
|
return new Proxy({}, {
|
|
159
157
|
get: (_target, prop) => {
|
|
160
|
-
|
|
161
|
-
return (_a = selectorsRef.current) === null || _a === void 0 ? void 0 : _a[prop];
|
|
158
|
+
return selectorsRef.current?.[prop];
|
|
162
159
|
},
|
|
163
160
|
});
|
|
164
161
|
});
|
|
165
162
|
const actions = (0, useConstant_1.useConstant)(() => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
case 2:
|
|
171
|
-
return interpreter.machine.meta.actions({
|
|
172
|
-
send: interpreter.send,
|
|
173
|
-
selectors: selectorsProxy,
|
|
174
|
-
});
|
|
175
|
-
default:
|
|
176
|
-
throw new Error("builderVersion not set");
|
|
177
|
-
}
|
|
163
|
+
return actionsFactory({
|
|
164
|
+
send: actor.send,
|
|
165
|
+
selectors: selectorsProxy,
|
|
166
|
+
});
|
|
178
167
|
});
|
|
179
168
|
if (!current) {
|
|
180
169
|
return null;
|
|
181
170
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
inState,
|
|
191
|
-
meta: (0, utils_1.mergeMeta)(current.meta),
|
|
192
|
-
});
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
switch ((_b = interpreter.machine.meta) === null || _b === void 0 ? void 0 : _b.builderVersion) {
|
|
196
|
-
case 1:
|
|
197
|
-
const ViewV1 = interpreter.machine.meta.view;
|
|
198
|
-
return (react_2.default.createElement(ViewV1, { selectors: selectorsRef.current, actions: actions, slots: slots, inState: inState }));
|
|
199
|
-
case 2:
|
|
200
|
-
const ViewV2 = interpreter.machine.meta.View;
|
|
201
|
-
return (react_2.default.createElement(ViewV2, { selectors: selectorsRef.current, actions: actions, slots: slots }));
|
|
202
|
-
default:
|
|
203
|
-
throw new Error("builderVersion not set");
|
|
204
|
-
}
|
|
171
|
+
selectorsRef.current = selectorsFactory({
|
|
172
|
+
ctx: current.context,
|
|
173
|
+
canHandleEvent,
|
|
174
|
+
// Workaround for type instantiation possibly infinite error
|
|
175
|
+
inState: inState,
|
|
176
|
+
meta: (0, utils_1.mergeMeta)(current.getMeta()),
|
|
177
|
+
});
|
|
178
|
+
return (react_2.default.createElement(View, { selectors: selectorsRef.current, actions: actions, slots: slots }));
|
|
205
179
|
}
|
|
206
180
|
exports.XstateTreeView = XstateTreeView;
|
|
207
181
|
/**
|
|
208
182
|
* @internal
|
|
209
183
|
*/
|
|
210
184
|
function recursivelySend(service, event) {
|
|
211
|
-
|
|
212
|
-
const children = ([...service.children.values()] || []).filter((s) => s.id.includes("-slot"));
|
|
185
|
+
const children = Object.values(service.getSnapshot().children).filter((s) => s.id.includes("-slot"));
|
|
213
186
|
// If the service can't handle the event, don't send it
|
|
214
|
-
if (
|
|
187
|
+
if (service.getSnapshot().can(event)) {
|
|
215
188
|
try {
|
|
216
189
|
service.send(event);
|
|
217
190
|
}
|
|
218
191
|
catch (e) {
|
|
219
|
-
console.error("Error sending event ", event, " to machine ", service.
|
|
192
|
+
console.error("Error sending event ", event, " to machine ", service.id, e);
|
|
220
193
|
}
|
|
221
194
|
}
|
|
222
195
|
children.forEach((child) => recursivelySend(child, event));
|
|
@@ -231,26 +204,39 @@ exports.recursivelySend = recursivelySend;
|
|
|
231
204
|
* @param routing - The routing configuration for the tree
|
|
232
205
|
*/
|
|
233
206
|
function buildRootComponent(machine, routing) {
|
|
234
|
-
if (!machine.
|
|
235
|
-
throw new Error("Root machine
|
|
207
|
+
if (!machine._xstateTree) {
|
|
208
|
+
throw new Error("Root machine is not an xstate-tree machine, missing metadata");
|
|
236
209
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (!machine.meta.view) {
|
|
240
|
-
throw new Error("Root machine has no associated view");
|
|
241
|
-
}
|
|
242
|
-
break;
|
|
243
|
-
case 2:
|
|
244
|
-
if (!machine.meta.View) {
|
|
245
|
-
throw new Error("Root machine has no associated view");
|
|
246
|
-
}
|
|
247
|
-
break;
|
|
210
|
+
if (!machine._xstateTree.View) {
|
|
211
|
+
throw new Error("Root machine has no associated view");
|
|
248
212
|
}
|
|
249
213
|
const RootComponent = function XstateTreeRootComponent() {
|
|
250
|
-
const
|
|
214
|
+
const lastSnapshotsRef = (0, react_2.useRef)({});
|
|
215
|
+
const [_, __, interpreter] = (0, react_1.useActor)(machine, {
|
|
216
|
+
inspect(event) {
|
|
217
|
+
switch (event.type) {
|
|
218
|
+
case "@xstate.actor":
|
|
219
|
+
console.log(`[xstate-tree] actor spawned: ${event.actorRef.id}`);
|
|
220
|
+
break;
|
|
221
|
+
case "@xstate.event":
|
|
222
|
+
console.log(`[xstate-tree] event: ${event.sourceRef ? event.sourceRef.id : "UNKNOWN"} -> ${event.event.type} -> ${event.actorRef.id}`, event.event);
|
|
223
|
+
break;
|
|
224
|
+
case "@xstate.snapshot":
|
|
225
|
+
const lastSnapshot = lastSnapshotsRef.current[event.actorRef.sessionId];
|
|
226
|
+
if (!lastSnapshot) {
|
|
227
|
+
console.log(`[xstate-tree] initial snapshot: ${event.actorRef.id}`, (0, utils_1.toJSON)(event.snapshot));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
console.log(`[xstate-tree] snapshot: ${event.actorRef.id} transitioning to`, (0, utils_1.toJSON)(event.snapshot), "from", (0, utils_1.toJSON)(lastSnapshot));
|
|
231
|
+
}
|
|
232
|
+
lastSnapshotsRef.current[event.actorRef.sessionId] = event.snapshot;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
id: machine.config.id,
|
|
237
|
+
});
|
|
251
238
|
const [activeRoute, setActiveRoute] = (0, react_2.useState)(undefined);
|
|
252
239
|
const activeRouteEventsRef = (0, react_2.useRef)([]);
|
|
253
|
-
const [forceRenderValue, forceRender] = (0, react_2.useState)(false);
|
|
254
240
|
const setActiveRouteEvents = (events) => {
|
|
255
241
|
activeRouteEventsRef.current = events;
|
|
256
242
|
};
|
|
@@ -303,7 +289,6 @@ function buildRootComponent(machine, routing) {
|
|
|
303
289
|
});
|
|
304
290
|
});
|
|
305
291
|
void Promise.all(redirectPromises).then((redirects) => {
|
|
306
|
-
var _a, _b, _c;
|
|
307
292
|
const didAnyRedirect = redirects.some((x) => x !== undefined);
|
|
308
293
|
if (!didAnyRedirect || controller.signal.aborted) {
|
|
309
294
|
return;
|
|
@@ -318,9 +303,9 @@ function buildRootComponent(machine, routing) {
|
|
|
318
303
|
}, {
|
|
319
304
|
// since the redirect results are partials, need to merge them with the original event
|
|
320
305
|
// params/query to ensure that all params/query are present
|
|
321
|
-
query: { ...(
|
|
322
|
-
params: { ...(
|
|
323
|
-
meta: { replace: true, ...(
|
|
306
|
+
query: { ...(activeRoutesEvent.query ?? {}) },
|
|
307
|
+
params: { ...(activeRoutesEvent.params ?? {}) },
|
|
308
|
+
meta: { replace: true, ...(activeRoutesEvent.meta ?? {}) },
|
|
324
309
|
});
|
|
325
310
|
activeRoute.navigate(routeArguments);
|
|
326
311
|
});
|
|
@@ -329,11 +314,10 @@ function buildRootComponent(machine, routing) {
|
|
|
329
314
|
};
|
|
330
315
|
}, [activeRoute]);
|
|
331
316
|
(0, react_2.useEffect)(() => {
|
|
332
|
-
var _a, _b;
|
|
333
317
|
if (routing) {
|
|
334
318
|
const { getPathName = () => routing.history.location.pathname, getQueryString = () => routing.history.location.search, } = routing;
|
|
335
319
|
const initialMeta = {
|
|
336
|
-
...(
|
|
320
|
+
...(routing.history.location.state?.meta ?? {}),
|
|
337
321
|
onloadEvent: (0, utils_1.isLikelyPageLoad)(),
|
|
338
322
|
};
|
|
339
323
|
const queryString = getQueryString();
|
|
@@ -353,8 +337,7 @@ function buildRootComponent(machine, routing) {
|
|
|
353
337
|
(0, react_2.useEffect)(() => {
|
|
354
338
|
if (routing) {
|
|
355
339
|
const unsub = routing.history.listen((location) => {
|
|
356
|
-
|
|
357
|
-
const result = (0, routing_1.handleLocationChange)(routing.routes, routing.basePath, location.pathname, location.search, (_a = location.state) === null || _a === void 0 ? void 0 : _a.meta);
|
|
340
|
+
const result = (0, routing_1.handleLocationChange)(routing.routes, routing.basePath, location.pathname, location.search, location.state?.meta);
|
|
358
341
|
if (result) {
|
|
359
342
|
setActiveRouteEvents(result.events);
|
|
360
343
|
setActiveRoute({ ...result.matchedRoute });
|
|
@@ -376,16 +359,12 @@ function buildRootComponent(machine, routing) {
|
|
|
376
359
|
activeRouteEvents: activeRouteEventsRef,
|
|
377
360
|
};
|
|
378
361
|
}, [activeRoute]);
|
|
379
|
-
if (!interpreter.initialized) {
|
|
380
|
-
setTimeout(() => forceRender(!forceRenderValue), 0);
|
|
381
|
-
return null;
|
|
382
|
-
}
|
|
383
362
|
if (routingProviderValue) {
|
|
384
363
|
return (react_2.default.createElement(routing_1.RoutingContext.Provider, { value: routingProviderValue },
|
|
385
|
-
react_2.default.createElement(XstateTreeView, {
|
|
364
|
+
react_2.default.createElement(XstateTreeView, { actor: interpreter })));
|
|
386
365
|
}
|
|
387
366
|
else {
|
|
388
|
-
return react_2.default.createElement(XstateTreeView, {
|
|
367
|
+
return react_2.default.createElement(XstateTreeView, { actor: interpreter });
|
|
389
368
|
}
|
|
390
369
|
};
|
|
391
370
|
RootComponent.rootMachine = machine;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@koordinates/xstate-tree",
|
|
3
3
|
"main": "lib/index.js",
|
|
4
4
|
"types": "lib/xstate-tree.d.ts",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "5.1.0-next.2",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"description": "Build UIs with Actors using xstate and React",
|
|
8
8
|
"keywords": [
|
|
@@ -41,10 +41,9 @@
|
|
|
41
41
|
"@types/react": "^17.0.29",
|
|
42
42
|
"@types/react-dom": "^18.0.6",
|
|
43
43
|
"@types/testing-library__jest-dom": "^5.14.1",
|
|
44
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
|
45
45
|
"@vitejs/plugin-react": "^2.1.0",
|
|
46
|
-
"@xstate/
|
|
47
|
-
"@xstate/react": "^3.0.0",
|
|
46
|
+
"@xstate/react": "^4.0.2",
|
|
48
47
|
"classnames": "^2.3.1",
|
|
49
48
|
"cz-conventional-changelog": "^3.3.0",
|
|
50
49
|
"eslint": "^7.32.0",
|
|
@@ -66,15 +65,15 @@
|
|
|
66
65
|
"todomvc-app-css": "^2.4.2",
|
|
67
66
|
"todomvc-common": "^1.0.5",
|
|
68
67
|
"ts-jest": "^28.0.5",
|
|
69
|
-
"typescript": "
|
|
68
|
+
"typescript": "5.0.2",
|
|
70
69
|
"vite": "^3.1.3",
|
|
71
70
|
"vite-tsconfig-paths": "^3.5.0",
|
|
72
|
-
"xstate": "^4.
|
|
71
|
+
"xstate": "^5.4.1"
|
|
73
72
|
},
|
|
74
73
|
"peerDependencies": {
|
|
75
|
-
"@xstate/react": "^
|
|
74
|
+
"@xstate/react": "^4.x",
|
|
76
75
|
"react": ">= 16.8.0 < 19.0.0",
|
|
77
|
-
"xstate": "
|
|
76
|
+
"xstate": "^5.x",
|
|
78
77
|
"zod": "^3.x"
|
|
79
78
|
},
|
|
80
79
|
"scripts": {
|