@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/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
- // @ts-ignore fixed in v5 branch
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, { interpreter: interpreter });
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 = [...children.values()];
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) && i.initialized);
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.get(`${slot.toLowerCase()}-slot`);
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
- return (react_2.default.createElement(react_2.default.Fragment, null, childInterpreters.map((i) => (react_2.default.createElement(XstateTreeView, { key: i.id, interpreter: i })))));
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({ interpreter }) {
139
- var _a, _b;
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 } = interpreter.machine.meta;
145
- const slots = useSlots(interpreter, interpreterSlots.map((x) => x.name));
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 interpreter.getSnapshot().can(e);
148
- }, [interpreter]);
146
+ return actor.getSnapshot().can(e);
147
+ }, [actor]);
149
148
  const inState = (0, react_2.useCallback)((state) => {
150
- var _a, _b;
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 === null || current === void 0 ? void 0 : current.value]);
154
+ [current?.value]);
157
155
  const selectorsProxy = (0, useConstant_1.useConstant)(() => {
158
156
  return new Proxy({}, {
159
157
  get: (_target, prop) => {
160
- var _a;
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
- var _a;
167
- switch ((_a = interpreter.machine.meta) === null || _a === void 0 ? void 0 : _a.builderVersion) {
168
- case 1:
169
- return interpreter.machine.meta.actions(interpreter.send, selectorsProxy);
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
- switch ((_a = interpreter.machine.meta) === null || _a === void 0 ? void 0 : _a.builderVersion) {
183
- case 1:
184
- selectorsRef.current = interpreter.machine.meta.selectors(current.context, canHandleEvent, inState, current.value);
185
- break;
186
- case 2:
187
- selectorsRef.current = interpreter.machine.meta.selectors({
188
- ctx: current.context,
189
- canHandleEvent,
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
- var _a;
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 ((_a = service.getSnapshot()) === null || _a === void 0 ? void 0 : _a.nextEvents.includes(event.type)) {
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.machine.id, e);
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.meta) {
235
- throw new Error("Root machine has no meta");
207
+ if (!machine._xstateTree) {
208
+ throw new Error("Root machine is not an xstate-tree machine, missing metadata");
236
209
  }
237
- switch (machine.meta.builderVersion) {
238
- case 1:
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 [_, __, interpreter] = (0, react_1.useMachine)(machine, { devTools: true });
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: { ...((_a = activeRoutesEvent.query) !== null && _a !== void 0 ? _a : {}) },
322
- params: { ...((_b = activeRoutesEvent.params) !== null && _b !== void 0 ? _b : {}) },
323
- meta: { replace: true, ...((_c = activeRoutesEvent.meta) !== null && _c !== void 0 ? _c : {}) },
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
- ...((_b = (_a = routing.history.location.state) === null || _a === void 0 ? void 0 : _a.meta) !== null && _b !== void 0 ? _b : {}),
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
- var _a;
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, { interpreter: interpreter })));
364
+ react_2.default.createElement(XstateTreeView, { actor: interpreter })));
386
365
  }
387
366
  else {
388
- return react_2.default.createElement(XstateTreeView, { interpreter: interpreter });
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": "4.5.0",
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.30.5",
44
+ "@typescript-eslint/eslint-plugin": "^5.57.0",
45
45
  "@vitejs/plugin-react": "^2.1.0",
46
- "@xstate/immer": "^0.3.1",
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": "^4.7.3",
68
+ "typescript": "5.0.2",
70
69
  "vite": "^3.1.3",
71
70
  "vite-tsconfig-paths": "^3.5.0",
72
- "xstate": "^4.33.0"
71
+ "xstate": "^5.4.1"
73
72
  },
74
73
  "peerDependencies": {
75
- "@xstate/react": "^3.x",
74
+ "@xstate/react": "^4.x",
76
75
  "react": ">= 16.8.0 < 19.0.0",
77
- "xstate": ">= 4.20 < 5.0.0",
76
+ "xstate": "^5.x",
78
77
  "zod": "^3.x"
79
78
  },
80
79
  "scripts": {