@koordinates/xstate-tree 4.2.0 → 4.3.0-beta.10

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
@@ -1,13 +1,42 @@
1
- import { useMachine } from "@xstate/react";
2
- import memoize from "fast-memoize";
3
- import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
4
- import { TinyEmitter } from "tiny-emitter";
5
- import { handleLocationChange, RoutingContext, } from "./routing";
6
- import { useActiveRouteEvents } from "./routing/providers";
7
- import { useConstant } from "./useConstant";
8
- import { useService } from "./useService";
9
- import { assertIsDefined, isLikelyPageLoad } from "./utils";
10
- export const emitter = new TinyEmitter();
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
- export function broadcast(event) {
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
- export function onBroadcast(handler) {
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 = memoize((interpreter) => {
41
- return React.memo(function InterpreterView() {
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 React.createElement(XstateTreeView, { interpreter: interpreter });
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
- export const getMultiSlotViewForChildren = memoize((parent, slot) => {
61
- return React.memo(function MultiSlotView() {
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 (React.createElement(XstateTreeMultiSlotView, { childInterpreters: interpretersWeCareAbout }));
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 React.createElement(MultiView, null);
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 React.createElement(View, null);
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,31 +129,31 @@ function useSlots(interpreter, slots) {
98
129
  });
99
130
  }
100
131
  function XstateTreeMultiSlotView({ childInterpreters, }) {
101
- return (React.createElement(React.Fragment, null, childInterpreters.map((i) => (React.createElement(XstateTreeView, { key: i.id, interpreter: i })))));
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
- export function XstateTreeView({ interpreter }) {
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
  },
121
152
  // This is needed because the inState function needs to be recreated if the
122
153
  // current state the machine is in changes. But _only_ then
123
154
  // eslint-disable-next-line react-hooks/exhaustive-deps
124
- [current.value]);
125
- const selectorsProxy = useConstant(() => {
155
+ [current === null || current === void 0 ? void 0 : current.value]);
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:
@@ -156,27 +187,30 @@ export function XstateTreeView({ interpreter }) {
156
187
  ctx: current.context,
157
188
  canHandleEvent,
158
189
  inState,
190
+ meta: (0, utils_1.mergeMeta)(current.meta),
159
191
  });
160
192
  break;
161
193
  }
162
194
  switch ((_b = interpreter.machine.meta) === null || _b === void 0 ? void 0 : _b.builderVersion) {
163
195
  case 1:
164
196
  const ViewV1 = interpreter.machine.meta.view;
165
- return (React.createElement(ViewV1, { selectors: selectorsRef.current, actions: actions, slots: slots, inState: inState }));
197
+ return (react_2.default.createElement(ViewV1, { selectors: selectorsRef.current, actions: actions, slots: slots, inState: inState }));
166
198
  case 2:
167
199
  const ViewV2 = interpreter.machine.meta.View;
168
- return (React.createElement(ViewV2, { selectors: selectorsRef.current, actions: actions, slots: slots }));
200
+ return (react_2.default.createElement(ViewV2, { selectors: selectorsRef.current, actions: actions, slots: slots }));
169
201
  default:
170
202
  throw new Error("builderVersion not set");
171
203
  }
172
204
  }
205
+ exports.XstateTreeView = XstateTreeView;
173
206
  /**
174
207
  * @internal
175
208
  */
176
- export function recursivelySend(service, event) {
209
+ function recursivelySend(service, event) {
210
+ var _a;
177
211
  const children = ([...service.children.values()] || []).filter((s) => s.id.includes("-slot"));
178
212
  // If the service can't handle the event, don't send it
179
- if (service.state.nextEvents.includes(event.type)) {
213
+ if ((_a = service.getSnapshot()) === null || _a === void 0 ? void 0 : _a.nextEvents.includes(event.type)) {
180
214
  try {
181
215
  service.send(event);
182
216
  }
@@ -186,6 +220,7 @@ export function recursivelySend(service, event) {
186
220
  }
187
221
  children.forEach((child) => recursivelySend(child, event));
188
222
  }
223
+ exports.recursivelySend = recursivelySend;
189
224
  /**
190
225
  * @public
191
226
  *
@@ -194,7 +229,7 @@ export function recursivelySend(service, event) {
194
229
  * @param machine - The root machine of the tree
195
230
  * @param routing - The routing configuration for the tree
196
231
  */
197
- export function buildRootComponent(machine, routing) {
232
+ function buildRootComponent(machine, routing) {
198
233
  if (!machine.meta) {
199
234
  throw new Error("Root machine has no meta");
200
235
  }
@@ -211,23 +246,31 @@ export function buildRootComponent(machine, routing) {
211
246
  break;
212
247
  }
213
248
  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);
249
+ const [_, __, interpreter] = (0, react_1.useMachine)(machine, { devTools: true });
250
+ const [activeRoute, setActiveRoute] = (0, react_2.useState)(undefined);
251
+ const activeRouteEventsRef = (0, react_2.useRef)([]);
252
+ const [forceRenderValue, forceRender] = (0, react_2.useState)(false);
218
253
  const setActiveRouteEvents = (events) => {
219
254
  activeRouteEventsRef.current = events;
220
255
  };
221
- useEffect(() => {
256
+ const insideRoutingContext = (0, routing_1.useInRoutingContext)();
257
+ if (insideRoutingContext && typeof routing !== "undefined") {
258
+ const m = "Routing root rendered inside routing context, this implies a bug";
259
+ if (process.env.NODE_ENV !== "production") {
260
+ throw new Error(m);
261
+ }
262
+ console.error(m);
263
+ }
264
+ (0, react_2.useEffect)(() => {
222
265
  function handler(event) {
223
266
  recursivelySend(interpreter, event);
224
267
  }
225
- emitter.on("event", handler);
268
+ exports.emitter.on("event", handler);
226
269
  return () => {
227
- emitter.off("event", handler);
270
+ exports.emitter.off("event", handler);
228
271
  };
229
272
  }, [interpreter]);
230
- useEffect(() => {
273
+ (0, react_2.useEffect)(() => {
231
274
  if (activeRoute === undefined) {
232
275
  return;
233
276
  }
@@ -240,7 +283,7 @@ export function buildRootComponent(machine, routing) {
240
283
  }
241
284
  const routeEventPairs = [];
242
285
  const activeRoutesEvent = activeRouteEventsRef.current.find((e) => e.type === activeRoute.event);
243
- assertIsDefined(activeRoutesEvent);
286
+ (0, utils_1.assertIsDefined)(activeRoutesEvent);
244
287
  for (let i = 0; i < routes.length; i++) {
245
288
  const route = routes[i];
246
289
  const routeEvent = activeRouteEventsRef.current[i];
@@ -250,7 +293,7 @@ export function buildRootComponent(machine, routing) {
250
293
  return route.redirect !== undefined;
251
294
  });
252
295
  const redirectPromises = routePairsWithRedirects.map(([route, event]) => {
253
- assertIsDefined(route.redirect);
296
+ (0, utils_1.assertIsDefined)(route.redirect);
254
297
  return route.redirect({
255
298
  signal: controller.signal,
256
299
  query: event.query,
@@ -284,11 +327,16 @@ export function buildRootComponent(machine, routing) {
284
327
  controller.abort();
285
328
  };
286
329
  }, [activeRoute]);
287
- useEffect(() => {
330
+ (0, react_2.useEffect)(() => {
331
+ var _a, _b;
288
332
  if (routing) {
289
333
  const { getPathName = () => routing.history.location.pathname, getQueryString = () => routing.history.location.search, } = routing;
334
+ const initialMeta = {
335
+ ...((_b = (_a = routing.history.location.state) === null || _a === void 0 ? void 0 : _a.meta) !== null && _b !== void 0 ? _b : {}),
336
+ onloadEvent: (0, utils_1.isLikelyPageLoad)(),
337
+ };
290
338
  const queryString = getQueryString();
291
- const result = handleLocationChange(routing.routes, routing.basePath, getPathName(), getQueryString(), { onloadEvent: isLikelyPageLoad() });
339
+ const result = (0, routing_1.handleLocationChange)(routing.routes, routing.basePath, getPathName(), getQueryString(), initialMeta);
292
340
  if (result) {
293
341
  setActiveRouteEvents(result.events);
294
342
  setActiveRoute({ ...result.matchedRoute });
@@ -296,14 +344,16 @@ export function buildRootComponent(machine, routing) {
296
344
  // Hack to ensure the initial location doesn't have undefined state
297
345
  // It's not supposed to, but it does for some reason
298
346
  // And the history library ignores popstate events with undefined state
299
- routing.history.replace(`${getPathName()}${queryString}`, {});
347
+ routing.history.replace(`${getPathName()}${queryString}`, {
348
+ meta: initialMeta,
349
+ });
300
350
  }
301
351
  }, []);
302
- useEffect(() => {
352
+ (0, react_2.useEffect)(() => {
303
353
  if (routing) {
304
354
  const unsub = routing.history.listen((location) => {
305
355
  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);
356
+ 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
357
  if (result) {
308
358
  setActiveRouteEvents(result.events);
309
359
  setActiveRoute({ ...result.matchedRoute });
@@ -315,26 +365,29 @@ export function buildRootComponent(machine, routing) {
315
365
  }
316
366
  return undefined;
317
367
  }, []);
318
- const routingProviderValue = useMemo(() => {
368
+ const routingProviderValue = (0, react_2.useMemo)(() => {
369
+ // Just to satisfy linter, need this memo to be re-calculated on route changes
370
+ activeRoute;
319
371
  if (!routing) {
320
372
  return null;
321
373
  }
322
374
  return {
323
375
  activeRouteEvents: activeRouteEventsRef,
324
376
  };
325
- }, []);
377
+ }, [activeRoute]);
326
378
  if (!interpreter.initialized) {
327
379
  setTimeout(() => forceRender(!forceRenderValue), 0);
328
380
  return null;
329
381
  }
330
382
  if (routingProviderValue) {
331
- return (React.createElement(RoutingContext.Provider, { value: routingProviderValue },
332
- React.createElement(XstateTreeView, { interpreter: interpreter })));
383
+ return (react_2.default.createElement(routing_1.RoutingContext.Provider, { value: routingProviderValue },
384
+ react_2.default.createElement(XstateTreeView, { interpreter: interpreter })));
333
385
  }
334
386
  else {
335
- return React.createElement(XstateTreeView, { interpreter: interpreter });
387
+ return react_2.default.createElement(XstateTreeView, { interpreter: interpreter });
336
388
  }
337
389
  };
338
390
  RootComponent.rootMachine = machine;
339
391
  return RootComponent;
340
392
  }
393
+ 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.2.0",
5
+ "version": "4.3.0-beta.10",
6
6
  "license": "MIT",
7
7
  "description": "Build UIs with Actors using xstate and React",
8
8
  "keywords": [
@@ -34,9 +34,10 @@
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": "^13.5.0",
37
+ "@testing-library/user-event": "^14.4.3",
38
38
  "@types/history": "^4.7.7",
39
39
  "@types/jest": "^28.1.4",
40
+ "@types/node": "^20.4.9",
40
41
  "@types/react": "^17.0.29",
41
42
  "@types/react-dom": "^18.0.6",
42
43
  "@types/testing-library__jest-dom": "^5.14.1",
@@ -72,9 +73,9 @@
72
73
  },
73
74
  "peerDependencies": {
74
75
  "@xstate/react": "^3.x",
76
+ "react": ">= 16.8.0 < 19.0.0",
75
77
  "xstate": ">= 4.20 < 5.0.0",
76
- "zod": "^3.x",
77
- "react": ">= 16.8.0 < 19.0.0"
78
+ "zod": "^3.x"
78
79
  },
79
80
  "scripts": {
80
81
  "lint": "eslint 'src/**/*'",