@xmachines/play-xstate 6.1.0 → 7.0.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/README.md +64 -12
- package/dist/capabilities.js +6 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/define-player.js +8 -8
- package/dist/define-player.js.map +1 -1
- package/dist/errors.js +262 -259
- package/dist/errors.js.map +1 -1
- package/dist/index.js +4 -25
- package/dist/player-actor.js +318 -448
- package/dist/player-actor.js.map +1 -1
- package/dist/routing/build-url.js +74 -108
- package/dist/routing/build-url.js.map +1 -1
- package/dist/routing/derive-current-route.d.ts.map +1 -1
- package/dist/routing/derive-current-route.js +46 -67
- package/dist/routing/derive-current-route.js.map +1 -1
- package/dist/routing/derive-initial-route.js +53 -64
- package/dist/routing/derive-initial-route.js.map +1 -1
- package/dist/routing/derive-route.js +225 -242
- package/dist/routing/derive-route.js.map +1 -1
- package/dist/routing/format-play-route-transitions.js +192 -258
- package/dist/routing/format-play-route-transitions.js.map +1 -1
- package/dist/state-meta.d.ts +56 -25
- package/dist/state-meta.d.ts.map +1 -1
- package/dist/state-meta.js +164 -105
- package/dist/state-meta.js.map +1 -1
- package/dist/view/derive-current-view.d.ts +18 -10
- package/dist/view/derive-current-view.d.ts.map +1 -1
- package/dist/view/derive-current-view.js +243 -221
- package/dist/view/derive-current-view.js.map +1 -1
- package/dist/with-routing.js +94 -136
- package/dist/with-routing.js.map +1 -1
- package/dist/with-view.d.ts.map +1 -1
- package/dist/with-view.js +82 -152
- package/dist/with-view.js.map +1 -1
- package/package.json +19 -19
- package/dist/index.js.map +0 -1
- package/dist/routing/index.js +0 -14
- package/dist/routing/index.js.map +0 -1
- package/dist/routing/types.js +0 -2
- package/dist/routing/types.js.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
|
@@ -1,271 +1,205 @@
|
|
|
1
|
-
import { assign } from "xstate";
|
|
2
|
-
import { shallowEqualExcept } from "@xmachines/core/utils";
|
|
3
1
|
import { MissingStateIdError } from "../errors.js";
|
|
4
2
|
import { normalizeRoute, routeData, routeHandler, routeReenter } from "./derive-route.js";
|
|
3
|
+
import { shallowEqualExcept } from "@xmachines/core/utils";
|
|
4
|
+
import { assign } from "xstate";
|
|
5
|
+
//#region packages/play-xstate/src/routing/format-play-route-transitions.ts
|
|
5
6
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
* Reuses the previous container of the params and of the query when the new
|
|
8
|
+
* container is equal to it in a shallow comparison.
|
|
9
|
+
*
|
|
10
|
+
* The params and the query of a route arrive in a FRESH object on every `play.route`
|
|
11
|
+
* event: a bridge builds them again for each parse of a URL, and `|| {}` allocates
|
|
12
|
+
* an object also for the empty case. The emit gate of the view compares the context
|
|
13
|
+
* field by field, with `Object.is`. Without this reuse, a navigation to the same
|
|
14
|
+
* values, such as a popstate event to the current URL or a second send from the
|
|
15
|
+
* code, emits the view again for nothing.
|
|
16
|
+
*/
|
|
17
|
+
var keepEqualContainer = (prev, next) => prev !== void 0 && shallowEqualExcept(prev, next) ? prev : next;
|
|
17
18
|
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
* Normalizes the `play.route` entry of an `on` record into an array.
|
|
20
|
+
*
|
|
21
|
+
* A user writes one transition or an array of them, and XState accepts both forms.
|
|
22
|
+
* The merge therefore needs one form.
|
|
23
|
+
*/
|
|
24
|
+
var asTransitionArray = (value) => value === void 0 ? [] : Array.isArray(value) ? value : [value];
|
|
24
25
|
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
* True when the route declares a `data` field.
|
|
27
|
+
*
|
|
28
|
+
* The assigner below reads this ONE time, when it builds the action, and not on every
|
|
29
|
+
* event: the declaration of a state is static.
|
|
30
|
+
*/
|
|
31
|
+
var declaresRouteData = (route) => typeof route === "object" && route !== null && route.data !== void 0;
|
|
31
32
|
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
* The `params` and the `query` of the event, each beside the container that the context
|
|
34
|
+
* holds already. Neither one reads the metadata of the route, so both stand here rather
|
|
35
|
+
* than inside the factory, where a call rebuilt them for every generated transition.
|
|
36
|
+
*/
|
|
37
|
+
var assignParams = ({ context, event }) => keepEqualContainer(context.params, event.params || {});
|
|
38
|
+
var assignQuery = ({ context, event }) => keepEqualContainer(context.query, event.query || {});
|
|
38
39
|
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
40
|
+
* Builds the assign action of a generated `play.route` transition.
|
|
41
|
+
*
|
|
42
|
+
* Every generated transition gets its OWN action object, from this one factory. The
|
|
43
|
+
* root transition and the local transition of the same state therefore hold no shared
|
|
44
|
+
* object, and the body of the assign lives in one place.
|
|
45
|
+
*
|
|
46
|
+
* A route that declares no `data` gets an assign with no `data` KEY. A key that writes
|
|
47
|
+
* the previous value back is not the same thing: `assign` puts an own property on the
|
|
48
|
+
* context for each key that it carries, so a machine that declares no `data` field
|
|
49
|
+
* received one with no value, and the `/context` projection of every view carried it.
|
|
50
|
+
*/
|
|
51
|
+
var makeRouteAssign = (routeMeta) => {
|
|
52
|
+
if (!declaresRouteData(routeMeta)) return assign({
|
|
53
|
+
params: assignParams,
|
|
54
|
+
query: assignQuery
|
|
55
|
+
});
|
|
56
|
+
return assign({
|
|
57
|
+
params: assignParams,
|
|
58
|
+
query: assignQuery,
|
|
59
|
+
data: ({ context, event }) => {
|
|
60
|
+
const previous = context.data;
|
|
61
|
+
const next = routeData(routeMeta, {
|
|
62
|
+
context,
|
|
63
|
+
event
|
|
64
|
+
});
|
|
65
|
+
return next === void 0 ? previous : keepEqualContainer(previous, next);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
64
68
|
};
|
|
65
69
|
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
70
|
+
* Rebuilds one state node with its local `play.route` transitions in place.
|
|
71
|
+
*
|
|
72
|
+
* The transitions of the user stay, and they come AFTER the generated ones: XState
|
|
73
|
+
* reads the candidates in their order, so a generated guard wins when it matches, and
|
|
74
|
+
* the transitions of the user are the fallback. This is the rule that the root of the
|
|
75
|
+
* machine follows too.
|
|
76
|
+
*
|
|
77
|
+
* The function writes into NO object of the caller. It returns a new node.
|
|
78
|
+
*/
|
|
79
|
+
var withLocalTransitions = (node, states, transitions) => {
|
|
80
|
+
if (transitions.length === 0) return {
|
|
81
|
+
...node,
|
|
82
|
+
states
|
|
83
|
+
};
|
|
84
|
+
const existingOn = node.on ?? {};
|
|
85
|
+
return {
|
|
86
|
+
...node,
|
|
87
|
+
states,
|
|
88
|
+
on: {
|
|
89
|
+
...existingOn,
|
|
90
|
+
"play.route": [...transitions, ...asTransitionArray(existingOn["play.route"])]
|
|
91
|
+
}
|
|
92
|
+
};
|
|
87
93
|
};
|
|
88
94
|
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (node.states) {
|
|
196
|
-
const child = collectRoutes(node.states, statePath);
|
|
197
|
-
if (child.changed || child.localTransitions.length > 0) {
|
|
198
|
-
// Rebuild this node, and do NOT write into the config of the caller. The
|
|
199
|
-
// function returns a new config, and the original must stay untouched.
|
|
200
|
-
nextNode = withLocalTransitions(node, child.states, child.localTransitions);
|
|
201
|
-
changed = true;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return [key, nextNode];
|
|
205
|
-
});
|
|
206
|
-
return { states: Object.fromEntries(nextEntries), localTransitions, changed };
|
|
207
|
-
};
|
|
208
|
-
const machineStates = machineConfig.states;
|
|
209
|
-
// `states` comes back rebuilt where a descendant asked for a local handler, and it
|
|
210
|
-
// comes back as the original record where none did. The walk returns no local
|
|
211
|
-
// transition for the top level: the parent of a top-level state IS the root, and the
|
|
212
|
-
// root transition of such a state is already local to its parent.
|
|
213
|
-
const walk = machineStates ? collectRoutes(machineStates) : undefined;
|
|
214
|
-
// The ROOT of the machine can declare a route of its own, which a chart does when
|
|
215
|
-
// its whole document owns a path prefix, for example `/doc` with `/doc/a` under it.
|
|
216
|
-
// `collectRoutes` walks `states` only, so without this block the root gets no
|
|
217
|
-
// transition, and a URL for the root moves the machine nowhere: a press on BACK to
|
|
218
|
-
// `/doc` leaves the actor on the child it already holds, and the URL and the actor
|
|
219
|
-
// diverge.
|
|
220
|
-
//
|
|
221
|
-
// The transition targets the root by its ID. XState enters the `initial` child of
|
|
222
|
-
// a compound target, which is the state that a URL for the root means. The `reenter`
|
|
223
|
-
// flag does NOT decide that: a route to the root lands on the initial child of the
|
|
224
|
-
// root under `false` and under `true`. The flag decides only whether the root runs
|
|
225
|
-
// its own `exit` and `entry` actions again. The root therefore reads the flag from
|
|
226
|
-
// its `meta.route` like every other state, and it defaults to `false`.
|
|
227
|
-
const rootNode = machineConfig;
|
|
228
|
-
const rootRouteMeta = rootNode.meta?.route;
|
|
229
|
-
const rootRoutePath = rootRouteMeta === undefined
|
|
230
|
-
? ""
|
|
231
|
-
: normalizeRoute(rootRouteMeta, "formatPlayRouteTransitions");
|
|
232
|
-
// The placement of the ROOT has no meaning, because the root has no parent to hold a
|
|
233
|
-
// local transition. A value that the routing layer does not know must still fail
|
|
234
|
-
// here, so that one typo behaves the same wherever an author writes it.
|
|
235
|
-
routeHandler(rootRouteMeta);
|
|
236
|
-
if (rootRoutePath) {
|
|
237
|
-
if (!rootNode.id)
|
|
238
|
-
throw new MissingStateIdError("(the root of the machine)", rootRoutePath);
|
|
239
|
-
routeTransitions.push({
|
|
240
|
-
target: `#${rootNode.id}`,
|
|
241
|
-
guard: ({ event }) => event.to === `#${rootNode.id}`,
|
|
242
|
-
reenter: routeReenter(rootRouteMeta),
|
|
243
|
-
actions: makeRouteAssign(rootRouteMeta),
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
const rebuiltStates = walk?.changed === true;
|
|
247
|
-
// A machine whose every routable state declares `handler: "local"` produces NO root
|
|
248
|
-
// transition, and its transitions live on the rebuilt states alone. The rebuilt
|
|
249
|
-
// states must therefore survive on their own, and not only beside a root
|
|
250
|
-
// transition.
|
|
251
|
-
if (routeTransitions.length === 0 && !rebuiltStates) {
|
|
252
|
-
return machineConfig;
|
|
253
|
-
}
|
|
254
|
-
const next = {
|
|
255
|
-
...machineConfig,
|
|
256
|
-
...(rebuiltStates ? { states: walk?.states } : {}),
|
|
257
|
-
};
|
|
258
|
-
if (routeTransitions.length > 0) {
|
|
259
|
-
const existingOn = machineConfig.on || {};
|
|
260
|
-
// Keep each play.route transition of the user, for example a 404 fallback that a
|
|
261
|
-
// person wrote, and append those transitions AFTER the generated ones: XState
|
|
262
|
-
// evaluates the candidate transitions in their order. Therefore a generated guard
|
|
263
|
-
// wins when it matches, and the transitions of the user are the fallback.
|
|
264
|
-
next["on"] = {
|
|
265
|
-
...existingOn,
|
|
266
|
-
"play.route": [...routeTransitions, ...asTransitionArray(existingOn["play.route"])],
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
return next;
|
|
95
|
+
* Makes the play.route transitions from the declarative route configs
|
|
96
|
+
*
|
|
97
|
+
* The function walks the machine states. It looks for each state with a `meta.route`
|
|
98
|
+
* field, and it generates the transitions that handle a `play.route` event: each
|
|
99
|
+
* transition matches event.to against a state ID.
|
|
100
|
+
*
|
|
101
|
+
* The internal formatRouteTransitions function of XState was the model for this
|
|
102
|
+
* function (stateUtils.ts, line 391).
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const machineConfig = {
|
|
107
|
+
* id: "myMachine",
|
|
108
|
+
* states: {
|
|
109
|
+
* home: { id: "home", meta: { route: "/home" } },
|
|
110
|
+
* dashboard: { id: "dashboard", meta: { route: "/dashboard" } }
|
|
111
|
+
* }
|
|
112
|
+
* };
|
|
113
|
+
*
|
|
114
|
+
* const machine = createMachine(formatPlayRouteTransitions(machineConfig));
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* The function generates the `play.route` handlers at the root level. Those
|
|
118
|
+
* handlers:
|
|
119
|
+
* - match event.to against a state ID, for example event.to === "#home"
|
|
120
|
+
* - target the correct state
|
|
121
|
+
* - assign the params and the query of the event to the context
|
|
122
|
+
*
|
|
123
|
+
* @param machineConfig - The XState machine config, before `createMachine`. It must extend `RouteMachineConfig`.
|
|
124
|
+
* @returns The same machine config, with the generated `play.route` handlers in it. The original type `T` stays.
|
|
125
|
+
*/
|
|
126
|
+
function formatPlayRouteTransitions(machineConfig) {
|
|
127
|
+
const routeTransitions = [];
|
|
128
|
+
const collectRoutes = (states, parentPath = "") => {
|
|
129
|
+
const localTransitions = [];
|
|
130
|
+
let changed = false;
|
|
131
|
+
const nextEntries = Object.entries(states).map(([key, stateConfig]) => {
|
|
132
|
+
const node = stateConfig;
|
|
133
|
+
const statePath = parentPath ? `${parentPath}.${key}` : key;
|
|
134
|
+
const routeMeta = node.meta?.route;
|
|
135
|
+
const routePath = routeMeta === void 0 ? "" : normalizeRoute(routeMeta, "formatPlayRouteTransitions");
|
|
136
|
+
const placement = routeHandler(routeMeta);
|
|
137
|
+
if (routePath && !node.id) throw new MissingStateIdError(key, routePath);
|
|
138
|
+
if (routePath && node.id) {
|
|
139
|
+
const reenter = routeReenter(routeMeta);
|
|
140
|
+
const parentIsRoot = parentPath === "";
|
|
141
|
+
const wantsRoot = parentIsRoot || placement === "root" || placement === "both";
|
|
142
|
+
const wantsLocal = !parentIsRoot && (placement === "local" || placement === "both");
|
|
143
|
+
if (wantsRoot) routeTransitions.push({
|
|
144
|
+
target: `.${statePath}`,
|
|
145
|
+
guard: ({ event }) => event.to === `#${node.id}`,
|
|
146
|
+
reenter,
|
|
147
|
+
actions: makeRouteAssign(routeMeta)
|
|
148
|
+
});
|
|
149
|
+
if (wantsLocal) localTransitions.push({
|
|
150
|
+
target: `.${key}`,
|
|
151
|
+
guard: ({ event }) => event.to === `#${node.id}`,
|
|
152
|
+
reenter,
|
|
153
|
+
actions: makeRouteAssign(routeMeta)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
let nextNode = stateConfig;
|
|
157
|
+
if (node.states) {
|
|
158
|
+
const child = collectRoutes(node.states, statePath);
|
|
159
|
+
if (child.changed || child.localTransitions.length > 0) {
|
|
160
|
+
nextNode = withLocalTransitions(node, child.states, child.localTransitions);
|
|
161
|
+
changed = true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return [key, nextNode];
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
states: Object.fromEntries(nextEntries),
|
|
168
|
+
localTransitions,
|
|
169
|
+
changed
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
const machineStates = machineConfig.states;
|
|
173
|
+
const walk = machineStates ? collectRoutes(machineStates) : void 0;
|
|
174
|
+
const rootNode = machineConfig;
|
|
175
|
+
const rootRouteMeta = rootNode.meta?.route;
|
|
176
|
+
const rootRoutePath = rootRouteMeta === void 0 ? "" : normalizeRoute(rootRouteMeta, "formatPlayRouteTransitions");
|
|
177
|
+
routeHandler(rootRouteMeta);
|
|
178
|
+
if (rootRoutePath) {
|
|
179
|
+
if (!rootNode.id) throw new MissingStateIdError("(the root of the machine)", rootRoutePath);
|
|
180
|
+
routeTransitions.push({
|
|
181
|
+
target: `#${rootNode.id}`,
|
|
182
|
+
guard: ({ event }) => event.to === `#${rootNode.id}`,
|
|
183
|
+
reenter: routeReenter(rootRouteMeta),
|
|
184
|
+
actions: makeRouteAssign(rootRouteMeta)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const rebuiltStates = walk?.changed === true;
|
|
188
|
+
if (routeTransitions.length === 0 && !rebuiltStates) return machineConfig;
|
|
189
|
+
const next = {
|
|
190
|
+
...machineConfig,
|
|
191
|
+
...rebuiltStates ? { states: walk?.states } : {}
|
|
192
|
+
};
|
|
193
|
+
if (routeTransitions.length > 0) {
|
|
194
|
+
const existingOn = machineConfig.on || {};
|
|
195
|
+
next["on"] = {
|
|
196
|
+
...existingOn,
|
|
197
|
+
"play.route": [...routeTransitions, ...asTransitionArray(existingOn["play.route"])]
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return next;
|
|
270
201
|
}
|
|
202
|
+
//#endregion
|
|
203
|
+
export { formatPlayRouteTransitions };
|
|
204
|
+
|
|
271
205
|
//# sourceMappingURL=format-play-route-transitions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-play-route-transitions.js","sourceRoot":"","sources":["../../src/routing/format-play-route-transitions.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format-play-route-transitions.js","names":[],"sources":["../../src/routing/format-play-route-transitions.ts"],"sourcesContent":["import { assign } from \"xstate\";\nimport type { PlayRouteEvent, RouteData } from \"@xmachines/play-router\";\nimport { shallowEqualExcept } from \"@xmachines/core/utils\";\nimport { MissingStateIdError } from \"../errors.js\";\nimport { normalizeRoute, routeData, routeHandler, routeReenter } from \"./derive-route.js\";\nimport type { RouteMetadata, RouteObject } from \"./types.js\";\n\n/**\n * Reuses the previous container of the params and of the query when the new\n * container is equal to it in a shallow comparison.\n *\n * The params and the query of a route arrive in a FRESH object on every `play.route`\n * event: a bridge builds them again for each parse of a URL, and `|| {}` allocates\n * an object also for the empty case. The emit gate of the view compares the context\n * field by field, with `Object.is`. Without this reuse, a navigation to the same\n * values, such as a popstate event to the current URL or a second send from the\n * code, emits the view again for nothing.\n */\nconst keepEqualContainer = <T extends Record<string, unknown>>(prev: T | undefined, next: T): T =>\n\tprev !== undefined && shallowEqualExcept(prev, next) ? prev : next;\n\n/**\n * The minimal structural shape of one XState state node, as\n * `formatPlayRouteTransitions` reads it during its walk over the machine config.\n *\n * This type holds only the fields that the function reads. Every other field of a\n * state node, such as `on`, `entry`, and `after`, passes through the index\n * signature without a change.\n */\nexport type RouteStateNode = {\n\t/**\n\t * The optional explicit state ID, for example `\"home\"` or `\"settings\"`. It is the `#id` target of a `play.route` event.\n\t */\n\tid?: string;\n\t/** The state metadata. A `meta.route` field gives the state a route. */\n\tmeta?: {\n\t\t/**\n\t\t * The template of the URL path: the string form, for example\n\t\t * `\"/profile/:username\"`, or the object form (`{ path, reenter, title }`). Both\n\t\t * match {@link RouteMetadata}. The object form declares the `reenter` flag of the\n\t\t * generated transition, and that flag defaults to `false`.\n\t\t */\n\t\troute?: RouteMetadata;\n\t};\n\t/** The nested child states. The function walks them for each further route declaration. */\n\tstates?: Record<string, RouteStateNode>;\n\t/**\n\t * The event handlers of this state. The function reads them where a child asks for a\n\t * local `play.route` handler, so that the transitions of the user survive the merge.\n\t */\n\ton?: Record<string, unknown> | undefined;\n\t[key: string]: unknown;\n};\n\n/**\n * Normalizes the `play.route` entry of an `on` record into an array.\n *\n * A user writes one transition or an array of them, and XState accepts both forms.\n * The merge therefore needs one form.\n */\nconst asTransitionArray = (value: unknown): unknown[] =>\n\tvalue === undefined ? [] : Array.isArray(value) ? value : [value];\n\n/** The arguments that XState gives to the assigner of a generated transition. */\ntype RouteAssignArgs = { context: unknown; event: PlayRouteEvent };\n\n/**\n * True when the route declares a `data` field.\n *\n * The assigner below reads this ONE time, when it builds the action, and not on every\n * event: the declaration of a state is static.\n */\nconst declaresRouteData = (route: RouteMetadata | undefined): boolean =>\n\ttypeof route === \"object\" && route !== null && (route as RouteObject).data !== undefined;\n\n/**\n * The `params` and the `query` of the event, each beside the container that the context\n * holds already. Neither one reads the metadata of the route, so both stand here rather\n * than inside the factory, where a call rebuilt them for every generated transition.\n */\nconst assignParams = ({ context, event }: RouteAssignArgs) =>\n\tkeepEqualContainer((context as { params?: Record<string, string> }).params, event.params || {});\n\nconst assignQuery = ({ context, event }: RouteAssignArgs) =>\n\tkeepEqualContainer((context as { query?: Record<string, string> }).query, event.query || {});\n\n/**\n * Builds the assign action of a generated `play.route` transition.\n *\n * Every generated transition gets its OWN action object, from this one factory. The\n * root transition and the local transition of the same state therefore hold no shared\n * object, and the body of the assign lives in one place.\n *\n * A route that declares no `data` gets an assign with no `data` KEY. A key that writes\n * the previous value back is not the same thing: `assign` puts an own property on the\n * context for each key that it carries, so a machine that declares no `data` field\n * received one with no value, and the `/context` projection of every view carried it.\n */\nconst makeRouteAssign = (routeMeta: RouteMetadata | undefined) => {\n\tif (!declaresRouteData(routeMeta)) return assign({ params: assignParams, query: assignQuery });\n\n\treturn assign({\n\t\tparams: assignParams,\n\t\tquery: assignQuery,\n\t\t// A resolver that answers `undefined` leaves the field of the context as it stands,\n\t\t// and it therefore writes `undefined` over the data of an earlier route never.\n\t\tdata: ({ context, event }: RouteAssignArgs) => {\n\t\t\tconst previous = (context as { data?: RouteData }).data;\n\t\t\tconst next = routeData(routeMeta, { context, event });\n\t\t\treturn next === undefined ? previous : keepEqualContainer(previous, next);\n\t\t},\n\t});\n};\n\n/**\n * Rebuilds one state node with its local `play.route` transitions in place.\n *\n * The transitions of the user stay, and they come AFTER the generated ones: XState\n * reads the candidates in their order, so a generated guard wins when it matches, and\n * the transitions of the user are the fallback. This is the rule that the root of the\n * machine follows too.\n *\n * The function writes into NO object of the caller. It returns a new node.\n */\nconst withLocalTransitions = (\n\tnode: RouteStateNode,\n\tstates: Record<string, unknown>,\n\ttransitions: RouteTransition[],\n): unknown => {\n\tif (transitions.length === 0) return { ...node, states };\n\n\tconst existingOn = node.on ?? {};\n\treturn {\n\t\t...node,\n\t\tstates,\n\t\ton: {\n\t\t\t...existingOn,\n\t\t\t\"play.route\": [...transitions, ...asTransitionArray(existingOn[\"play.route\"])],\n\t\t},\n\t};\n};\n\ntype RouteGuardArgs = {\n\tevent: PlayRouteEvent;\n};\n\ntype RouteTransition = {\n\ttarget: string;\n\tguard: (args: RouteGuardArgs) => boolean;\n\t/**\n\t * The `reenter` flag of this transition. `meta.route` declares it, and the default\n\t * is `false`. The type is `boolean` and not the literal `true`, because the\n\t * declaration of the state, and not this function, decides the value.\n\t */\n\treenter: boolean;\n\t/**\n\t * The assign action of this transition. The type comes from\n\t * {@link makeRouteAssign}, and not from the bare `ReturnType<typeof assign>`: the\n\t * bare form widens the event of the action to `AnyEventObject`, and `event.params`\n\t * is then `any`. This form keeps `PlayRouteEvent`, and the compiler therefore\n\t * checks the body of the assign.\n\t */\n\tactions: ReturnType<typeof makeRouteAssign>;\n};\n\n/**\n * The minimal structural constraint of a machine config that\n * `formatPlayRouteTransitions` accepts.\n *\n * The constraint is loose on purpose. The function therefore accepts the bare config\n * object of `createMachine` and also the stricter config of\n * `setup().createMachine`, and the call site needs no type cast. The generic\n * parameter `T extends RouteMachineConfig` of `formatPlayRouteTransitions` keeps the\n * original concrete type through the transform. Therefore\n * `setup().createMachine()` accepts the return value directly.\n */\nexport type RouteMachineConfig = {\n\tcontext?: unknown;\n\tstates?: Record<string, unknown> | undefined;\n\ton?: Record<string, unknown> | undefined;\n\t[key: string]: unknown;\n};\n\n/**\n * Makes the play.route transitions from the declarative route configs\n *\n * The function walks the machine states. It looks for each state with a `meta.route`\n * field, and it generates the transitions that handle a `play.route` event: each\n * transition matches event.to against a state ID.\n *\n * The internal formatRouteTransitions function of XState was the model for this\n * function (stateUtils.ts, line 391).\n *\n * @example\n * ```typescript\n * const machineConfig = {\n * id: \"myMachine\",\n * states: {\n * home: { id: \"home\", meta: { route: \"/home\" } },\n * dashboard: { id: \"dashboard\", meta: { route: \"/dashboard\" } }\n * }\n * };\n *\n * const machine = createMachine(formatPlayRouteTransitions(machineConfig));\n * ```\n *\n * The function generates the `play.route` handlers at the root level. Those\n * handlers:\n * - match event.to against a state ID, for example event.to === \"#home\"\n * - target the correct state\n * - assign the params and the query of the event to the context\n *\n * @param machineConfig - The XState machine config, before `createMachine`. It must extend `RouteMachineConfig`.\n * @returns The same machine config, with the generated `play.route` handlers in it. The original type `T` stays.\n */\nexport function formatPlayRouteTransitions<T extends RouteMachineConfig>(machineConfig: T): T {\n\tconst routeTransitions: RouteTransition[] = [];\n\n\t/**\n\t * The result of the walk over one `states` record.\n\t *\n\t * `localTransitions` holds the transitions that belong on the node that OWNS the\n\t * record, and not on the nodes inside it. `states` is the record again, rebuilt\n\t * where a descendant asked for a local handler. `changed` tells the caller whether\n\t * it must keep the rebuilt record.\n\t */\n\ttype WalkResult = {\n\t\tstates: Record<string, unknown>;\n\t\tlocalTransitions: RouteTransition[];\n\t\tchanged: boolean;\n\t};\n\n\tconst collectRoutes = (states: Record<string, unknown>, parentPath = \"\"): WalkResult => {\n\t\tconst localTransitions: RouteTransition[] = [];\n\t\tlet changed = false;\n\n\t\t// The walk collects PAIRS, and `Object.fromEntries` builds the record at the end.\n\t\t// A write through `nextStates[key]` would reach the `__proto__` setter of\n\t\t// `Object.prototype` for a state whose key is `\"__proto__\"`, and it would set the\n\t\t// prototype of the record instead of adding a state to it. `fromEntries` defines\n\t\t// an own property for every key, so no key of a config is special.\n\t\tconst nextEntries = Object.entries(states).map(([key, stateConfig]): [string, unknown] => {\n\t\t\tconst node = stateConfig as RouteStateNode;\n\t\t\t// Build the complete path of the keys, from the root, for the target of the\n\t\t\t// transition. An XState target uses the hierarchy of the state keys, and not the\n\t\t\t// explicit IDs.\n\t\t\tconst statePath = parentPath ? `${parentPath}.${key}` : key;\n\t\t\t// The function checks the metadata, and a malformed object route therefore throws an\n\t\t\t// InvalidRouteMetadataError. It then normalizes the value into the string of the\n\t\t\t// path. An empty path marks the state as a state without a route, in the string form\n\t\t\t// (\"\") and also in the object form ({ path: \"\" }).\n\t\t\tconst routeMeta = node.meta?.route;\n\t\t\tconst routePath =\n\t\t\t\trouteMeta === undefined\n\t\t\t\t\t? \"\"\n\t\t\t\t\t: normalizeRoute(routeMeta, \"formatPlayRouteTransitions\");\n\n\t\t\t// Check the placement for EVERY declared route, and not only for one that\n\t\t\t// produces a transition. A state with an empty path generates none, and a typo\n\t\t\t// in its `handler` would otherwise wait silently until somebody gives the state\n\t\t\t// a path.\n\t\t\tconst placement = routeHandler(routeMeta);\n\n\t\t\tif (routePath && !node.id) {\n\t\t\t\tthrow new MissingStateIdError(key, routePath);\n\t\t\t}\n\n\t\t\tif (routePath && node.id) {\n\t\t\t\t// The declaration of the state decides the flag, and the default is `false`,\n\t\t\t\t// which is the default of XState. The flag spares the DOMAIN of the\n\t\t\t\t// transition: under the root placement it spares the root alone, and each\n\t\t\t\t// ancestor between the root and the target still runs its exit and its entry\n\t\t\t\t// actions. `handler` is the field that spares those.\n\t\t\t\tconst reenter = routeReenter(routeMeta);\n\n\t\t\t\t// A parent path of \"\" means that the parent IS the root. The root transition of\n\t\t\t\t// such a state already targets `.${key}`, so it IS the local transition, and\n\t\t\t\t// every placement therefore collapses to the root transition.\n\t\t\t\tconst parentIsRoot = parentPath === \"\";\n\t\t\t\tconst wantsRoot = parentIsRoot || placement === \"root\" || placement === \"both\";\n\t\t\t\tconst wantsLocal = !parentIsRoot && (placement === \"local\" || placement === \"both\");\n\n\t\t\t\t// The ROOT transition. XState installs every route transition on the root, and\n\t\t\t\t// this function keeps that placement as its default: a route from ANY state\n\t\t\t\t// therefore reaches this state.\n\t\t\t\tif (wantsRoot) {\n\t\t\t\t\trouteTransitions.push({\n\t\t\t\t\t\ttarget: `.${statePath}`,\n\t\t\t\t\t\tguard: ({ event }) => event.to === `#${node.id}`,\n\t\t\t\t\t\treenter,\n\t\t\t\t\t\tactions: makeRouteAssign(routeMeta),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// The LOCAL transition. It sits on the PARENT of this state, and it targets\n\t\t\t\t// this state relatively. XState offers the transitions of the deepest active\n\t\t\t\t// state node first, so this transition answers the event while the parent is\n\t\t\t\t// active. The parent therefore runs its exit and its entry actions again only\n\t\t\t\t// where the state declares `reenter: true`.\n\t\t\t\t//\n\t\t\t\t// Under `\"local\"` this is the ONLY transition of the state, so a route that\n\t\t\t\t// arrives while the parent is inactive matches nothing and the actor does not\n\t\t\t\t// move. That is the declared intent of `\"local\"`: it scopes the route.\n\t\t\t\tif (wantsLocal) {\n\t\t\t\t\tlocalTransitions.push({\n\t\t\t\t\t\ttarget: `.${key}`,\n\t\t\t\t\t\tguard: ({ event }) => event.to === `#${node.id}`,\n\t\t\t\t\t\treenter,\n\t\t\t\t\t\tactions: makeRouteAssign(routeMeta),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet nextNode: unknown = stateConfig;\n\n\t\t\tif (node.states) {\n\t\t\t\tconst child = collectRoutes(node.states, statePath);\n\t\t\t\tif (child.changed || child.localTransitions.length > 0) {\n\t\t\t\t\t// Rebuild this node, and do NOT write into the config of the caller. The\n\t\t\t\t\t// function returns a new config, and the original must stay untouched.\n\t\t\t\t\tnextNode = withLocalTransitions(node, child.states, child.localTransitions);\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn [key, nextNode];\n\t\t});\n\n\t\treturn { states: Object.fromEntries(nextEntries), localTransitions, changed };\n\t};\n\n\tconst machineStates = machineConfig.states;\n\t// `states` comes back rebuilt where a descendant asked for a local handler, and it\n\t// comes back as the original record where none did. The walk returns no local\n\t// transition for the top level: the parent of a top-level state IS the root, and the\n\t// root transition of such a state is already local to its parent.\n\tconst walk = machineStates ? collectRoutes(machineStates) : undefined;\n\n\t// The ROOT of the machine can declare a route of its own, which a chart does when\n\t// its whole document owns a path prefix, for example `/doc` with `/doc/a` under it.\n\t// `collectRoutes` walks `states` only, so without this block the root gets no\n\t// transition, and a URL for the root moves the machine nowhere: a press on BACK to\n\t// `/doc` leaves the actor on the child it already holds, and the URL and the actor\n\t// diverge.\n\t//\n\t// The transition targets the root by its ID. XState enters the `initial` child of\n\t// a compound target, which is the state that a URL for the root means. The `reenter`\n\t// flag does NOT decide that: a route to the root lands on the initial child of the\n\t// root under `false` and under `true`. The flag decides only whether the root runs\n\t// its own `exit` and `entry` actions again. The root therefore reads the flag from\n\t// its `meta.route` like every other state, and it defaults to `false`.\n\tconst rootNode = machineConfig as RouteStateNode;\n\tconst rootRouteMeta = rootNode.meta?.route;\n\tconst rootRoutePath =\n\t\trootRouteMeta === undefined\n\t\t\t? \"\"\n\t\t\t: normalizeRoute(rootRouteMeta, \"formatPlayRouteTransitions\");\n\n\t// The placement of the ROOT has no meaning, because the root has no parent to hold a\n\t// local transition. A value that the routing layer does not know must still fail\n\t// here, so that one typo behaves the same wherever an author writes it.\n\trouteHandler(rootRouteMeta);\n\n\tif (rootRoutePath) {\n\t\tif (!rootNode.id) throw new MissingStateIdError(\"(the root of the machine)\", rootRoutePath);\n\n\t\trouteTransitions.push({\n\t\t\ttarget: `#${rootNode.id}`,\n\t\t\tguard: ({ event }) => event.to === `#${rootNode.id}`,\n\t\t\treenter: routeReenter(rootRouteMeta),\n\t\t\tactions: makeRouteAssign(rootRouteMeta),\n\t\t});\n\t}\n\n\tconst rebuiltStates = walk?.changed === true;\n\n\t// A machine whose every routable state declares `handler: \"local\"` produces NO root\n\t// transition, and its transitions live on the rebuilt states alone. The rebuilt\n\t// states must therefore survive on their own, and not only beside a root\n\t// transition.\n\tif (routeTransitions.length === 0 && !rebuiltStates) {\n\t\treturn machineConfig;\n\t}\n\n\tconst next: Record<string, unknown> = {\n\t\t...machineConfig,\n\t\t...(rebuiltStates ? { states: walk?.states } : {}),\n\t};\n\n\tif (routeTransitions.length > 0) {\n\t\tconst existingOn = machineConfig.on || {};\n\n\t\t// Keep each play.route transition of the user, for example a 404 fallback that a\n\t\t// person wrote, and append those transitions AFTER the generated ones: XState\n\t\t// evaluates the candidate transitions in their order. Therefore a generated guard\n\t\t// wins when it matches, and the transitions of the user are the fallback.\n\t\tnext[\"on\"] = {\n\t\t\t...existingOn,\n\t\t\t\"play.route\": [...routeTransitions, ...asTransitionArray(existingOn[\"play.route\"])],\n\t\t};\n\t}\n\n\treturn next as T;\n}\n"],"mappings":"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"}
|