@koordinates/xstate-tree 4.2.0 → 4.3.0-beta.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 +18 -7
- package/lib/index.js +40 -8
- package/lib/lazy.js +20 -13
- package/lib/routing/Link.js +36 -6
- package/lib/routing/createRoute/createRoute.js +24 -12
- package/lib/routing/createRoute/index.js +5 -1
- package/lib/routing/handleLocationChange/handleLocationChange.js +13 -8
- package/lib/routing/handleLocationChange/index.js +5 -1
- package/lib/routing/index.js +20 -8
- package/lib/routing/joinRoutes.js +5 -1
- package/lib/routing/matchRoute/index.js +5 -1
- package/lib/routing/matchRoute/matchRoute.js +5 -1
- package/lib/routing/providers.js +15 -7
- package/lib/routing/routingEvent.js +2 -1
- package/lib/routing/useHref.js +7 -3
- package/lib/routing/useIsRouteActive.js +7 -3
- package/lib/routing/useRouteArgsIfActive.js +11 -7
- package/lib/setupScript.js +3 -1
- package/lib/slots/index.js +17 -1
- package/lib/slots/slots.js +9 -4
- package/lib/testingUtilities.js +54 -25
- package/lib/types.js +2 -1
- package/lib/useConstant.js +7 -3
- package/lib/useService.js +16 -11
- package/lib/utils.js +17 -7
- package/lib/xstate-tree.d.ts +18 -2
- package/lib/xstateTree.js +97 -61
- package/package.json +2 -2
package/lib/xstateTree.js
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.buildRootComponent = exports.recursivelySend = exports.XstateTreeView = exports.getMultiSlotViewForChildren = exports.onBroadcast = exports.broadcast = exports.emitter = void 0;
|
|
30
|
+
const react_1 = require("@xstate/react");
|
|
31
|
+
const fast_memoize_1 = __importDefault(require("fast-memoize"));
|
|
32
|
+
const react_2 = __importStar(require("react"));
|
|
33
|
+
const tiny_emitter_1 = require("tiny-emitter");
|
|
34
|
+
const routing_1 = require("./routing");
|
|
35
|
+
const providers_1 = require("./routing/providers");
|
|
36
|
+
const useConstant_1 = require("./useConstant");
|
|
37
|
+
const useService_1 = require("./useService");
|
|
38
|
+
const utils_1 = require("./utils");
|
|
39
|
+
exports.emitter = new tiny_emitter_1.TinyEmitter();
|
|
11
40
|
/**
|
|
12
41
|
* @public
|
|
13
42
|
*
|
|
@@ -15,10 +44,11 @@ export const emitter = new TinyEmitter();
|
|
|
15
44
|
*
|
|
16
45
|
* @param event - the event to broadcast
|
|
17
46
|
*/
|
|
18
|
-
|
|
47
|
+
function broadcast(event) {
|
|
19
48
|
console.debug("[xstate-tree] broadcasting event ", event.type);
|
|
20
|
-
emitter.emit("event", event);
|
|
49
|
+
exports.emitter.emit("event", event);
|
|
21
50
|
}
|
|
51
|
+
exports.broadcast = broadcast;
|
|
22
52
|
/**
|
|
23
53
|
* @public
|
|
24
54
|
*
|
|
@@ -26,21 +56,22 @@ export function broadcast(event) {
|
|
|
26
56
|
*
|
|
27
57
|
* @param handler - the handler to call when an event is broadcast
|
|
28
58
|
*/
|
|
29
|
-
|
|
30
|
-
emitter.on("event", handler);
|
|
59
|
+
function onBroadcast(handler) {
|
|
60
|
+
exports.emitter.on("event", handler);
|
|
31
61
|
return () => {
|
|
32
|
-
emitter.off("event", handler);
|
|
62
|
+
exports.emitter.off("event", handler);
|
|
33
63
|
};
|
|
34
64
|
}
|
|
65
|
+
exports.onBroadcast = onBroadcast;
|
|
35
66
|
function cacheKeyForInterpreter(
|
|
36
67
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
68
|
interpreter) {
|
|
38
69
|
return interpreter.sessionId;
|
|
39
70
|
}
|
|
40
|
-
const getViewForInterpreter =
|
|
41
|
-
return
|
|
42
|
-
const activeRouteEvents = useActiveRouteEvents();
|
|
43
|
-
useEffect(() => {
|
|
71
|
+
const getViewForInterpreter = (0, fast_memoize_1.default)((interpreter) => {
|
|
72
|
+
return react_2.default.memo(function InterpreterView() {
|
|
73
|
+
const activeRouteEvents = (0, providers_1.useActiveRouteEvents)();
|
|
74
|
+
(0, react_2.useEffect)(() => {
|
|
44
75
|
if (activeRouteEvents) {
|
|
45
76
|
activeRouteEvents.forEach((event) => {
|
|
46
77
|
if (interpreter.state.nextEvents.includes(event.type)) {
|
|
@@ -49,7 +80,7 @@ const getViewForInterpreter = memoize((interpreter) => {
|
|
|
49
80
|
});
|
|
50
81
|
}
|
|
51
82
|
}, []);
|
|
52
|
-
return
|
|
83
|
+
return react_2.default.createElement(XstateTreeView, { interpreter: interpreter });
|
|
53
84
|
});
|
|
54
85
|
},
|
|
55
86
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -57,35 +88,35 @@ const getViewForInterpreter = memoize((interpreter) => {
|
|
|
57
88
|
/**
|
|
58
89
|
* @private
|
|
59
90
|
*/
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
const [_, children] = useService(parent);
|
|
91
|
+
exports.getMultiSlotViewForChildren = (0, fast_memoize_1.default)((parent, slot) => {
|
|
92
|
+
return react_2.default.memo(function MultiSlotView() {
|
|
93
|
+
const [_, children] = (0, useService_1.useService)(parent);
|
|
63
94
|
const interpreters = [...children.values()];
|
|
64
95
|
// Once the interpreter is stopped, initialized gets set to false
|
|
65
96
|
// We don't want to render stopped interpreters
|
|
66
97
|
const interpretersWeCareAbout = interpreters.filter((i) => i.id.includes(slot) && i.initialized);
|
|
67
|
-
return (
|
|
98
|
+
return (react_2.default.createElement(XstateTreeMultiSlotView, { childInterpreters: interpretersWeCareAbout }));
|
|
68
99
|
});
|
|
69
100
|
}, {
|
|
70
101
|
serializer: (args) => `${cacheKeyForInterpreter(args[0])}-${args[1]}`,
|
|
71
102
|
});
|
|
72
103
|
function useSlots(interpreter, slots) {
|
|
73
|
-
return useConstant(() => {
|
|
104
|
+
return (0, useConstant_1.useConstant)(() => {
|
|
74
105
|
return slots.reduce((views, slot) => {
|
|
75
106
|
return {
|
|
76
107
|
...views,
|
|
77
108
|
[slot]: () => {
|
|
78
109
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
79
|
-
const [__, children] = useService(interpreter);
|
|
110
|
+
const [__, children] = (0, useService_1.useService)(interpreter);
|
|
80
111
|
if (slot.toString().endsWith("Multi")) {
|
|
81
|
-
const MultiView = getMultiSlotViewForChildren(interpreter, slot.toLowerCase());
|
|
82
|
-
return
|
|
112
|
+
const MultiView = (0, exports.getMultiSlotViewForChildren)(interpreter, slot.toLowerCase());
|
|
113
|
+
return react_2.default.createElement(MultiView, null);
|
|
83
114
|
}
|
|
84
115
|
else {
|
|
85
116
|
const interpreterForSlot = children.get(`${slot.toLowerCase()}-slot`);
|
|
86
117
|
if (interpreterForSlot) {
|
|
87
118
|
const View = getViewForInterpreter(interpreterForSlot);
|
|
88
|
-
return
|
|
119
|
+
return react_2.default.createElement(View, null);
|
|
89
120
|
}
|
|
90
121
|
else {
|
|
91
122
|
// Waiting for the interpreter for this slot to be invoked
|
|
@@ -98,23 +129,23 @@ function useSlots(interpreter, slots) {
|
|
|
98
129
|
});
|
|
99
130
|
}
|
|
100
131
|
function XstateTreeMultiSlotView({ childInterpreters, }) {
|
|
101
|
-
return (
|
|
132
|
+
return (react_2.default.createElement(react_2.default.Fragment, null, childInterpreters.map((i) => (react_2.default.createElement(XstateTreeView, { key: i.id, interpreter: i })))));
|
|
102
133
|
}
|
|
103
134
|
/**
|
|
104
135
|
* @internal
|
|
105
136
|
*/
|
|
106
|
-
|
|
137
|
+
function XstateTreeView({ interpreter }) {
|
|
107
138
|
var _a, _b;
|
|
108
|
-
const [current] = useService(interpreter);
|
|
109
|
-
const currentRef = useRef(current);
|
|
139
|
+
const [current] = (0, useService_1.useService)(interpreter);
|
|
140
|
+
const currentRef = (0, react_2.useRef)(current);
|
|
110
141
|
currentRef.current = current;
|
|
111
|
-
const selectorsRef = useRef(undefined);
|
|
142
|
+
const selectorsRef = (0, react_2.useRef)(undefined);
|
|
112
143
|
const { slots: interpreterSlots } = interpreter.machine.meta;
|
|
113
144
|
const slots = useSlots(interpreter, interpreterSlots.map((x) => x.name));
|
|
114
|
-
const canHandleEvent = useCallback((e) => {
|
|
145
|
+
const canHandleEvent = (0, react_2.useCallback)((e) => {
|
|
115
146
|
return interpreter.getSnapshot().can(e);
|
|
116
147
|
}, [interpreter]);
|
|
117
|
-
const inState = useCallback((state) => {
|
|
148
|
+
const inState = (0, react_2.useCallback)((state) => {
|
|
118
149
|
var _a, _b;
|
|
119
150
|
return (_b = (_a = currentRef.current) === null || _a === void 0 ? void 0 : _a.matches(state)) !== null && _b !== void 0 ? _b : false;
|
|
120
151
|
},
|
|
@@ -122,7 +153,7 @@ export function XstateTreeView({ interpreter }) {
|
|
|
122
153
|
// current state the machine is in changes. But _only_ then
|
|
123
154
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
124
155
|
[current.value]);
|
|
125
|
-
const selectorsProxy = useConstant(() => {
|
|
156
|
+
const selectorsProxy = (0, useConstant_1.useConstant)(() => {
|
|
126
157
|
return new Proxy({}, {
|
|
127
158
|
get: (_target, prop) => {
|
|
128
159
|
var _a;
|
|
@@ -130,7 +161,7 @@ export function XstateTreeView({ interpreter }) {
|
|
|
130
161
|
},
|
|
131
162
|
});
|
|
132
163
|
});
|
|
133
|
-
const actions = useConstant(() => {
|
|
164
|
+
const actions = (0, useConstant_1.useConstant)(() => {
|
|
134
165
|
var _a;
|
|
135
166
|
switch ((_a = interpreter.machine.meta) === null || _a === void 0 ? void 0 : _a.builderVersion) {
|
|
136
167
|
case 1:
|
|
@@ -162,18 +193,19 @@ export function XstateTreeView({ interpreter }) {
|
|
|
162
193
|
switch ((_b = interpreter.machine.meta) === null || _b === void 0 ? void 0 : _b.builderVersion) {
|
|
163
194
|
case 1:
|
|
164
195
|
const ViewV1 = interpreter.machine.meta.view;
|
|
165
|
-
return (
|
|
196
|
+
return (react_2.default.createElement(ViewV1, { selectors: selectorsRef.current, actions: actions, slots: slots, inState: inState }));
|
|
166
197
|
case 2:
|
|
167
198
|
const ViewV2 = interpreter.machine.meta.View;
|
|
168
|
-
return (
|
|
199
|
+
return (react_2.default.createElement(ViewV2, { selectors: selectorsRef.current, actions: actions, slots: slots }));
|
|
169
200
|
default:
|
|
170
201
|
throw new Error("builderVersion not set");
|
|
171
202
|
}
|
|
172
203
|
}
|
|
204
|
+
exports.XstateTreeView = XstateTreeView;
|
|
173
205
|
/**
|
|
174
206
|
* @internal
|
|
175
207
|
*/
|
|
176
|
-
|
|
208
|
+
function recursivelySend(service, event) {
|
|
177
209
|
const children = ([...service.children.values()] || []).filter((s) => s.id.includes("-slot"));
|
|
178
210
|
// If the service can't handle the event, don't send it
|
|
179
211
|
if (service.state.nextEvents.includes(event.type)) {
|
|
@@ -186,6 +218,7 @@ export function recursivelySend(service, event) {
|
|
|
186
218
|
}
|
|
187
219
|
children.forEach((child) => recursivelySend(child, event));
|
|
188
220
|
}
|
|
221
|
+
exports.recursivelySend = recursivelySend;
|
|
189
222
|
/**
|
|
190
223
|
* @public
|
|
191
224
|
*
|
|
@@ -194,7 +227,7 @@ export function recursivelySend(service, event) {
|
|
|
194
227
|
* @param machine - The root machine of the tree
|
|
195
228
|
* @param routing - The routing configuration for the tree
|
|
196
229
|
*/
|
|
197
|
-
|
|
230
|
+
function buildRootComponent(machine, routing) {
|
|
198
231
|
if (!machine.meta) {
|
|
199
232
|
throw new Error("Root machine has no meta");
|
|
200
233
|
}
|
|
@@ -211,23 +244,23 @@ export function buildRootComponent(machine, routing) {
|
|
|
211
244
|
break;
|
|
212
245
|
}
|
|
213
246
|
const RootComponent = function XstateTreeRootComponent() {
|
|
214
|
-
const [_, __, interpreter] = useMachine(machine, { devTools: true });
|
|
215
|
-
const [activeRoute, setActiveRoute] = useState(undefined);
|
|
216
|
-
const activeRouteEventsRef = useRef([]);
|
|
217
|
-
const [forceRenderValue, forceRender] = useState(false);
|
|
247
|
+
const [_, __, interpreter] = (0, react_1.useMachine)(machine, { devTools: true });
|
|
248
|
+
const [activeRoute, setActiveRoute] = (0, react_2.useState)(undefined);
|
|
249
|
+
const activeRouteEventsRef = (0, react_2.useRef)([]);
|
|
250
|
+
const [forceRenderValue, forceRender] = (0, react_2.useState)(false);
|
|
218
251
|
const setActiveRouteEvents = (events) => {
|
|
219
252
|
activeRouteEventsRef.current = events;
|
|
220
253
|
};
|
|
221
|
-
useEffect(() => {
|
|
254
|
+
(0, react_2.useEffect)(() => {
|
|
222
255
|
function handler(event) {
|
|
223
256
|
recursivelySend(interpreter, event);
|
|
224
257
|
}
|
|
225
|
-
emitter.on("event", handler);
|
|
258
|
+
exports.emitter.on("event", handler);
|
|
226
259
|
return () => {
|
|
227
|
-
emitter.off("event", handler);
|
|
260
|
+
exports.emitter.off("event", handler);
|
|
228
261
|
};
|
|
229
262
|
}, [interpreter]);
|
|
230
|
-
useEffect(() => {
|
|
263
|
+
(0, react_2.useEffect)(() => {
|
|
231
264
|
if (activeRoute === undefined) {
|
|
232
265
|
return;
|
|
233
266
|
}
|
|
@@ -240,7 +273,7 @@ export function buildRootComponent(machine, routing) {
|
|
|
240
273
|
}
|
|
241
274
|
const routeEventPairs = [];
|
|
242
275
|
const activeRoutesEvent = activeRouteEventsRef.current.find((e) => e.type === activeRoute.event);
|
|
243
|
-
assertIsDefined(activeRoutesEvent);
|
|
276
|
+
(0, utils_1.assertIsDefined)(activeRoutesEvent);
|
|
244
277
|
for (let i = 0; i < routes.length; i++) {
|
|
245
278
|
const route = routes[i];
|
|
246
279
|
const routeEvent = activeRouteEventsRef.current[i];
|
|
@@ -250,7 +283,7 @@ export function buildRootComponent(machine, routing) {
|
|
|
250
283
|
return route.redirect !== undefined;
|
|
251
284
|
});
|
|
252
285
|
const redirectPromises = routePairsWithRedirects.map(([route, event]) => {
|
|
253
|
-
assertIsDefined(route.redirect);
|
|
286
|
+
(0, utils_1.assertIsDefined)(route.redirect);
|
|
254
287
|
return route.redirect({
|
|
255
288
|
signal: controller.signal,
|
|
256
289
|
query: event.query,
|
|
@@ -284,11 +317,11 @@ export function buildRootComponent(machine, routing) {
|
|
|
284
317
|
controller.abort();
|
|
285
318
|
};
|
|
286
319
|
}, [activeRoute]);
|
|
287
|
-
useEffect(() => {
|
|
320
|
+
(0, react_2.useEffect)(() => {
|
|
288
321
|
if (routing) {
|
|
289
322
|
const { getPathName = () => routing.history.location.pathname, getQueryString = () => routing.history.location.search, } = routing;
|
|
290
323
|
const queryString = getQueryString();
|
|
291
|
-
const result = handleLocationChange(routing.routes, routing.basePath, getPathName(), getQueryString(), { onloadEvent: isLikelyPageLoad() });
|
|
324
|
+
const result = (0, routing_1.handleLocationChange)(routing.routes, routing.basePath, getPathName(), getQueryString(), { onloadEvent: (0, utils_1.isLikelyPageLoad)() });
|
|
292
325
|
if (result) {
|
|
293
326
|
setActiveRouteEvents(result.events);
|
|
294
327
|
setActiveRoute({ ...result.matchedRoute });
|
|
@@ -299,11 +332,11 @@ export function buildRootComponent(machine, routing) {
|
|
|
299
332
|
routing.history.replace(`${getPathName()}${queryString}`, {});
|
|
300
333
|
}
|
|
301
334
|
}, []);
|
|
302
|
-
useEffect(() => {
|
|
335
|
+
(0, react_2.useEffect)(() => {
|
|
303
336
|
if (routing) {
|
|
304
337
|
const unsub = routing.history.listen((location) => {
|
|
305
338
|
var _a;
|
|
306
|
-
const result = handleLocationChange(routing.routes, routing.basePath, location.pathname, location.search, (_a = location.state) === null || _a === void 0 ? void 0 : _a.meta);
|
|
339
|
+
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);
|
|
307
340
|
if (result) {
|
|
308
341
|
setActiveRouteEvents(result.events);
|
|
309
342
|
setActiveRoute({ ...result.matchedRoute });
|
|
@@ -315,26 +348,29 @@ export function buildRootComponent(machine, routing) {
|
|
|
315
348
|
}
|
|
316
349
|
return undefined;
|
|
317
350
|
}, []);
|
|
318
|
-
const routingProviderValue = useMemo(() => {
|
|
351
|
+
const routingProviderValue = (0, react_2.useMemo)(() => {
|
|
352
|
+
// Just to satisfy linter, need this memo to be re-calculated on route changes
|
|
353
|
+
activeRoute;
|
|
319
354
|
if (!routing) {
|
|
320
355
|
return null;
|
|
321
356
|
}
|
|
322
357
|
return {
|
|
323
358
|
activeRouteEvents: activeRouteEventsRef,
|
|
324
359
|
};
|
|
325
|
-
}, []);
|
|
360
|
+
}, [activeRoute]);
|
|
326
361
|
if (!interpreter.initialized) {
|
|
327
362
|
setTimeout(() => forceRender(!forceRenderValue), 0);
|
|
328
363
|
return null;
|
|
329
364
|
}
|
|
330
365
|
if (routingProviderValue) {
|
|
331
|
-
return (
|
|
332
|
-
|
|
366
|
+
return (react_2.default.createElement(routing_1.RoutingContext.Provider, { value: routingProviderValue },
|
|
367
|
+
react_2.default.createElement(XstateTreeView, { interpreter: interpreter })));
|
|
333
368
|
}
|
|
334
369
|
else {
|
|
335
|
-
return
|
|
370
|
+
return react_2.default.createElement(XstateTreeView, { interpreter: interpreter });
|
|
336
371
|
}
|
|
337
372
|
};
|
|
338
373
|
RootComponent.rootMachine = machine;
|
|
339
374
|
return RootComponent;
|
|
340
375
|
}
|
|
376
|
+
exports.buildRootComponent = buildRootComponent;
|
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": "4.
|
|
5
|
+
"version": "4.3.0-beta.2",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"description": "Build UIs with Actors using xstate and React",
|
|
8
8
|
"keywords": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@testing-library/dom": "^8.14.0",
|
|
35
35
|
"@testing-library/jest-dom": "^5.16.1",
|
|
36
36
|
"@testing-library/react": "^13.4.0",
|
|
37
|
-
"@testing-library/user-event": "^
|
|
37
|
+
"@testing-library/user-event": "^14.4.3",
|
|
38
38
|
"@types/history": "^4.7.7",
|
|
39
39
|
"@types/jest": "^28.1.4",
|
|
40
40
|
"@types/react": "^17.0.29",
|