@xmachines/play-xstate 3.0.0 → 4.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 +148 -57
- package/dist/capabilities.d.ts +92 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +4 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/define-player.d.ts +7 -1
- package/dist/define-player.d.ts.map +1 -1
- package/dist/define-player.js +9 -60
- package/dist/define-player.js.map +1 -1
- package/dist/errors.d.ts +29 -26
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +53 -35
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/player-actor.d.ts +52 -117
- package/dist/player-actor.d.ts.map +1 -1
- package/dist/player-actor.js +72 -228
- package/dist/player-actor.js.map +1 -1
- package/dist/routing/derive-current-route.d.ts +1 -36
- package/dist/routing/derive-current-route.d.ts.map +1 -1
- package/dist/routing/derive-current-route.js +2 -76
- package/dist/routing/derive-current-route.js.map +1 -1
- package/dist/routing/derive-initial-route.d.ts.map +1 -1
- package/dist/routing/derive-initial-route.js +11 -0
- package/dist/routing/derive-initial-route.js.map +1 -1
- package/dist/routing/derive-route.d.ts +81 -2
- package/dist/routing/derive-route.d.ts.map +1 -1
- package/dist/routing/derive-route.js +97 -3
- package/dist/routing/derive-route.js.map +1 -1
- package/dist/routing/format-play-route-transitions.d.ts +8 -2
- package/dist/routing/format-play-route-transitions.d.ts.map +1 -1
- package/dist/routing/format-play-route-transitions.js +170 -43
- package/dist/routing/format-play-route-transitions.js.map +1 -1
- package/dist/routing/index.d.ts +1 -1
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/types.d.ts +9 -5
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/state-meta.d.ts +52 -0
- package/dist/state-meta.d.ts.map +1 -0
- package/dist/state-meta.js +77 -0
- package/dist/state-meta.js.map +1 -0
- package/dist/types.d.ts +18 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/view/derive-current-view.d.ts +1 -1
- package/dist/view/derive-current-view.d.ts.map +1 -1
- package/dist/view/derive-current-view.js +2 -2
- package/dist/view/derive-current-view.js.map +1 -1
- package/dist/with-routing.d.ts +45 -0
- package/dist/with-routing.d.ts.map +1 -0
- package/dist/with-routing.js +78 -0
- package/dist/with-routing.js.map +1 -0
- package/dist/with-view.d.ts +42 -0
- package/dist/with-view.d.ts.map +1 -0
- package/dist/with-view.js +150 -0
- package/dist/with-view.js.map +1 -0
- package/package.json +38 -17
- package/dist/guards/compose.d.ts +0 -158
- package/dist/guards/compose.d.ts.map +0 -1
- package/dist/guards/compose.js +0 -188
- package/dist/guards/compose.js.map +0 -1
- package/dist/guards/helpers.d.ts +0 -62
- package/dist/guards/helpers.d.ts.map +0 -1
- package/dist/guards/helpers.js +0 -85
- package/dist/guards/helpers.js.map +0 -1
- package/dist/guards/index.d.ts +0 -20
- package/dist/guards/index.d.ts.map +0 -1
- package/dist/guards/index.js +0 -18
- package/dist/guards/index.js.map +0 -1
- package/dist/guards/types.d.ts +0 -22
- package/dist/guards/types.d.ts.map +0 -1
- package/dist/guards/types.js +0 -2
- package/dist/guards/types.js.map +0 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The read of the `meta` of the active states, which BOTH capabilities share.
|
|
3
|
+
*
|
|
4
|
+
* `deriveCurrentRoute` folds this record into a URL, and `deriveCurrentView` folds the
|
|
5
|
+
* same record into a view spec. The two therefore cannot move apart on the selection of
|
|
6
|
+
* the branch, and they cannot move apart on their tolerance of a degenerate snapshot.
|
|
7
|
+
*
|
|
8
|
+
* It lives in its own module, and no longer beside the route derivation, because it
|
|
9
|
+
* belongs to neither capability. `@xmachines/play-xstate/view` imported it from
|
|
10
|
+
* `routing/derive-current-route.ts`, and that module reads `deriveRoute`, which names
|
|
11
|
+
* `@xmachines/play-router`. A view-only application therefore loaded the whole route
|
|
12
|
+
* derivation, and the OPTIONAL peer of the routing reached the runtime graph of the view
|
|
13
|
+
* entry point.
|
|
14
|
+
*/
|
|
15
|
+
import type { AnyMachineSnapshot } from "xstate";
|
|
16
|
+
/**
|
|
17
|
+
* Collects the `meta` object of the active state nodes of a SINGLE branch, from the
|
|
18
|
+
* root to a leaf. The key of each entry is the state id, and the order is the
|
|
19
|
+
* ancestors first. {@link deriveRoute} folds this shape.
|
|
20
|
+
*
|
|
21
|
+
* `snapshot.getMeta()` returns a FLAT record of the meta object of every active
|
|
22
|
+
* state. For a machine that is not parallel, that record is one chain of ancestors
|
|
23
|
+
* already. For a PARALLEL machine, it mixes the entries of every active region. A
|
|
24
|
+
* blind fold therefore joins a relative route of one region to an absolute route of
|
|
25
|
+
* a sibling region, and the URL belongs to the tree of neither region. The flat
|
|
26
|
+
* record also cannot go back into a hierarchy: when a state declares an explicit
|
|
27
|
+
* `id`, for example a child with `id: "dashboard-overview"` under `id: "dashboard"`,
|
|
28
|
+
* the meta keys are those ids, and they carry no information about the ancestry.
|
|
29
|
+
*
|
|
30
|
+
* Therefore this function walks `snapshot.value`, which holds the real hierarchy of
|
|
31
|
+
* the active states by the state key, against the node tree of the machine. It
|
|
32
|
+
* follows the FIRST active child at each level. This gives one deterministic branch,
|
|
33
|
+
* which matches the historical behavior of "the first that it finds" for a parallel
|
|
34
|
+
* machine. It also works with an explicit id, because each node carries its own `id`
|
|
35
|
+
* and its own `meta`.
|
|
36
|
+
*
|
|
37
|
+
* The function returns `null` when the snapshot exposes no machine and no value to
|
|
38
|
+
* walk. This is a defensive measure, because a real XState snapshot always exposes
|
|
39
|
+
* them. The caller then uses `getMeta()`, as before.
|
|
40
|
+
*/
|
|
41
|
+
export declare const firstActiveBranchMeta: (snapshot: AnyMachineSnapshot) => Record<string, unknown> | null;
|
|
42
|
+
/**
|
|
43
|
+
* Resolves the meta record that the route derivation and the view derivation fold:
|
|
44
|
+
* the single active branch that {@link firstActiveBranchMeta} walks, which is
|
|
45
|
+
* correct for a parallel machine and for an explicit id, or the flat `getMeta()`
|
|
46
|
+
* record when the snapshot has no machine tree to walk. `deriveCurrentRoute` and
|
|
47
|
+
* `deriveCurrentView` share this function. Therefore the two sides cannot move apart
|
|
48
|
+
* on the selection of the branch, and they cannot move apart on their tolerance of a
|
|
49
|
+
* degenerate snapshot.
|
|
50
|
+
*/
|
|
51
|
+
export declare const activeStateMeta: (snapshot: AnyMachineSnapshot) => Record<string, unknown> | null;
|
|
52
|
+
//# sourceMappingURL=state-meta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-meta.d.ts","sourceRoot":"","sources":["../src/state-meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,qBAAqB,GACjC,UAAU,kBAAkB,KAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAqC5B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,kBAAkB,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAMxF,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collects the `meta` object of the active state nodes of a SINGLE branch, from the
|
|
3
|
+
* root to a leaf. The key of each entry is the state id, and the order is the
|
|
4
|
+
* ancestors first. {@link deriveRoute} folds this shape.
|
|
5
|
+
*
|
|
6
|
+
* `snapshot.getMeta()` returns a FLAT record of the meta object of every active
|
|
7
|
+
* state. For a machine that is not parallel, that record is one chain of ancestors
|
|
8
|
+
* already. For a PARALLEL machine, it mixes the entries of every active region. A
|
|
9
|
+
* blind fold therefore joins a relative route of one region to an absolute route of
|
|
10
|
+
* a sibling region, and the URL belongs to the tree of neither region. The flat
|
|
11
|
+
* record also cannot go back into a hierarchy: when a state declares an explicit
|
|
12
|
+
* `id`, for example a child with `id: "dashboard-overview"` under `id: "dashboard"`,
|
|
13
|
+
* the meta keys are those ids, and they carry no information about the ancestry.
|
|
14
|
+
*
|
|
15
|
+
* Therefore this function walks `snapshot.value`, which holds the real hierarchy of
|
|
16
|
+
* the active states by the state key, against the node tree of the machine. It
|
|
17
|
+
* follows the FIRST active child at each level. This gives one deterministic branch,
|
|
18
|
+
* which matches the historical behavior of "the first that it finds" for a parallel
|
|
19
|
+
* machine. It also works with an explicit id, because each node carries its own `id`
|
|
20
|
+
* and its own `meta`.
|
|
21
|
+
*
|
|
22
|
+
* The function returns `null` when the snapshot exposes no machine and no value to
|
|
23
|
+
* walk. This is a defensive measure, because a real XState snapshot always exposes
|
|
24
|
+
* them. The caller then uses `getMeta()`, as before.
|
|
25
|
+
*/
|
|
26
|
+
export const firstActiveBranchMeta = (snapshot) => {
|
|
27
|
+
const root = snapshot.machine?.root;
|
|
28
|
+
if (!root)
|
|
29
|
+
return null;
|
|
30
|
+
const ordered = {};
|
|
31
|
+
let node = root;
|
|
32
|
+
let value = snapshot.value;
|
|
33
|
+
while (node) {
|
|
34
|
+
if (node.meta && typeof node.meta === "object") {
|
|
35
|
+
ordered[node.id] = node.meta; // nosemgrep: gitlab.eslint.detect-object-injection
|
|
36
|
+
}
|
|
37
|
+
// Find the key of the active child at this level. A string value is the name of an
|
|
38
|
+
// atomic active leaf. An object value is a compound node, with one key, or a
|
|
39
|
+
// parallel node, with many keys: take the first key, for one deterministic
|
|
40
|
+
// branch.
|
|
41
|
+
let key;
|
|
42
|
+
if (typeof value === "string") {
|
|
43
|
+
key = value;
|
|
44
|
+
}
|
|
45
|
+
else if (value && typeof value === "object") {
|
|
46
|
+
key = Object.keys(value)[0];
|
|
47
|
+
}
|
|
48
|
+
if (key === undefined)
|
|
49
|
+
break;
|
|
50
|
+
const child = node.states?.[key]; // nosemgrep: gitlab.eslint.detect-object-injection
|
|
51
|
+
if (!child)
|
|
52
|
+
break;
|
|
53
|
+
value =
|
|
54
|
+
typeof value === "object" && value !== null
|
|
55
|
+
? value[key] // nosemgrep: gitlab.eslint.detect-object-injection
|
|
56
|
+
: {};
|
|
57
|
+
node = child;
|
|
58
|
+
}
|
|
59
|
+
return ordered;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Resolves the meta record that the route derivation and the view derivation fold:
|
|
63
|
+
* the single active branch that {@link firstActiveBranchMeta} walks, which is
|
|
64
|
+
* correct for a parallel machine and for an explicit id, or the flat `getMeta()`
|
|
65
|
+
* record when the snapshot has no machine tree to walk. `deriveCurrentRoute` and
|
|
66
|
+
* `deriveCurrentView` share this function. Therefore the two sides cannot move apart
|
|
67
|
+
* on the selection of the branch, and they cannot move apart on their tolerance of a
|
|
68
|
+
* degenerate snapshot.
|
|
69
|
+
*/
|
|
70
|
+
export const activeStateMeta = (snapshot) => {
|
|
71
|
+
if (!snapshot || typeof snapshot.getMeta !== "function") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const meta = firstActiveBranchMeta(snapshot) ?? snapshot.getMeta();
|
|
75
|
+
return meta && typeof meta === "object" ? meta : null;
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=state-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-meta.js","sourceRoot":"","sources":["../src/state-meta.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACpC,QAA4B,EACK,EAAE;IACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,IAEnB,CAAC;IACb,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,IAAI,IAAI,GAA4B,IAAI,CAAC;IACzC,IAAI,KAAK,GAAY,QAAQ,CAAC,KAAK,CAAC;IAEpC,OAAO,IAAI,EAAE,CAAC;QACb,IAAI,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,mDAAmD;QAClF,CAAC;QAED,mFAAmF;QACnF,6EAA6E;QAC7E,2EAA2E;QAC3E,UAAU;QACV,IAAI,GAAuB,CAAC;QAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/B,GAAG,GAAG,KAAK,CAAC;QACb,CAAC;aAAM,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/C,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM;QAE7B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAA4B,CAAC,CAAC,mDAAmD;QAChH,IAAI,CAAC,KAAK;YAAE,MAAM;QAClB,KAAK;YACJ,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;gBAC1C,CAAC,CAAE,KAAiC,CAAC,GAAG,CAAC,CAAC,mDAAmD;gBAC7F,CAAC,CAAC,EAAE,CAAC;QACP,IAAI,GAAG,KAAK,CAAC;IACd,CAAC;IAED,OAAO,OAAO,CAAC;AAChB,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAA4B,EAAkC,EAAE;IAC/F,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,IAAI,GAAG,qBAAqB,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;IACnE,OAAO,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,IAAgC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpF,CAAC,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import type { ActorOptions, AnyStateMachine, InputFrom, SnapshotFrom } from "xstate";
|
|
2
2
|
import type { PlayerActor as PlayerActorClass } from "./player-actor.js";
|
|
3
|
+
import type { PlayerConstructor } from "./capabilities.js";
|
|
3
4
|
/**
|
|
4
5
|
* The configuration of definePlayer()
|
|
5
6
|
*/
|
|
6
|
-
export interface PlayerConfig<TMachine extends AnyStateMachine> {
|
|
7
|
+
export interface PlayerConfig<TMachine extends AnyStateMachine, TCapabilities extends object = object> {
|
|
7
8
|
/** The XState v5 state machine */
|
|
8
9
|
machine: TMachine;
|
|
9
10
|
/** The lifecycle hooks and the configuration */
|
|
10
11
|
options?: PlayerOptions<TMachine>;
|
|
12
|
+
/**
|
|
13
|
+
* The actor class to build, which decides which CAPABILITIES the actor carries.
|
|
14
|
+
*
|
|
15
|
+
* `PlayerActor` is the default, and it holds `state` and `send` alone. Compose a class
|
|
16
|
+
* to add a capability, and the factory then answers an actor with it:
|
|
17
|
+
*
|
|
18
|
+
* ```typescript
|
|
19
|
+
* definePlayer({ machine, actor: compose(PlayerActor, withRouting, withView) });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* An application that declares no route, and renders no view, passes nothing and loads
|
|
23
|
+
* neither body of code.
|
|
24
|
+
*/
|
|
25
|
+
actor?: PlayerConstructor<TMachine, TCapabilities>;
|
|
11
26
|
}
|
|
12
27
|
/**
|
|
13
28
|
* The lifecycle hooks of the player — the observability surface around the actor.
|
|
@@ -21,7 +36,7 @@ export interface PlayerOptions<TMachine extends AnyStateMachine> {
|
|
|
21
36
|
onStart?: (actor: PlayerActorClass<TMachine>) => void;
|
|
22
37
|
/**
|
|
23
38
|
* The actor calls it on each real stop, which means only after it tore a running
|
|
24
|
-
* actor down. A second `stop()` call,
|
|
39
|
+
* actor down. A second `stop()` call, and a stop of an
|
|
25
40
|
* actor that never started fire the hook not.
|
|
26
41
|
*/
|
|
27
42
|
onStop?: (actor: PlayerActorClass<TMachine>) => void;
|
|
@@ -118,5 +133,5 @@ export interface PlayerFactoryResumeOptions<TMachine extends AnyStateMachine> {
|
|
|
118
133
|
* An absent input is then a compile error, and not an actor that starts in an error
|
|
119
134
|
* status with a `null` initial route.
|
|
120
135
|
*/
|
|
121
|
-
export type PlayerFactory<TMachine extends AnyStateMachine> = undefined extends InputFrom<TMachine> ? (input?: InputFrom<TMachine>, options?: PlayerFactoryResumeOptions<TMachine>) => PlayerActorClass<TMachine> : (input: InputFrom<TMachine>, options?: PlayerFactoryResumeOptions<TMachine>) => PlayerActorClass<TMachine
|
|
136
|
+
export type PlayerFactory<TMachine extends AnyStateMachine, TCapabilities extends object = object> = undefined extends InputFrom<TMachine> ? (input?: InputFrom<TMachine>, options?: PlayerFactoryResumeOptions<TMachine>) => PlayerActorClass<TMachine> & TCapabilities : (input: InputFrom<TMachine>, options?: PlayerFactoryResumeOptions<TMachine>) => PlayerActorClass<TMachine> & TCapabilities;
|
|
122
137
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACrF,OAAO,KAAK,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACrF,OAAO,KAAK,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,YAAY,CAC5B,QAAQ,SAAS,eAAe,EAChC,aAAa,SAAS,MAAM,GAAG,MAAM;IAErC,kCAAkC;IAClC,OAAO,EAAE,QAAQ,CAAC;IAElB,gDAAgD;IAChD,OAAO,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAElC;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;CACnD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,QAAQ,SAAS,eAAe;IAC9D;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAEtD;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAErD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CACd,KAAK,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EACjC,SAAS,EAAE,YAAY,CAAC,QAAQ,CAAC,EACjC,SAAS,EAAE,YAAY,CAAC,QAAQ,CAAC,KAC7B,IAAI,CAAC;IAEV,uDAAuD;IACvD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAE3F;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC;CAC5C;AAED;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B,CAAC,QAAQ,SAAS,eAAe;IAC3E;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,CAAC,QAAQ,SAAS,eAAe,EAAE,aAAa,SAAS,MAAM,GAAG,MAAM,IAChG,SAAS,SAAS,SAAS,CAAC,QAAQ,CAAC,GAClC,CACA,KAAK,CAAC,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC3B,OAAO,CAAC,EAAE,0BAA0B,CAAC,QAAQ,CAAC,KAC1C,gBAAgB,CAAC,QAAQ,CAAC,GAAG,aAAa,GAC9C,CACA,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC1B,OAAO,CAAC,EAAE,0BAA0B,CAAC,QAAQ,CAAC,KAC1C,gBAAgB,CAAC,QAAQ,CAAC,GAAG,aAAa,CAAC"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* machine always belongs to the region of its URL.
|
|
8
8
|
*/
|
|
9
9
|
import type { AnyMachineSnapshot } from "xstate";
|
|
10
|
-
import { type PlaySpec } from "@xmachines/play-
|
|
10
|
+
import { type PlaySpec } from "@xmachines/play-view";
|
|
11
11
|
/**
|
|
12
12
|
* Derives the current view of the actor from the state metadata.
|
|
13
13
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive-current-view.d.ts","sourceRoot":"","sources":["../../src/view/derive-current-view.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAiC,KAAK,QAAQ,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"derive-current-view.d.ts","sourceRoot":"","sources":["../../src/view/derive-current-view.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAiC,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAyEpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,kBAAkB,KAAG,QAAQ,GAAG,IA4C3E,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { composePlayState, toAtomState } from "@xmachines/play-
|
|
2
|
-
import { activeStateMeta } from "../
|
|
1
|
+
import { composePlayState, toAtomState } from "@xmachines/play-view";
|
|
2
|
+
import { activeStateMeta } from "../state-meta.js";
|
|
3
3
|
/**
|
|
4
4
|
* The migration warning of the `contextProps` field, which 1.x removed. The code
|
|
5
5
|
* writes it one time for each spec. A meta.view object from a person passes around
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive-current-view.js","sourceRoot":"","sources":["../../src/view/derive-current-view.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAiB,MAAM,
|
|
1
|
+
{"version":3,"file":"derive-current-view.js","sourceRoot":"","sources":["../../src/view/derive-current-view.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The routing capability of an XState actor, as a mixin.
|
|
3
|
+
*
|
|
4
|
+
* `PlayerActor` holds `state` and `send`, and nothing about a route. This module adds
|
|
5
|
+
* `currentRoute` and `initialRoute`, which is what `Routable` of `@xmachines/play-router`
|
|
6
|
+
* asks for and what every router bridge reads.
|
|
7
|
+
*
|
|
8
|
+
* It is a SEPARATE entry point, `@xmachines/play-xstate/routing`, and the core imports it
|
|
9
|
+
* never. `@xmachines/play-router` is an OPTIONAL peer of this package for the same reason:
|
|
10
|
+
* an application that declares no route installs neither that package nor the route map
|
|
11
|
+
* and the bridge that it carries.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { definePlayer, PlayerActor, compose } from "@xmachines/play-xstate";
|
|
16
|
+
* import { withRouting } from "@xmachines/play-xstate/routing";
|
|
17
|
+
*
|
|
18
|
+
* const createPlayer = definePlayer({ machine, actor: compose(PlayerActor, withRouting) });
|
|
19
|
+
* const actor = createPlayer();
|
|
20
|
+
* actor.currentRoute.get(); // "/profile/alice"
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @packageDocumentation
|
|
24
|
+
*/
|
|
25
|
+
import type { AnyStateMachine } from "xstate";
|
|
26
|
+
import type { Routable } from "@xmachines/play-router";
|
|
27
|
+
import type { PlayerConstructor } from "./capabilities.js";
|
|
28
|
+
/**
|
|
29
|
+
* Adds the routing capability to an actor class.
|
|
30
|
+
*
|
|
31
|
+
* The result satisfies `Routable`, so a router bridge accepts it. A class without this
|
|
32
|
+
* mixin exposes no `currentRoute`, and a bridge therefore refuses it when it compiles,
|
|
33
|
+
* rather than at the first navigation.
|
|
34
|
+
*
|
|
35
|
+
* The mixin declares NO constructor, and that is deliberate: TypeScript demands the
|
|
36
|
+
* signature `...args: any[]` from a mixin over a generic base (TS2545), and this
|
|
37
|
+
* workspace writes no `any`. A field initializer of a subclass runs after `super()`
|
|
38
|
+
* instead, and it reads the inputs that `PlayerActor` keeps for a capability.
|
|
39
|
+
*
|
|
40
|
+
* @param Base - The actor class to extend, normally `PlayerActor`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function withRouting<TMachine extends AnyStateMachine>(Base: PlayerConstructor<TMachine>): PlayerConstructor<TMachine, Routable>;
|
|
43
|
+
export { deriveRoute, isAbsoluteRoute, buildRouteUrl, formatPlayRouteTransitions, } from "./routing/index.js";
|
|
44
|
+
export type { RouteMachineConfig, RouteStateNode, RouteContext, RouteObject, RouteMetadata, RouteData, RouteDataResolver, } from "./routing/index.js";
|
|
45
|
+
//# sourceMappingURL=with-routing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-routing.d.ts","sourceRoot":"","sources":["../src/with-routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,QAAQ,SAAS,eAAe,EAC3D,IAAI,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAC/B,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAoDvC;AAcD,OAAO,EACN,WAAW,EACX,eAAe,EACf,aAAa,EACb,0BAA0B,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACX,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,WAAW,EACX,aAAa,EACb,SAAS,EACT,iBAAiB,GACjB,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Signal } from "@xmachines/play-signals";
|
|
2
|
+
import { deriveCurrentRoute, deriveInitialRoute } from "./routing/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Adds the routing capability to an actor class.
|
|
5
|
+
*
|
|
6
|
+
* The result satisfies `Routable`, so a router bridge accepts it. A class without this
|
|
7
|
+
* mixin exposes no `currentRoute`, and a bridge therefore refuses it when it compiles,
|
|
8
|
+
* rather than at the first navigation.
|
|
9
|
+
*
|
|
10
|
+
* The mixin declares NO constructor, and that is deliberate: TypeScript demands the
|
|
11
|
+
* signature `...args: any[]` from a mixin over a generic base (TS2545), and this
|
|
12
|
+
* workspace writes no `any`. A field initializer of a subclass runs after `super()`
|
|
13
|
+
* instead, and it reads the inputs that `PlayerActor` keeps for a capability.
|
|
14
|
+
*
|
|
15
|
+
* @param Base - The actor class to extend, normally `PlayerActor`.
|
|
16
|
+
*/
|
|
17
|
+
export function withRouting(Base) {
|
|
18
|
+
class RoutableActor extends Base {
|
|
19
|
+
/**
|
|
20
|
+
* A TC39 `Signal.Computed`. It derives the current URL path from the `meta.route`
|
|
21
|
+
* template of the active machine state and from the context of the actor.
|
|
22
|
+
*
|
|
23
|
+
* It returns `null` when the current state has no `meta.route` field, and also when
|
|
24
|
+
* a necessary `:param` has no value in the context. The second case is normally
|
|
25
|
+
* temporary during a transition, and the signal computes the value again on the next
|
|
26
|
+
* snapshot.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // It returns "/profile/alice" when context.params.userId === "alice",
|
|
31
|
+
* // and null while the param is still absent.
|
|
32
|
+
* const route = actor.currentRoute.get();
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
currentRoute = new Signal.Computed(() => deriveCurrentRoute(this.state.get()));
|
|
36
|
+
/**
|
|
37
|
+
* The route of the initial state of the machine. The construction fixes it, and it
|
|
38
|
+
* never changes, also when the code restores the actor from a snapshot.
|
|
39
|
+
*
|
|
40
|
+
* A router bridge compares it with the browser URL, and it therefore separates a
|
|
41
|
+
* deep link (a URL that is not the initial one → the router wins) from a restore
|
|
42
|
+
* (the initial URL, and the actor at a different route from the restore → the actor
|
|
43
|
+
* wins).
|
|
44
|
+
*
|
|
45
|
+
* `deriveInitialRoute` derives the value statically from the machine definition,
|
|
46
|
+
* with the pure `initialTransition` helper of XState: the chain of the initial states
|
|
47
|
+
* and their `meta.route` templates are fixed at the moment of the machine
|
|
48
|
+
* definition, and the substitution of a `:param` uses the real initial context of
|
|
49
|
+
* the machine for the `input` of this actor. The code makes no second actor, and a
|
|
50
|
+
* snapshot of a restore changes the value never: it is always the **default**
|
|
51
|
+
* initial route of the machine.
|
|
52
|
+
*
|
|
53
|
+
* Without a snapshot of a restore, the pre-start snapshot of this actor IS that
|
|
54
|
+
* default initial state, so the code derives the route from it directly. A restore
|
|
55
|
+
* alone needs `initialTransition`, whose inert actor scope runs the initial
|
|
56
|
+
* transition two more times, one of them with an undefined `input`. That is a quirk
|
|
57
|
+
* of XState, and it costs too much for every other case.
|
|
58
|
+
*/
|
|
59
|
+
initialRoute = this.playerRestoredSnapshot === undefined
|
|
60
|
+
? deriveCurrentRoute(this.getSnapshot())
|
|
61
|
+
: deriveInitialRoute(this.playerMachine, this.playerInput);
|
|
62
|
+
}
|
|
63
|
+
return RoutableActor;
|
|
64
|
+
}
|
|
65
|
+
// The utilities of the route derivation and of the URL construction.
|
|
66
|
+
//
|
|
67
|
+
// They belong HERE, and no longer on the main entry. Each one names
|
|
68
|
+
// `@xmachines/play-router` in its own `.d.ts` — `PlayRouteEvent`, `RouteData`,
|
|
69
|
+
// `RouteObject` and `RouteMetadata` all have their single definition there. That
|
|
70
|
+
// package is an OPTIONAL peer, so a main entry that reached them gave every consumer
|
|
71
|
+
// of `definePlayer` a type graph that names a package their node_modules does not
|
|
72
|
+
// hold. `skipLibCheck` hides the fault and does not remove it: the types resolve to
|
|
73
|
+
// nothing.
|
|
74
|
+
//
|
|
75
|
+
// The view half exposes nothing on the main entry, for the same reason. The two
|
|
76
|
+
// capabilities hold one shape.
|
|
77
|
+
export { deriveRoute, isAbsoluteRoute, buildRouteUrl, formatPlayRouteTransitions, } from "./routing/index.js";
|
|
78
|
+
//# sourceMappingURL=with-routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-routing.js","sourceRoot":"","sources":["../src/with-routing.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAIjD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE5E;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAC1B,IAAiC;IAEjC,MAAM,aAAc,SAAQ,IAAI;QAC/B;;;;;;;;;;;;;;;WAeG;QACa,YAAY,GAAmC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CACvF,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CACpC,CAAC;QAEF;;;;;;;;;;;;;;;;;;;;;;WAsBG;QACa,YAAY,GAC3B,IAAI,CAAC,sBAAsB,KAAK,SAAS;YACxC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACxC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;KAC7D;IAED,OAAO,aAAa,CAAC;AACtB,CAAC;AAED,qEAAqE;AACrE,EAAE;AACF,oEAAoE;AACpE,+EAA+E;AAC/E,iFAAiF;AACjF,qFAAqF;AACrF,kFAAkF;AAClF,oFAAoF;AACpF,WAAW;AACX,EAAE;AACF,gFAAgF;AAChF,+BAA+B;AAC/B,OAAO,EACN,WAAW,EACX,eAAe,EACf,aAAa,EACb,0BAA0B,GAC1B,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The view capability of an XState actor, as a mixin.
|
|
3
|
+
*
|
|
4
|
+
* `PlayerActor` holds `state` and `send`, and nothing about a view. This module adds
|
|
5
|
+
* `currentView`, which is what `Viewable` of `@xmachines/play-view` asks for and what
|
|
6
|
+
* every renderer reads.
|
|
7
|
+
*
|
|
8
|
+
* It is a SEPARATE entry point, `@xmachines/play-xstate/view`, and the core imports it
|
|
9
|
+
* never. `@xmachines/play-view` is an OPTIONAL peer of this package for the same reason:
|
|
10
|
+
* an application that renders no view installs neither that package nor the json-render
|
|
11
|
+
* types that it names.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { definePlayer, PlayerActor, compose } from "@xmachines/play-xstate";
|
|
16
|
+
* import { withRouting } from "@xmachines/play-xstate/routing";
|
|
17
|
+
* import { withView } from "@xmachines/play-xstate/view";
|
|
18
|
+
*
|
|
19
|
+
* const createPlayer = definePlayer({
|
|
20
|
+
* machine,
|
|
21
|
+
* actor: compose(PlayerActor, withRouting, withView),
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
import type { AnyStateMachine } from "xstate";
|
|
28
|
+
import { type Viewable } from "@xmachines/play-view";
|
|
29
|
+
import type { PlayerConstructor } from "./capabilities.js";
|
|
30
|
+
/**
|
|
31
|
+
* Adds the view capability to an actor class.
|
|
32
|
+
*
|
|
33
|
+
* Compose it AFTER the routing capability. An override calls `super.onSnapshot()` first,
|
|
34
|
+
* so `compose(PlayerActor, withRouting, withView)` gives the route its new value before
|
|
35
|
+
* the view derives from the same transition. A router bridge therefore sees a guard
|
|
36
|
+
* redirect before a renderer sees the view of the state that the guard refused.
|
|
37
|
+
*
|
|
38
|
+
* @param Base - The actor class to extend, normally `PlayerActor` or the result of
|
|
39
|
+
* another capability.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withView<TMachine extends AnyStateMachine>(Base: PlayerConstructor<TMachine>): PlayerConstructor<TMachine, Viewable>;
|
|
42
|
+
//# sourceMappingURL=with-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-view.d.ts","sourceRoot":"","sources":["../src/with-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAsB,eAAe,EAAE,MAAM,QAAQ,CAAC;AAGlE,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAsD3D;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,SAAS,eAAe,EACxD,IAAI,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAC/B,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAgFvC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Signal } from "@xmachines/play-signals";
|
|
2
|
+
import { shallowEqualExcept } from "@xmachines/play";
|
|
3
|
+
import { reuseComposedState } from "@xmachines/play-view";
|
|
4
|
+
import { toError } from "./player-actor.js";
|
|
5
|
+
import { deriveCurrentView } from "./view/derive-current-view.js";
|
|
6
|
+
/**
|
|
7
|
+
* The structural equality of two derived view specs, with a limit on its depth.
|
|
8
|
+
*
|
|
9
|
+
* The function walks exactly the shape that `deriveCurrentView` builds: the spec
|
|
10
|
+
* fields, then `elements`, then the `props` object of each element. It compares
|
|
11
|
+
* each leaf with `Object.is`. It never enters the VALUE of a prop: a new reference
|
|
12
|
+
* therefore emits the view again, also when the contents are equal. This design
|
|
13
|
+
* keeps two things correct: a prop of a container (a Map, a Set, or an instance of a
|
|
14
|
+
* class, which a structural comparison cannot see), and a cyclic value, which gives
|
|
15
|
+
* a structural comparison a recursion without an end.
|
|
16
|
+
*/
|
|
17
|
+
const viewSpecsEquivalent = (a, b) => {
|
|
18
|
+
if (a === b)
|
|
19
|
+
return true;
|
|
20
|
+
if (!a || !b)
|
|
21
|
+
return false;
|
|
22
|
+
if (!shallowEqualExcept(a, b, "elements"))
|
|
23
|
+
return false;
|
|
24
|
+
const aElements = a.elements ?? {};
|
|
25
|
+
const bElements = b.elements ?? {};
|
|
26
|
+
// A derived spec spreads the same static meta.view. Therefore the elements
|
|
27
|
+
// usually have the same reference, and the walk over each element is then not
|
|
28
|
+
// necessary.
|
|
29
|
+
if (aElements === bElements)
|
|
30
|
+
return true;
|
|
31
|
+
const elementKeys = Object.keys(aElements);
|
|
32
|
+
if (elementKeys.length !== Object.keys(bElements).length)
|
|
33
|
+
return false;
|
|
34
|
+
for (const key of elementKeys) {
|
|
35
|
+
const aElement = aElements[key]; // nosemgrep: gitlab.eslint.detect-object-injection
|
|
36
|
+
const bElement = bElements[key]; // nosemgrep: gitlab.eslint.detect-object-injection
|
|
37
|
+
if (aElement === bElement)
|
|
38
|
+
continue;
|
|
39
|
+
if (!aElement || !bElement)
|
|
40
|
+
return false;
|
|
41
|
+
if (!shallowEqualExcept(aElement, bElement, "props"))
|
|
42
|
+
return false;
|
|
43
|
+
if (!shallowEqualExcept(aElement.props ?? {}, bElement.props ?? {}))
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The last snapshot that the view pipeline of each actor read.
|
|
50
|
+
*
|
|
51
|
+
* A WeakMap at the module level, and not a field of the class: a `private` or a `#` field
|
|
52
|
+
* makes a class type NOMINAL, so the compiler then compares the mixin class with no other
|
|
53
|
+
* type and the return of this function needs `as unknown as`. The map holds the same state
|
|
54
|
+
* with no member, and it releases each entry with its actor.
|
|
55
|
+
*
|
|
56
|
+
* XState notifies each observer on EVERY event that it processes, and an event that it
|
|
57
|
+
* ignores delivers the identical snapshot again. `deriveCurrentView` is pure in the
|
|
58
|
+
* snapshot, so an identical reference can change no result. An actor that the map does not
|
|
59
|
+
* hold has read no snapshot yet, which is correct for the first derivation: the snapshot of
|
|
60
|
+
* the construction has the same reference as the snapshot that `start()` replays.
|
|
61
|
+
*/
|
|
62
|
+
const lastViewSnapshots = new WeakMap();
|
|
63
|
+
/**
|
|
64
|
+
* Adds the view capability to an actor class.
|
|
65
|
+
*
|
|
66
|
+
* Compose it AFTER the routing capability. An override calls `super.onSnapshot()` first,
|
|
67
|
+
* so `compose(PlayerActor, withRouting, withView)` gives the route its new value before
|
|
68
|
+
* the view derives from the same transition. A router bridge therefore sees a guard
|
|
69
|
+
* redirect before a renderer sees the view of the state that the guard refused.
|
|
70
|
+
*
|
|
71
|
+
* @param Base - The actor class to extend, normally `PlayerActor` or the result of
|
|
72
|
+
* another capability.
|
|
73
|
+
*/
|
|
74
|
+
export function withView(Base) {
|
|
75
|
+
class ViewableActor extends Base {
|
|
76
|
+
/**
|
|
77
|
+
* The reactive signal of the current view spec. The signal derives the spec from the
|
|
78
|
+
* `meta.view` metadata of the active state.
|
|
79
|
+
*
|
|
80
|
+
* It emits a **new object reference** on each real change of the view on the screen:
|
|
81
|
+
* the view of a different state, or a change of a param or of the context that changes
|
|
82
|
+
* the resolved spec. A re-entry with `reenter: true` and new params also changes the
|
|
83
|
+
* spec. A snapshot that changes no view on the screen, such as an assign of the
|
|
84
|
+
* context alone, keeps the previous reference. A provider below the signal therefore
|
|
85
|
+
* mounts the UI again not on every event.
|
|
86
|
+
*
|
|
87
|
+
* The `PlaySpec` of the emission carries the context of the machine in its composed
|
|
88
|
+
* `state` field, under the read-only `/context` subtree. A spec therefore reads the
|
|
89
|
+
* context, and also each URL param, through the ordinary state grammar
|
|
90
|
+
* (`{ $state: "/context/params/section" }`). The context-projection module of
|
|
91
|
+
* `@xmachines/play-view` holds the complete contract.
|
|
92
|
+
*
|
|
93
|
+
* The signal returns `null` when the current state has no `meta.view` metadata.
|
|
94
|
+
*
|
|
95
|
+
* Two states can declare two separate `meta.view` literals with an identical
|
|
96
|
+
* structure. A transition between those two states then emits two different
|
|
97
|
+
* references, and a provider mounts the UI again. Move the shared literal into one
|
|
98
|
+
* `typedSpec` constant, and the identity then removes the duplicate.
|
|
99
|
+
*/
|
|
100
|
+
currentView = new Signal.State(null);
|
|
101
|
+
onSnapshot(snapshot) {
|
|
102
|
+
// FIRST, so that every capability composed before this one holds its new value.
|
|
103
|
+
super.onSnapshot(snapshot);
|
|
104
|
+
this.validateAndCacheView(snapshot);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Derives the view at the entry of a state, and keeps it. This happens one time for
|
|
108
|
+
* each transition. The signal holds the view, and the code computes it not on each
|
|
109
|
+
* read.
|
|
110
|
+
*
|
|
111
|
+
* @param snapshot - The current XState snapshot
|
|
112
|
+
*/
|
|
113
|
+
validateAndCacheView(snapshot) {
|
|
114
|
+
if (snapshot === lastViewSnapshots.get(this)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
lastViewSnapshots.set(this, snapshot);
|
|
118
|
+
try {
|
|
119
|
+
const view = deriveCurrentView(snapshot);
|
|
120
|
+
// Emit only after a real change of the view on the screen: deriveCurrentView
|
|
121
|
+
// returns a fresh object on each call, and the identity of the reference therefore
|
|
122
|
+
// tells nothing. A new reference for a snapshot that changes the view not, for
|
|
123
|
+
// example a context-only assign, makes a provider below mount the UI again, and
|
|
124
|
+
// that removes the state of the view. A deep equality test is deliberately NOT
|
|
125
|
+
// here: it sees nothing inside a Map or a Set, and it therefore stops a real
|
|
126
|
+
// change, and it recurses without an end on a cyclic prop. The last spec of an
|
|
127
|
+
// emission IS the current value of the signal. Read it without a track, so that
|
|
128
|
+
// the gate registers currentView never as a dependency of a computation around it.
|
|
129
|
+
const lastEmittedView = Signal.subtle.untrack(() => this.currentView.get());
|
|
130
|
+
// Use the reference of the previous composed state again when the value of the
|
|
131
|
+
// /context projection did not change. A context-only assign that changes no
|
|
132
|
+
// projected value therefore changes the identity of the state not.
|
|
133
|
+
const nextView = reuseComposedState(lastEmittedView, view);
|
|
134
|
+
if (viewSpecsEquivalent(lastEmittedView, nextView)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.currentView.set(nextView);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
const onError = this.hooks.onError;
|
|
141
|
+
if (onError) {
|
|
142
|
+
onError(this, toError(error));
|
|
143
|
+
}
|
|
144
|
+
// On an error: keep the last valid view, and clear it not
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return ViewableActor;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=with-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-view.js","sourceRoot":"","sources":["../src/with-view.ts"],"names":[],"mappings":"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"}
|