xstate 3.2.1 → 3.3.3
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/.vscode/launch.json +15 -13
- package/README.md +37 -9
- package/dist/xstate.js +1 -1
- package/dist/xstate.utils.js +1 -1
- package/es/Machine.d.ts +2 -2
- package/es/Machine.js +2 -2
- package/es/State.d.ts +8 -7
- package/es/State.js +3 -2
- package/es/StateNode.d.ts +50 -13
- package/es/StateNode.js +617 -412
- package/es/graph.d.ts +9 -6
- package/es/graph.js +31 -24
- package/es/patterns.js +1 -1
- package/es/scxml.d.ts +2 -1
- package/es/scxml.js +33 -10
- package/es/types.d.ts +38 -7
- package/es/utils.d.ts +14 -1
- package/es/utils.js +33 -5
- package/lib/Machine.d.ts +2 -2
- package/lib/Machine.js +2 -2
- package/lib/State.d.ts +8 -7
- package/lib/State.js +3 -2
- package/lib/StateNode.d.ts +50 -13
- package/lib/StateNode.js +616 -411
- package/lib/graph.d.ts +9 -6
- package/lib/graph.js +30 -22
- package/lib/patterns.js +1 -1
- package/lib/scxml.d.ts +2 -1
- package/lib/scxml.js +33 -10
- package/lib/types.d.ts +38 -7
- package/lib/utils.d.ts +14 -1
- package/lib/utils.js +35 -5
- package/package.json +3 -3
- package/src/Machine.ts +5 -3
- package/src/State.ts +10 -2
- package/src/StateNode.ts +966 -590
- package/src/graph.ts +60 -31
- package/src/scxml.ts +80 -49
- package/src/types.ts +48 -7
- package/src/utils.ts +52 -7
- package/test/actions.test.ts +24 -1
- package/test/activities.test.ts +165 -0
- package/test/deep.test.ts +14 -16
- package/test/deterministic.test.ts +26 -5
- package/test/examples/6.17.test.ts +64 -0
- package/test/fixtures/id.ts +1 -1
- package/test/graph.test.ts +39 -16
- package/test/guards.test.ts +172 -15
- package/test/history.test.ts +193 -58
- package/test/invalid.test.ts +48 -0
- package/test/multiple.test.ts +12 -18
- package/test/parallel.test.ts +472 -1
- package/test/scxml.test.ts +13 -4
- package/test/stateIn.test.ts +1 -1
- package/test/transient.test.ts +183 -1
package/src/StateNode.ts
CHANGED
|
@@ -6,7 +6,11 @@ import {
|
|
|
6
6
|
path,
|
|
7
7
|
toStatePaths,
|
|
8
8
|
pathsToStateValue,
|
|
9
|
-
pathToStateValue
|
|
9
|
+
pathToStateValue,
|
|
10
|
+
getActionType,
|
|
11
|
+
flatMap,
|
|
12
|
+
mapFilterValues,
|
|
13
|
+
nestedPath
|
|
10
14
|
} from './utils';
|
|
11
15
|
import {
|
|
12
16
|
Event,
|
|
@@ -17,18 +21,24 @@ import {
|
|
|
17
21
|
StandardMachine,
|
|
18
22
|
ParallelMachine,
|
|
19
23
|
SimpleOrCompoundStateNodeConfig,
|
|
20
|
-
MachineConfig,
|
|
21
24
|
ParallelMachineConfig,
|
|
22
25
|
EventType,
|
|
23
|
-
ActionMap,
|
|
24
26
|
StandardMachineConfig,
|
|
25
27
|
TransitionConfig,
|
|
26
28
|
ActivityMap,
|
|
27
|
-
StateNodeConfig,
|
|
28
29
|
Activity,
|
|
30
|
+
ConditionalTransitionConfig,
|
|
31
|
+
EntryExitStates,
|
|
32
|
+
TargetTransitionConfig,
|
|
29
33
|
StateTransition,
|
|
34
|
+
ActionObject,
|
|
35
|
+
StateValueMap,
|
|
36
|
+
MachineOptions,
|
|
37
|
+
Condition,
|
|
38
|
+
ConditionPredicate,
|
|
30
39
|
EventObject,
|
|
31
|
-
|
|
40
|
+
HistoryStateNodeConfig,
|
|
41
|
+
HistoryValue
|
|
32
42
|
} from './types';
|
|
33
43
|
import { matchesState } from './matchesState';
|
|
34
44
|
import { State } from './State';
|
|
@@ -39,85 +49,22 @@ const HISTORY_KEY = '$history';
|
|
|
39
49
|
const NULL_EVENT = '';
|
|
40
50
|
const STATE_IDENTIFIER = '#';
|
|
41
51
|
const isStateId = (str: string) => str[0] === STATE_IDENTIFIER;
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
onExit: [],
|
|
45
|
-
actions: []
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Given a StateNode, walk up the parent chain until we find an
|
|
50
|
-
* orthogonal region of a parallel state, or the top level machine
|
|
51
|
-
* itself
|
|
52
|
-
*/
|
|
53
|
-
const regionOf = (node: StateNode): StateNode => {
|
|
54
|
-
// If we reach the top of the state machine, we're a "region".
|
|
55
|
-
// If our parent is a parallel state, we're a region.
|
|
56
|
-
while (node.parent && !node.parent.parallel) {
|
|
57
|
-
node = node.parent;
|
|
58
|
-
}
|
|
59
|
-
return node;
|
|
52
|
+
const defaultOptions: MachineOptions = {
|
|
53
|
+
guards: {}
|
|
60
54
|
};
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
* Ensure that the passed in StateNode instance belongs to a region
|
|
64
|
-
* that previously had not been used, or that matches the existing
|
|
65
|
-
* StateNode for the orthogonal regions. This function is used to
|
|
66
|
-
* verify that a transition that has multiple targets ends doesn't try
|
|
67
|
-
* to target several states in the same orthogonal region. The passed
|
|
68
|
-
* state is added to the regions data structure using the state's
|
|
69
|
-
* _region_ (see regionOf), and the region's parent. If there is
|
|
70
|
-
* already an object in the structure which is not already the state
|
|
71
|
-
* in question, an Error is thrown, otherwise the state is added to
|
|
72
|
-
* the structure, and the _region_ is returned.
|
|
73
|
-
*
|
|
74
|
-
* @param sourceState the state in which the event was triggered (used
|
|
75
|
-
* to report error messages)
|
|
76
|
-
* @param event the event that triggered the transition (used to
|
|
77
|
-
* report error messages)
|
|
78
|
-
* @param regions A data structure that retains the current set of
|
|
79
|
-
* orthogonal regions (their IDs), grouped by their parallel state
|
|
80
|
-
* (their IDs), with the values being the chosen states
|
|
81
|
-
* @param state A state to add to the structure if possible.
|
|
82
|
-
* @returns The region of the state, in order for the caller to repeat the process for the parent.
|
|
83
|
-
* @throws Error if the region found already exists in the regions
|
|
84
|
-
*/
|
|
85
|
-
|
|
86
|
-
const ensureTargetStateIsInCorrectRegion = (
|
|
87
|
-
sourceState: StateNode,
|
|
88
|
-
event: Event,
|
|
89
|
-
regions: Record<string, Record<string, StateNode>>,
|
|
90
|
-
stateToCheck: StateNode
|
|
91
|
-
): StateNode => {
|
|
92
|
-
const region = regionOf(stateToCheck);
|
|
93
|
-
const parent = region.parent;
|
|
94
|
-
const parentId = parent ? parent.id : ''; // '' == machine
|
|
95
|
-
|
|
96
|
-
regions[parentId] = regions[parentId] || {};
|
|
97
|
-
if (
|
|
98
|
-
regions[parentId][region.id] &&
|
|
99
|
-
regions[parentId][region.id] !== stateToCheck
|
|
100
|
-
) {
|
|
101
|
-
throw new Error(
|
|
102
|
-
`Event '${event}' on state '${sourceState.id}' leads to an invalid configuration: ` +
|
|
103
|
-
`Two or more states in the orthogonal region '${region.id}'.`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
// Keep track of which state was chosen in a particular region.
|
|
107
|
-
regions[parentId][region.id] = stateToCheck;
|
|
108
|
-
return region;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
class StateNode implements StateNodeConfig {
|
|
56
|
+
class StateNode {
|
|
112
57
|
public key: string;
|
|
113
58
|
public id: string;
|
|
114
59
|
public path: string[];
|
|
115
60
|
public initial?: string;
|
|
116
61
|
public parallel?: boolean;
|
|
62
|
+
public transient: boolean;
|
|
117
63
|
public states: Record<string, StateNode>;
|
|
64
|
+
public history: false | 'shallow' | 'deep';
|
|
118
65
|
public on: Record<string, ConditionalTransitionConfig>;
|
|
119
|
-
public onEntry
|
|
120
|
-
public onExit
|
|
66
|
+
public onEntry: Action[];
|
|
67
|
+
public onExit: Action[];
|
|
121
68
|
public activities?: Activity[];
|
|
122
69
|
public strict: boolean;
|
|
123
70
|
public parent?: StateNode;
|
|
@@ -137,7 +84,8 @@ class StateNode implements StateNodeConfig {
|
|
|
137
84
|
public config:
|
|
138
85
|
| SimpleOrCompoundStateNodeConfig
|
|
139
86
|
| StandardMachineConfig
|
|
140
|
-
| ParallelMachineConfig
|
|
87
|
+
| ParallelMachineConfig,
|
|
88
|
+
public options: MachineOptions = defaultOptions
|
|
141
89
|
) {
|
|
142
90
|
this.key = config.key || '(machine)';
|
|
143
91
|
this.parent = config.parent;
|
|
@@ -154,35 +102,41 @@ class StateNode implements StateNodeConfig {
|
|
|
154
102
|
this.initial = config.initial;
|
|
155
103
|
this.parallel = !!config.parallel;
|
|
156
104
|
this.states = (config.states
|
|
157
|
-
? mapValues<
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
105
|
+
? mapValues<SimpleOrCompoundStateNodeConfig, StateNode>(
|
|
106
|
+
config.states,
|
|
107
|
+
(stateConfig, key) => {
|
|
108
|
+
const stateNode = new StateNode({
|
|
109
|
+
...stateConfig,
|
|
110
|
+
key,
|
|
111
|
+
parent: this
|
|
112
|
+
});
|
|
113
|
+
Object.assign(this.idMap, {
|
|
114
|
+
[stateNode.id]: stateNode,
|
|
115
|
+
...stateNode.idMap
|
|
116
|
+
});
|
|
117
|
+
return stateNode;
|
|
118
|
+
}
|
|
119
|
+
)
|
|
172
120
|
: {}) as Record<string, StateNode>;
|
|
173
121
|
|
|
122
|
+
// History config
|
|
123
|
+
this.history =
|
|
124
|
+
config.history === true ? 'shallow' : config.history || false;
|
|
125
|
+
|
|
174
126
|
this.on = config.on ? this.formatTransitions(config.on) : {};
|
|
127
|
+
this.transient = !!this.on[NULL_EVENT];
|
|
175
128
|
this.strict = !!config.strict;
|
|
176
129
|
this.onEntry = config.onEntry
|
|
177
130
|
? ([] as Action[]).concat(config.onEntry)
|
|
178
|
-
:
|
|
179
|
-
this.onExit = config.onExit
|
|
180
|
-
? ([] as Action[]).concat(config.onExit)
|
|
181
|
-
: undefined;
|
|
131
|
+
: [];
|
|
132
|
+
this.onExit = config.onExit ? ([] as Action[]).concat(config.onExit) : [];
|
|
182
133
|
this.data = config.data;
|
|
183
134
|
this.activities = config.activities;
|
|
184
135
|
}
|
|
185
136
|
public getStateNodes(state: StateValue | State): StateNode[] {
|
|
137
|
+
if (!state) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
186
140
|
const stateValue =
|
|
187
141
|
state instanceof State
|
|
188
142
|
? state.value
|
|
@@ -219,411 +173,285 @@ class StateNode implements StateNodeConfig {
|
|
|
219
173
|
|
|
220
174
|
return this.events.indexOf(eventType) !== -1;
|
|
221
175
|
}
|
|
222
|
-
|
|
223
|
-
|
|
176
|
+
private _transitionLeafNode(
|
|
177
|
+
stateValue: string,
|
|
178
|
+
state: State,
|
|
224
179
|
event: Event,
|
|
225
180
|
extendedState?: any
|
|
226
|
-
):
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
? this.resolve(pathToStateValue(this.getResolvedPath(state)))
|
|
230
|
-
: state instanceof State ? state : this.resolve(state);
|
|
231
|
-
|
|
232
|
-
if (this.strict) {
|
|
233
|
-
const eventType = getEventType(event);
|
|
234
|
-
if (this.events.indexOf(eventType) === -1) {
|
|
235
|
-
throw new Error(
|
|
236
|
-
`Machine '${this.id}' does not accept event '${eventType}'`
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const currentState = State.from(resolvedStateValue);
|
|
242
|
-
|
|
243
|
-
const stateTransition = this.transitionStateValue(
|
|
244
|
-
currentState,
|
|
245
|
-
event,
|
|
246
|
-
currentState,
|
|
247
|
-
extendedState
|
|
248
|
-
);
|
|
249
|
-
let nextState = this.stateTransitionToState(stateTransition, currentState);
|
|
250
|
-
|
|
251
|
-
if (!nextState) {
|
|
252
|
-
return State.inert(currentState);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let maybeNextState: State | undefined = nextState;
|
|
256
|
-
|
|
257
|
-
const raisedEvents = nextState.actions.filter(
|
|
258
|
-
action => typeof action === 'object' && action.type === actionTypes.raise
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
if (raisedEvents.length) {
|
|
262
|
-
const raisedEvent = (raisedEvents[0] as EventObject).event!;
|
|
263
|
-
|
|
264
|
-
nextState = this.transition(nextState, raisedEvent, extendedState);
|
|
265
|
-
nextState.actions.unshift(...nextState.actions);
|
|
266
|
-
return nextState;
|
|
267
|
-
}
|
|
181
|
+
): StateTransition {
|
|
182
|
+
const stateNode = this.getStateNode(stateValue);
|
|
183
|
+
const next = stateNode._next(state, event, extendedState);
|
|
268
184
|
|
|
269
|
-
if (
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
185
|
+
if (!next.value) {
|
|
186
|
+
const { value, entryExitStates, actions, paths } = this._next(
|
|
187
|
+
state,
|
|
188
|
+
event,
|
|
189
|
+
extendedState
|
|
190
|
+
);
|
|
275
191
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
192
|
+
return {
|
|
193
|
+
value,
|
|
194
|
+
entryExitStates: {
|
|
195
|
+
entry: entryExitStates ? entryExitStates.entry : new Set(),
|
|
196
|
+
exit: new Set<StateNode>([
|
|
197
|
+
stateNode,
|
|
198
|
+
...(entryExitStates
|
|
199
|
+
? Array.from(entryExitStates.exit)
|
|
200
|
+
: ([] as StateNode[]))
|
|
201
|
+
])
|
|
202
|
+
},
|
|
203
|
+
actions,
|
|
204
|
+
paths
|
|
205
|
+
};
|
|
285
206
|
}
|
|
286
207
|
|
|
287
|
-
return
|
|
208
|
+
return next;
|
|
288
209
|
}
|
|
289
|
-
private
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
activities: nextActivities,
|
|
297
|
-
events
|
|
298
|
-
} = stateTransition;
|
|
299
|
-
|
|
300
|
-
if (!nextStatePaths.length) {
|
|
301
|
-
return undefined;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const prevActivities =
|
|
305
|
-
prevState instanceof State ? prevState.activities : undefined;
|
|
306
|
-
|
|
307
|
-
const activities = { ...prevActivities, ...nextActivities };
|
|
308
|
-
|
|
309
|
-
const nextStateValue = this.resolve(pathsToStateValue(nextStatePaths));
|
|
310
|
-
return new State(
|
|
311
|
-
// next state value
|
|
312
|
-
nextStateValue,
|
|
313
|
-
// history
|
|
314
|
-
State.from(prevState),
|
|
315
|
-
// effects
|
|
316
|
-
nextActions
|
|
317
|
-
? nextActions.onExit
|
|
318
|
-
.concat(nextActions.actions)
|
|
319
|
-
.concat(nextActions.onEntry)
|
|
320
|
-
: [],
|
|
321
|
-
// activities
|
|
322
|
-
activities,
|
|
323
|
-
// data
|
|
324
|
-
this.getStateNodes(nextStateValue).reduce(
|
|
325
|
-
(data, stateNode) => {
|
|
326
|
-
if (stateNode.data !== undefined) {
|
|
327
|
-
data[stateNode.id] = stateNode.data;
|
|
328
|
-
}
|
|
210
|
+
private _transitionHierarchicalNode(
|
|
211
|
+
stateValue: StateValueMap,
|
|
212
|
+
state: State,
|
|
213
|
+
event: Event,
|
|
214
|
+
extendedState?: any
|
|
215
|
+
): StateTransition {
|
|
216
|
+
const subStateKeys = Object.keys(stateValue);
|
|
329
217
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
218
|
+
const stateNode = this.getStateNode(subStateKeys[0]);
|
|
219
|
+
const next = stateNode._transition(
|
|
220
|
+
stateValue[subStateKeys[0]],
|
|
221
|
+
state,
|
|
222
|
+
event,
|
|
223
|
+
extendedState
|
|
335
224
|
);
|
|
336
|
-
}
|
|
337
|
-
public getStateNode(stateKey: string): StateNode {
|
|
338
|
-
if (isStateId(stateKey)) {
|
|
339
|
-
return this.machine.getStateNodeById(stateKey);
|
|
340
|
-
}
|
|
341
225
|
|
|
342
|
-
if (!
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const result = this.states[stateKey];
|
|
350
|
-
if (!result) {
|
|
351
|
-
throw new Error(
|
|
352
|
-
`Child state '${stateKey}' does not exist on '${this.id}'`
|
|
226
|
+
if (!next.value) {
|
|
227
|
+
const { value, entryExitStates, actions, paths } = this._next(
|
|
228
|
+
state,
|
|
229
|
+
event,
|
|
230
|
+
extendedState
|
|
353
231
|
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return result;
|
|
357
|
-
}
|
|
358
|
-
public getStateNodeById(stateId: string): StateNode {
|
|
359
|
-
const resolvedStateId = isStateId(stateId)
|
|
360
|
-
? stateId.slice(STATE_IDENTIFIER.length)
|
|
361
|
-
: stateId;
|
|
362
|
-
const stateNode = this.idMap[resolvedStateId];
|
|
363
232
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
233
|
+
return {
|
|
234
|
+
value,
|
|
235
|
+
entryExitStates: {
|
|
236
|
+
entry: entryExitStates ? entryExitStates.entry : new Set(),
|
|
237
|
+
exit: new Set<StateNode>([
|
|
238
|
+
...(next.entryExitStates
|
|
239
|
+
? Array.from(next.entryExitStates.exit)
|
|
240
|
+
: []),
|
|
241
|
+
stateNode,
|
|
242
|
+
...(entryExitStates
|
|
243
|
+
? Array.from(entryExitStates.exit)
|
|
244
|
+
: ([] as StateNode[]))
|
|
245
|
+
])
|
|
246
|
+
},
|
|
247
|
+
actions,
|
|
248
|
+
paths
|
|
249
|
+
};
|
|
368
250
|
}
|
|
369
251
|
|
|
370
|
-
return
|
|
252
|
+
return next;
|
|
371
253
|
}
|
|
372
|
-
private
|
|
373
|
-
|
|
374
|
-
const subStateNode = this.getStateNode(stateValue);
|
|
375
|
-
return subStateNode.initial
|
|
376
|
-
? { [stateValue]: subStateNode.initialStateValue! }
|
|
377
|
-
: stateValue;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (this.parallel) {
|
|
381
|
-
return mapValues(
|
|
382
|
-
this.initialStateValue as Record<string, StateValue>,
|
|
383
|
-
(subStateValue, subStateKey) => {
|
|
384
|
-
return this.getStateNode(subStateKey).resolve(
|
|
385
|
-
stateValue[subStateKey] || subStateValue
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
return mapValues(stateValue, (subStateValue, subStateKey) => {
|
|
392
|
-
return this.getStateNode(subStateKey).resolve(subStateValue);
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
private transitionStateValue(
|
|
254
|
+
private _transitionOrthogonalNode(
|
|
255
|
+
stateValue: StateValueMap,
|
|
396
256
|
state: State,
|
|
397
257
|
event: Event,
|
|
398
|
-
fullState: State,
|
|
399
258
|
extendedState?: any
|
|
400
259
|
): StateTransition {
|
|
401
|
-
const
|
|
402
|
-
const
|
|
260
|
+
const noTransitionKeys: string[] = [];
|
|
261
|
+
const transitionMap: Record<string, StateTransition> = {};
|
|
403
262
|
|
|
404
|
-
|
|
405
|
-
const
|
|
263
|
+
Object.keys(stateValue).forEach(subStateKey => {
|
|
264
|
+
const subStateValue = stateValue[subStateKey];
|
|
406
265
|
|
|
407
|
-
|
|
266
|
+
if (!subStateValue) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const next = this.getStateNode(subStateKey)._transition(
|
|
271
|
+
subStateValue,
|
|
272
|
+
state,
|
|
408
273
|
event,
|
|
409
|
-
fullState,
|
|
410
|
-
history ? history.value : undefined,
|
|
411
274
|
extendedState
|
|
412
275
|
);
|
|
413
276
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (!result.statePaths.length && !this.parent) {
|
|
417
|
-
return this.next(
|
|
418
|
-
event,
|
|
419
|
-
fullState,
|
|
420
|
-
history ? history.value : undefined,
|
|
421
|
-
extendedState
|
|
422
|
-
);
|
|
277
|
+
if (!next.value) {
|
|
278
|
+
noTransitionKeys.push(subStateKey);
|
|
423
279
|
}
|
|
424
280
|
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Potential transition tuples from parent state nodes
|
|
429
|
-
const potentialStateTransitions: StateTransition[] = [];
|
|
430
|
-
let willTransition = false;
|
|
431
|
-
|
|
432
|
-
let nextStateTransitionMap = mapValues(
|
|
433
|
-
stateValue,
|
|
434
|
-
(subStateValue, subStateKey) => {
|
|
435
|
-
const subStateNode = this.getStateNode(subStateKey);
|
|
436
|
-
const subHistory = history ? history.value[subStateKey] : undefined;
|
|
437
|
-
const subState = new State(
|
|
438
|
-
subStateValue,
|
|
439
|
-
subHistory ? State.from(subHistory) : undefined
|
|
440
|
-
);
|
|
441
|
-
const subStateTransition = subStateNode.transitionStateValue(
|
|
442
|
-
subState,
|
|
443
|
-
event,
|
|
444
|
-
fullState,
|
|
445
|
-
extendedState
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
if (!subStateTransition.statePaths.length) {
|
|
449
|
-
potentialStateTransitions.push(
|
|
450
|
-
subStateNode.next(
|
|
451
|
-
event,
|
|
452
|
-
fullState,
|
|
453
|
-
history ? history.value : undefined,
|
|
454
|
-
extendedState
|
|
455
|
-
)
|
|
456
|
-
);
|
|
457
|
-
} else {
|
|
458
|
-
willTransition = true;
|
|
459
|
-
}
|
|
281
|
+
transitionMap[subStateKey] = next;
|
|
282
|
+
});
|
|
460
283
|
|
|
461
|
-
|
|
462
|
-
|
|
284
|
+
const willTransition = Object.keys(transitionMap).some(
|
|
285
|
+
key => transitionMap[key].value !== undefined
|
|
463
286
|
);
|
|
464
287
|
|
|
465
288
|
if (!willTransition) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
// Select the first potential state transition to take
|
|
469
|
-
return potentialStateTransitions[0];
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
statePaths: [],
|
|
474
|
-
actions: emptyActions,
|
|
475
|
-
activities: undefined,
|
|
476
|
-
events: []
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const [subStateKey] = Object.keys(nextStateTransitionMap);
|
|
481
|
-
|
|
482
|
-
// try with parent
|
|
483
|
-
const {
|
|
484
|
-
statePaths: parentStatePaths,
|
|
485
|
-
actions: parentNextActions,
|
|
486
|
-
activities: parentActivities
|
|
487
|
-
} = this.getStateNode(subStateKey).next(
|
|
289
|
+
const { value, entryExitStates, actions, paths } = this._next(
|
|
290
|
+
state,
|
|
488
291
|
event,
|
|
489
|
-
fullState,
|
|
490
|
-
history ? history.value : undefined,
|
|
491
292
|
extendedState
|
|
492
293
|
);
|
|
493
294
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
295
|
+
return {
|
|
296
|
+
value,
|
|
297
|
+
entryExitStates: {
|
|
298
|
+
entry: entryExitStates ? entryExitStates.entry : new Set(),
|
|
299
|
+
exit: new Set([
|
|
300
|
+
...Object.keys(this.states).map(key => this.states[key]),
|
|
301
|
+
...(entryExitStates ? Array.from(entryExitStates.exit) : [])
|
|
302
|
+
])
|
|
303
|
+
},
|
|
304
|
+
actions,
|
|
305
|
+
paths
|
|
500
306
|
};
|
|
307
|
+
}
|
|
501
308
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
onEntry: [...nextActions.onEntry, ...parentNextActions.onEntry],
|
|
506
|
-
actions: [...nextActions.actions, ...parentNextActions.actions],
|
|
507
|
-
onExit: [...nextActions.onExit, ...parentNextActions.onExit]
|
|
508
|
-
}
|
|
509
|
-
: parentNextActions
|
|
510
|
-
: nextActions;
|
|
309
|
+
const allPaths = flatMap(
|
|
310
|
+
Object.keys(transitionMap).map(key => transitionMap[key].paths)
|
|
311
|
+
);
|
|
511
312
|
|
|
313
|
+
// External transition that escapes orthogonal region
|
|
314
|
+
if (
|
|
315
|
+
allPaths.length === 1 &&
|
|
316
|
+
!matchesState(pathToStateValue(this.path), pathToStateValue(allPaths[0]))
|
|
317
|
+
) {
|
|
512
318
|
return {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
319
|
+
value: this.machine.resolve(pathsToStateValue(allPaths)),
|
|
320
|
+
entryExitStates: Object.keys(transitionMap)
|
|
321
|
+
.map(key => transitionMap[key].entryExitStates)
|
|
322
|
+
.reduce(
|
|
323
|
+
(allEntryExitStates, entryExitStates) => {
|
|
324
|
+
const { entry, exit } = entryExitStates!;
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
entry: new Set([
|
|
328
|
+
...Array.from(allEntryExitStates!.entry),
|
|
329
|
+
...Array.from(entry)
|
|
330
|
+
]),
|
|
331
|
+
exit: new Set([
|
|
332
|
+
...Array.from(allEntryExitStates!.exit),
|
|
333
|
+
...Array.from(exit)
|
|
334
|
+
])
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
{ entry: new Set(), exit: new Set() } as EntryExitStates
|
|
338
|
+
),
|
|
339
|
+
actions: flatMap(
|
|
340
|
+
Object.keys(transitionMap).map(key => {
|
|
341
|
+
return transitionMap[key].actions;
|
|
342
|
+
})
|
|
343
|
+
),
|
|
344
|
+
paths: allPaths
|
|
517
345
|
};
|
|
518
346
|
}
|
|
519
347
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
(subStateValue, key) => {
|
|
525
|
-
const subStateTransition = nextStateTransitionMap[key];
|
|
526
|
-
return {
|
|
527
|
-
statePaths:
|
|
528
|
-
subStateTransition && subStateTransition.statePaths.length
|
|
529
|
-
? subStateTransition.statePaths
|
|
530
|
-
: toStatePaths(
|
|
531
|
-
stateValue[key] || subStateValue
|
|
532
|
-
).map(subPath => [
|
|
533
|
-
...this.getStateNode(key).path,
|
|
534
|
-
...subPath
|
|
535
|
-
]),
|
|
536
|
-
actions:
|
|
537
|
-
subStateTransition && subStateTransition.actions
|
|
538
|
-
? subStateTransition.actions
|
|
539
|
-
: {
|
|
540
|
-
onEntry: [],
|
|
541
|
-
onExit: [],
|
|
542
|
-
actions: []
|
|
543
|
-
},
|
|
544
|
-
activities: undefined,
|
|
545
|
-
events: []
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
)
|
|
549
|
-
};
|
|
550
|
-
}
|
|
348
|
+
const allResolvedPaths = flatMap(
|
|
349
|
+
Object.keys(transitionMap).map(key => {
|
|
350
|
+
const transition = transitionMap[key];
|
|
351
|
+
const value = transition.value || state.value;
|
|
551
352
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
actions: nextSubActions,
|
|
562
|
-
activities: nextSubActivities
|
|
563
|
-
} = subStateTransition;
|
|
564
|
-
if (nextSubActions) {
|
|
565
|
-
if (nextSubActions.onEntry) {
|
|
566
|
-
finalActions.onEntry.push(...nextSubActions.onEntry);
|
|
567
|
-
}
|
|
568
|
-
if (nextSubActions.actions) {
|
|
569
|
-
finalActions.actions.push(...nextSubActions.actions);
|
|
570
|
-
}
|
|
571
|
-
if (nextSubActions.onExit) {
|
|
572
|
-
finalActions.onExit.push(...nextSubActions.onExit);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
if (nextSubActivities) {
|
|
576
|
-
Object.assign(finalActivities, nextSubActivities);
|
|
577
|
-
}
|
|
578
|
-
});
|
|
353
|
+
return toStatePaths(path(this.path)(value)[key]).map(statePath =>
|
|
354
|
+
this.path.concat(key, statePath)
|
|
355
|
+
);
|
|
356
|
+
})
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const nextStateValue = this.machine.resolve(
|
|
360
|
+
pathsToStateValue(allResolvedPaths)
|
|
361
|
+
);
|
|
579
362
|
|
|
580
363
|
return {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
364
|
+
value: nextStateValue,
|
|
365
|
+
entryExitStates: Object.keys(transitionMap).reduce(
|
|
366
|
+
(allEntryExitStates, key) => {
|
|
367
|
+
const { value: subStateValue, entryExitStates } = transitionMap[key];
|
|
368
|
+
|
|
369
|
+
// If the event was not handled (no subStateValue),
|
|
370
|
+
// machine should still be in state without reentry/exit.
|
|
371
|
+
if (!subStateValue || !entryExitStates) {
|
|
372
|
+
return allEntryExitStates;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const { entry, exit } = entryExitStates;
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
entry: new Set([
|
|
379
|
+
...Array.from(allEntryExitStates.entry),
|
|
380
|
+
...Array.from(entry)
|
|
381
|
+
]),
|
|
382
|
+
exit: new Set([
|
|
383
|
+
...Array.from(allEntryExitStates.exit),
|
|
384
|
+
...Array.from(exit)
|
|
385
|
+
])
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
{ entry: new Set(), exit: new Set() } as EntryExitStates
|
|
389
|
+
),
|
|
390
|
+
actions: flatMap(
|
|
391
|
+
Object.keys(transitionMap).map(key => {
|
|
392
|
+
return transitionMap[key].actions;
|
|
393
|
+
})
|
|
394
|
+
),
|
|
395
|
+
paths: toStatePaths(nextStateValue)
|
|
587
396
|
};
|
|
588
397
|
}
|
|
398
|
+
public _transition(
|
|
399
|
+
stateValue: StateValue,
|
|
400
|
+
state: State,
|
|
401
|
+
event: Event,
|
|
402
|
+
extendedState?: any
|
|
403
|
+
): StateTransition {
|
|
404
|
+
// leaf node
|
|
405
|
+
if (typeof stateValue === 'string') {
|
|
406
|
+
return this._transitionLeafNode(stateValue, state, event, extendedState);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// hierarchical node
|
|
410
|
+
if (Object.keys(stateValue).length === 1) {
|
|
411
|
+
return this._transitionHierarchicalNode(
|
|
412
|
+
stateValue,
|
|
413
|
+
state,
|
|
414
|
+
event,
|
|
415
|
+
extendedState
|
|
416
|
+
);
|
|
417
|
+
}
|
|
589
418
|
|
|
590
|
-
|
|
419
|
+
// orthogonal node
|
|
420
|
+
return this._transitionOrthogonalNode(
|
|
421
|
+
stateValue,
|
|
422
|
+
state,
|
|
423
|
+
event,
|
|
424
|
+
extendedState
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
private _next(
|
|
428
|
+
state: State,
|
|
591
429
|
event: Event,
|
|
592
|
-
fullState: State,
|
|
593
|
-
history?: StateValue,
|
|
594
430
|
extendedState?: any
|
|
595
431
|
): StateTransition {
|
|
596
432
|
const eventType = getEventType(event);
|
|
597
|
-
const actionMap: ActionMap = { onEntry: [], onExit: [], actions: [] };
|
|
598
|
-
const activityMap: ActivityMap = {};
|
|
599
433
|
const candidates = this.on[eventType];
|
|
434
|
+
const actions: Action[] = this.transient
|
|
435
|
+
? [{ type: actionTypes.null }]
|
|
436
|
+
: [];
|
|
600
437
|
|
|
601
|
-
if (
|
|
602
|
-
actionMap.onExit = this.onExit;
|
|
603
|
-
}
|
|
604
|
-
if (this.activities) {
|
|
605
|
-
this.activities.forEach(activity => {
|
|
606
|
-
activityMap[getEventType(activity)] = false;
|
|
607
|
-
actionMap.onExit = actionMap.onExit.concat(stop(activity));
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (!candidates) {
|
|
438
|
+
if (!candidates || !candidates.length) {
|
|
612
439
|
return {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
440
|
+
value: undefined,
|
|
441
|
+
entryExitStates: undefined,
|
|
442
|
+
actions,
|
|
443
|
+
paths: []
|
|
617
444
|
};
|
|
618
445
|
}
|
|
619
446
|
|
|
620
447
|
let nextStateStrings: string[] = [];
|
|
448
|
+
let selectedTransition: TargetTransitionConfig;
|
|
621
449
|
|
|
622
450
|
for (const candidate of candidates) {
|
|
623
451
|
const {
|
|
624
452
|
cond,
|
|
625
|
-
in: stateIn
|
|
626
|
-
actions: transitionActions
|
|
453
|
+
in: stateIn
|
|
454
|
+
// actions: transitionActions
|
|
627
455
|
} = candidate as TransitionConfig;
|
|
628
456
|
const extendedStateObject = extendedState || {};
|
|
629
457
|
const eventObject = toEventObject(event);
|
|
@@ -631,172 +459,450 @@ class StateNode implements StateNodeConfig {
|
|
|
631
459
|
const isInState = stateIn
|
|
632
460
|
? matchesState(
|
|
633
461
|
toStateValue(stateIn, this.delimiter),
|
|
634
|
-
path(this.path.slice(0, -2))(
|
|
462
|
+
path(this.path.slice(0, -2))(state.value)
|
|
635
463
|
)
|
|
636
464
|
: true;
|
|
637
465
|
|
|
638
466
|
if (
|
|
639
|
-
(!cond ||
|
|
467
|
+
(!cond ||
|
|
468
|
+
this._evaluateCond(
|
|
469
|
+
cond,
|
|
470
|
+
extendedStateObject,
|
|
471
|
+
eventObject,
|
|
472
|
+
state.value
|
|
473
|
+
)) &&
|
|
640
474
|
(!stateIn || isInState)
|
|
641
475
|
) {
|
|
642
476
|
nextStateStrings = Array.isArray(candidate.target)
|
|
643
477
|
? candidate.target
|
|
644
478
|
: [candidate.target];
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
479
|
+
actions.push(...(candidate.actions ? candidate.actions : [])); // TODO: fixme;
|
|
480
|
+
selectedTransition = candidate;
|
|
648
481
|
break;
|
|
649
482
|
}
|
|
650
483
|
}
|
|
651
484
|
|
|
652
485
|
if (nextStateStrings.length === 0) {
|
|
653
486
|
return {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
487
|
+
value: undefined,
|
|
488
|
+
entryExitStates: undefined,
|
|
489
|
+
actions,
|
|
490
|
+
paths: []
|
|
658
491
|
};
|
|
659
492
|
}
|
|
660
493
|
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
494
|
+
const nextStateNodes = flatMap(
|
|
495
|
+
nextStateStrings.map(str =>
|
|
496
|
+
this.getRelativeStateNodes(str, state.historyValue)
|
|
497
|
+
)
|
|
498
|
+
);
|
|
664
499
|
|
|
665
|
-
|
|
666
|
-
const nextStatePath = this.getResolvedPath(nextStateString);
|
|
667
|
-
let currentState = isStateId(nextStateString)
|
|
668
|
-
? this.machine
|
|
669
|
-
: this.parent;
|
|
670
|
-
let currentHistory = history;
|
|
671
|
-
let currentPath = this.key;
|
|
500
|
+
const nextStatePaths = nextStateNodes.map(stateNode => stateNode.path);
|
|
672
501
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
502
|
+
const entryExitStates = nextStateNodes.reduce(
|
|
503
|
+
(allEntryExitStates, nextStateNode) => {
|
|
504
|
+
const { entry, exit } = this._getEntryExitStates(
|
|
505
|
+
nextStateNode,
|
|
506
|
+
!!selectedTransition.internal
|
|
507
|
+
);
|
|
679
508
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
509
|
+
return {
|
|
510
|
+
entry: new Set([
|
|
511
|
+
...Array.from(allEntryExitStates.entry),
|
|
512
|
+
...Array.from(entry)
|
|
513
|
+
]),
|
|
514
|
+
exit: new Set([
|
|
515
|
+
...Array.from(allEntryExitStates.exit),
|
|
516
|
+
...Array.from(exit)
|
|
517
|
+
])
|
|
518
|
+
};
|
|
519
|
+
},
|
|
520
|
+
{ entry: new Set(), exit: new Set() } as EntryExitStates
|
|
521
|
+
);
|
|
683
522
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
523
|
+
return {
|
|
524
|
+
value: this.machine.resolve(
|
|
525
|
+
pathsToStateValue(
|
|
526
|
+
flatMap(
|
|
527
|
+
nextStateStrings.map(str =>
|
|
528
|
+
this.getRelativeStateNodes(str, state.historyValue).map(
|
|
529
|
+
s => s.path
|
|
530
|
+
)
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
),
|
|
535
|
+
entryExitStates,
|
|
536
|
+
actions,
|
|
537
|
+
paths: nextStatePaths
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
private _getEntryExitStates(
|
|
541
|
+
nextStateNode: StateNode,
|
|
542
|
+
internal: boolean
|
|
543
|
+
): EntryExitStates {
|
|
544
|
+
const entryExitStates = {
|
|
545
|
+
entry: [] as StateNode[],
|
|
546
|
+
exit: [] as StateNode[]
|
|
547
|
+
};
|
|
700
548
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
currentState = currentState.getStateNode(subPath);
|
|
704
|
-
}
|
|
705
|
-
} catch (e) {
|
|
706
|
-
throw new Error(
|
|
707
|
-
`Event '${event}' on state '${currentPath}' leads to undefined state '${nextStatePath.join(
|
|
708
|
-
this.delimiter
|
|
709
|
-
)}'.`
|
|
710
|
-
);
|
|
711
|
-
}
|
|
549
|
+
const fromPath = this.path;
|
|
550
|
+
const toPath = nextStateNode.path;
|
|
712
551
|
|
|
713
|
-
|
|
714
|
-
actionMap.onEntry = actionMap.onEntry.concat(currentState.onEntry);
|
|
715
|
-
}
|
|
716
|
-
if (currentState.activities) {
|
|
717
|
-
currentState.activities.forEach(activity => {
|
|
718
|
-
activityMap[getEventType(activity)] = true;
|
|
719
|
-
actionMap.onEntry = actionMap.onEntry.concat(start(activity));
|
|
720
|
-
});
|
|
721
|
-
}
|
|
552
|
+
let parent = this.machine;
|
|
722
553
|
|
|
723
|
-
|
|
554
|
+
for (let i = 0; i < Math.min(fromPath.length, toPath.length); i++) {
|
|
555
|
+
const fromPathSegment = fromPath[i];
|
|
556
|
+
const toPathSegment = toPath[i];
|
|
724
557
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
558
|
+
if (fromPathSegment === toPathSegment) {
|
|
559
|
+
parent = parent.getStateNode(fromPathSegment);
|
|
560
|
+
} else {
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const commonAncestorPath = parent.path;
|
|
729
566
|
|
|
730
|
-
|
|
731
|
-
|
|
567
|
+
let marker: StateNode = parent;
|
|
568
|
+
for (const segment of fromPath.slice(commonAncestorPath.length)) {
|
|
569
|
+
marker = marker.getStateNode(segment);
|
|
570
|
+
entryExitStates.exit.unshift(marker);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Child node
|
|
574
|
+
if (parent === this) {
|
|
575
|
+
if (!internal) {
|
|
576
|
+
entryExitStates.exit.push(this);
|
|
577
|
+
entryExitStates.entry.push(this);
|
|
732
578
|
}
|
|
579
|
+
}
|
|
733
580
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
);
|
|
581
|
+
marker = parent;
|
|
582
|
+
for (const segment of toPath.slice(commonAncestorPath.length)) {
|
|
583
|
+
marker = marker.getStateNode(segment);
|
|
584
|
+
entryExitStates.entry.push(marker);
|
|
585
|
+
}
|
|
740
586
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
587
|
+
return {
|
|
588
|
+
entry: new Set(entryExitStates.entry),
|
|
589
|
+
exit: new Set(entryExitStates.exit)
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
private _evaluateCond(
|
|
593
|
+
condition: Condition,
|
|
594
|
+
extendedState: any,
|
|
595
|
+
eventObject: EventObject,
|
|
596
|
+
interimState: StateValue
|
|
597
|
+
): boolean {
|
|
598
|
+
let condFn: ConditionPredicate;
|
|
599
|
+
|
|
600
|
+
if (typeof condition === 'string') {
|
|
601
|
+
if (!this.machine.options.guards[condition]) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
`String condition '${condition}' is not defined on machine '${
|
|
604
|
+
this.machine.id
|
|
605
|
+
}'`
|
|
747
606
|
);
|
|
748
607
|
}
|
|
749
608
|
|
|
750
|
-
|
|
609
|
+
condFn = this.machine.options.guards[condition];
|
|
610
|
+
} else {
|
|
611
|
+
condFn = condition;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return condFn(extendedState, eventObject, interimState);
|
|
615
|
+
}
|
|
616
|
+
private _getActions(transition: StateTransition): Action[] {
|
|
617
|
+
const entryExitActions = {
|
|
618
|
+
entry: transition.entryExitStates
|
|
619
|
+
? flatMap(
|
|
620
|
+
Array.from(transition.entryExitStates.entry).map(n => [
|
|
621
|
+
...n.onEntry,
|
|
622
|
+
...(n.activities
|
|
623
|
+
? n.activities.map(activity => start(activity))
|
|
624
|
+
: [])
|
|
625
|
+
])
|
|
626
|
+
)
|
|
627
|
+
: [],
|
|
628
|
+
exit: transition.entryExitStates
|
|
629
|
+
? flatMap(
|
|
630
|
+
Array.from(transition.entryExitStates.exit).map(n => [
|
|
631
|
+
...n.onExit,
|
|
632
|
+
...(n.activities
|
|
633
|
+
? n.activities.map(activity => stop(activity))
|
|
634
|
+
: [])
|
|
635
|
+
])
|
|
636
|
+
)
|
|
637
|
+
: []
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const actions = (entryExitActions.exit || [])
|
|
641
|
+
.concat(transition.actions || [])
|
|
642
|
+
.concat(entryExitActions.entry || []);
|
|
643
|
+
|
|
644
|
+
return actions;
|
|
645
|
+
}
|
|
646
|
+
private _getActivities(
|
|
647
|
+
state: State,
|
|
648
|
+
transition: StateTransition
|
|
649
|
+
): ActivityMap {
|
|
650
|
+
if (!transition.entryExitStates) {
|
|
651
|
+
return {};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const activityMap = { ...state.activities };
|
|
655
|
+
|
|
656
|
+
Array.from(transition.entryExitStates.exit).forEach(stateNode => {
|
|
657
|
+
if (!stateNode.activities) {
|
|
658
|
+
return; // TODO: fixme
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
stateNode.activities.forEach(activity => {
|
|
662
|
+
activityMap[getActionType(activity)] = false;
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
Array.from(transition.entryExitStates.entry).forEach(stateNode => {
|
|
667
|
+
if (!stateNode.activities) {
|
|
668
|
+
return; // TODO: fixme
|
|
669
|
+
}
|
|
751
670
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
671
|
+
stateNode.activities.forEach(activity => {
|
|
672
|
+
activityMap[getActionType(activity)] = true;
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
return activityMap;
|
|
677
|
+
}
|
|
678
|
+
public transition(
|
|
679
|
+
state: StateValue | State,
|
|
680
|
+
event: Event,
|
|
681
|
+
extendedState?: any
|
|
682
|
+
): State {
|
|
683
|
+
const resolvedStateValue =
|
|
684
|
+
typeof state === 'string'
|
|
685
|
+
? this.resolve(pathToStateValue(this.getResolvedPath(state)))
|
|
686
|
+
: state instanceof State
|
|
687
|
+
? state
|
|
688
|
+
: this.resolve(state);
|
|
689
|
+
|
|
690
|
+
const eventType = getEventType(event);
|
|
691
|
+
|
|
692
|
+
if (this.strict) {
|
|
693
|
+
if (this.events.indexOf(eventType) === -1) {
|
|
694
|
+
throw new Error(
|
|
695
|
+
`Machine '${this.id}' does not accept event '${eventType}'`
|
|
757
696
|
);
|
|
758
697
|
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const currentState = State.from(resolvedStateValue);
|
|
701
|
+
|
|
702
|
+
const historyValue =
|
|
703
|
+
resolvedStateValue instanceof State
|
|
704
|
+
? resolvedStateValue.historyValue
|
|
705
|
+
? resolvedStateValue.historyValue
|
|
706
|
+
: (this.machine.historyValue(
|
|
707
|
+
resolvedStateValue.value
|
|
708
|
+
) as HistoryValue)
|
|
709
|
+
: (this.machine.historyValue(resolvedStateValue) as HistoryValue);
|
|
710
|
+
|
|
711
|
+
const stateTransition = this._transition(
|
|
712
|
+
currentState.value,
|
|
713
|
+
currentState,
|
|
714
|
+
event,
|
|
715
|
+
extendedState
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
try {
|
|
719
|
+
this.ensureValidPaths(stateTransition.paths);
|
|
720
|
+
} catch (e) {
|
|
721
|
+
throw new Error(
|
|
722
|
+
`Event '${eventType}' leads to an invalid configuration: ` + e.message
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const actions = this._getActions(stateTransition);
|
|
727
|
+
const activities = this._getActivities(currentState, stateTransition);
|
|
759
728
|
|
|
760
|
-
|
|
729
|
+
const raisedEvents = actions.filter(
|
|
730
|
+
action =>
|
|
731
|
+
typeof action === 'object' &&
|
|
732
|
+
(action.type === actionTypes.raise || action.type === actionTypes.null)
|
|
733
|
+
) as ActionObject[];
|
|
761
734
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
735
|
+
const nonEventActions = actions.filter(
|
|
736
|
+
action =>
|
|
737
|
+
typeof action !== 'object' ||
|
|
738
|
+
(action.type !== actionTypes.raise && action.type !== actionTypes.null)
|
|
739
|
+
);
|
|
740
|
+
const stateNodes = stateTransition.value
|
|
741
|
+
? this.getStateNodes(stateTransition.value)
|
|
742
|
+
: [];
|
|
743
|
+
|
|
744
|
+
const isTransient = stateNodes.some(stateNode => stateNode.transient);
|
|
745
|
+
if (isTransient) {
|
|
746
|
+
raisedEvents.push({ type: actionTypes.null });
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const data = {};
|
|
750
|
+
stateNodes.forEach(stateNode => {
|
|
751
|
+
data[stateNode.id] = stateNode.data;
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const nextState = stateTransition.value
|
|
755
|
+
? new State(
|
|
756
|
+
stateTransition.value,
|
|
757
|
+
StateNode.updateHistoryValue(historyValue, stateTransition.value),
|
|
758
|
+
currentState,
|
|
759
|
+
nonEventActions,
|
|
760
|
+
activities,
|
|
761
|
+
data,
|
|
762
|
+
raisedEvents
|
|
763
|
+
)
|
|
764
|
+
: undefined;
|
|
765
|
+
|
|
766
|
+
if (!nextState) {
|
|
767
|
+
// Unchanged state should be returned with no actions
|
|
768
|
+
return State.inert(currentState);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Dispose of previous histories to prevent memory leaks
|
|
772
|
+
delete currentState.history;
|
|
773
|
+
|
|
774
|
+
let maybeNextState = nextState;
|
|
775
|
+
while (raisedEvents.length) {
|
|
776
|
+
const currentActions = maybeNextState.actions;
|
|
777
|
+
const raisedEvent = raisedEvents.shift()!;
|
|
778
|
+
maybeNextState = this.transition(
|
|
779
|
+
maybeNextState,
|
|
780
|
+
raisedEvent.type === actionTypes.null ? NULL_EVENT : raisedEvent.event,
|
|
781
|
+
extendedState
|
|
782
|
+
);
|
|
783
|
+
maybeNextState.actions.unshift(...currentActions);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return maybeNextState;
|
|
787
|
+
}
|
|
788
|
+
private ensureValidPaths(paths: string[][]): void {
|
|
789
|
+
const visitedParents = new Map<StateNode, StateNode[]>();
|
|
790
|
+
|
|
791
|
+
const stateNodes = flatMap(
|
|
792
|
+
paths.map(_path => this.getRelativeStateNodes(_path))
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
outer: for (const stateNode of stateNodes) {
|
|
796
|
+
let marker = stateNode;
|
|
797
|
+
|
|
798
|
+
while (marker.parent) {
|
|
799
|
+
if (visitedParents.has(marker.parent)) {
|
|
800
|
+
if (marker.parent.parallel) {
|
|
801
|
+
continue outer;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
throw new Error(
|
|
805
|
+
`State node '${stateNode.id}' shares parent '${
|
|
806
|
+
marker.parent.id
|
|
807
|
+
}' with state node '${visitedParents
|
|
808
|
+
.get(marker.parent)!
|
|
809
|
+
.map(a => a.id)}'`
|
|
810
|
+
);
|
|
765
811
|
}
|
|
766
|
-
currentState = currentState.states[currentState.initial];
|
|
767
812
|
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
});
|
|
813
|
+
if (!visitedParents.get(marker.parent)) {
|
|
814
|
+
visitedParents.set(marker.parent, [stateNode]);
|
|
815
|
+
} else {
|
|
816
|
+
visitedParents.get(marker.parent)!.push(stateNode);
|
|
773
817
|
}
|
|
818
|
+
|
|
819
|
+
marker = marker.parent;
|
|
774
820
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
).concat(currentState.on[NULL_EVENT] ? { type: actionTypes.null } : []);
|
|
782
|
-
myActions.forEach(action => raisedEvents.push(action));
|
|
783
|
-
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
public getStateNode(stateKey: string): StateNode {
|
|
824
|
+
if (isStateId(stateKey)) {
|
|
825
|
+
return this.machine.getStateNodeById(stateKey);
|
|
826
|
+
}
|
|
784
827
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
828
|
+
if (!this.states) {
|
|
829
|
+
throw new Error(
|
|
830
|
+
`Unable to retrieve child state '${stateKey}' from '${
|
|
831
|
+
this.id
|
|
832
|
+
}'; no child states exist.`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const result = this.states[stateKey];
|
|
837
|
+
if (!result) {
|
|
838
|
+
throw new Error(
|
|
839
|
+
`Child state '${stateKey}' does not exist on '${this.id}'`
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return result;
|
|
791
844
|
}
|
|
845
|
+
public getStateNodeById(stateId: string): StateNode {
|
|
846
|
+
const resolvedStateId = isStateId(stateId)
|
|
847
|
+
? stateId.slice(STATE_IDENTIFIER.length)
|
|
848
|
+
: stateId;
|
|
849
|
+
const stateNode = this.machine.idMap[resolvedStateId];
|
|
850
|
+
|
|
851
|
+
if (!stateNode) {
|
|
852
|
+
throw new Error(
|
|
853
|
+
`Substate '#${resolvedStateId}' does not exist on '${this.id}'`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return stateNode;
|
|
858
|
+
}
|
|
859
|
+
public getStateNodeByPath(statePath: string | string[]): StateNode {
|
|
860
|
+
const arrayStatePath = toStatePath(statePath, this.delimiter);
|
|
861
|
+
let currentStateNode: StateNode = this;
|
|
862
|
+
while (arrayStatePath.length) {
|
|
863
|
+
const key = arrayStatePath.shift()!;
|
|
864
|
+
currentStateNode = currentStateNode.getStateNode(key);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return currentStateNode;
|
|
868
|
+
}
|
|
869
|
+
private resolve(stateValue: StateValue): StateValue {
|
|
870
|
+
if (typeof stateValue === 'string') {
|
|
871
|
+
const subStateNode = this.getStateNode(stateValue);
|
|
872
|
+
return subStateNode.initial
|
|
873
|
+
? { [stateValue]: subStateNode.initialStateValue! }
|
|
874
|
+
: stateValue;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (this.parallel) {
|
|
878
|
+
return mapValues(
|
|
879
|
+
this.initialStateValue as Record<string, StateValue>,
|
|
880
|
+
(subStateValue, subStateKey) => {
|
|
881
|
+
return subStateValue
|
|
882
|
+
? this.getStateNode(subStateKey).resolve(
|
|
883
|
+
stateValue[subStateKey] || subStateValue
|
|
884
|
+
)
|
|
885
|
+
: {};
|
|
886
|
+
}
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return mapValues(stateValue, (subStateValue, subStateKey) => {
|
|
891
|
+
return subStateValue
|
|
892
|
+
? this.getStateNode(subStateKey).resolve(subStateValue)
|
|
893
|
+
: {};
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
792
897
|
private get resolvedStateValue(): StateValue {
|
|
793
898
|
const { key } = this;
|
|
794
899
|
|
|
795
900
|
if (this.parallel) {
|
|
796
901
|
return {
|
|
797
|
-
[key]:
|
|
902
|
+
[key]: mapFilterValues(
|
|
798
903
|
this.states,
|
|
799
|
-
stateNode => stateNode.resolvedStateValue[stateNode.key]
|
|
904
|
+
stateNode => stateNode.resolvedStateValue[stateNode.key],
|
|
905
|
+
stateNode => !stateNode.history
|
|
800
906
|
)
|
|
801
907
|
};
|
|
802
908
|
}
|
|
@@ -826,16 +932,19 @@ class StateNode implements StateNodeConfig {
|
|
|
826
932
|
return toStatePath(stateIdentifier, this.delimiter);
|
|
827
933
|
}
|
|
828
934
|
private get initialStateValue(): StateValue | undefined {
|
|
829
|
-
|
|
830
|
-
this.__cache.initialState
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
935
|
+
if (this.__cache.initialState) {
|
|
936
|
+
return this.__cache.initialState;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
const initialStateValue = (this.parallel
|
|
940
|
+
? mapFilterValues(
|
|
941
|
+
this.states as Record<string, StateNode>,
|
|
942
|
+
state => state.initialStateValue || {},
|
|
943
|
+
stateNode => !stateNode.history
|
|
944
|
+
)
|
|
945
|
+
: typeof this.resolvedStateValue === 'string'
|
|
946
|
+
? undefined
|
|
947
|
+
: this.resolvedStateValue[this.key]) as StateValue;
|
|
839
948
|
|
|
840
949
|
this.__cache.initialState = initialStateValue;
|
|
841
950
|
|
|
@@ -865,7 +974,53 @@ class StateNode implements StateNodeConfig {
|
|
|
865
974
|
}
|
|
866
975
|
});
|
|
867
976
|
|
|
868
|
-
|
|
977
|
+
// TODO: deduplicate - DRY (from this.transition())
|
|
978
|
+
const raisedEvents = actions.filter(
|
|
979
|
+
action =>
|
|
980
|
+
typeof action === 'object' &&
|
|
981
|
+
(action.type === actionTypes.raise || action.type === actionTypes.null)
|
|
982
|
+
) as ActionObject[];
|
|
983
|
+
|
|
984
|
+
const initialState = new State(
|
|
985
|
+
initialStateValue,
|
|
986
|
+
undefined,
|
|
987
|
+
undefined,
|
|
988
|
+
actions,
|
|
989
|
+
activityMap
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
let maybeNextState = initialState;
|
|
993
|
+
while (raisedEvents.length) {
|
|
994
|
+
const currentActions = maybeNextState.actions;
|
|
995
|
+
const raisedEvent = raisedEvents.shift()!;
|
|
996
|
+
maybeNextState = this.transition(
|
|
997
|
+
maybeNextState,
|
|
998
|
+
raisedEvent.type === actionTypes.null ? NULL_EVENT : raisedEvent.event,
|
|
999
|
+
undefined // TODO: consider initial state given external state
|
|
1000
|
+
);
|
|
1001
|
+
maybeNextState.actions.unshift(...currentActions);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
return maybeNextState;
|
|
1005
|
+
}
|
|
1006
|
+
public get target(): StateValue | undefined {
|
|
1007
|
+
let target;
|
|
1008
|
+
if (this.history) {
|
|
1009
|
+
const historyConfig = this.config as HistoryStateNodeConfig;
|
|
1010
|
+
if (historyConfig.target && typeof historyConfig.target === 'string') {
|
|
1011
|
+
target = isStateId(historyConfig.target)
|
|
1012
|
+
? pathToStateValue(
|
|
1013
|
+
this.machine
|
|
1014
|
+
.getStateNodeById(historyConfig.target)
|
|
1015
|
+
.path.slice(this.path.length - 1)
|
|
1016
|
+
)
|
|
1017
|
+
: historyConfig.target;
|
|
1018
|
+
} else {
|
|
1019
|
+
target = historyConfig.target;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
return target;
|
|
869
1024
|
}
|
|
870
1025
|
public getStates(stateValue: StateValue): StateNode[] {
|
|
871
1026
|
if (typeof stateValue === 'string') {
|
|
@@ -880,30 +1035,215 @@ class StateNode implements StateNodeConfig {
|
|
|
880
1035
|
|
|
881
1036
|
return stateNodes;
|
|
882
1037
|
}
|
|
883
|
-
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Returns the leaf nodes from a state path relative to this state node.
|
|
1041
|
+
*
|
|
1042
|
+
* @param relativeStateId The relative state path to retrieve the state nodes
|
|
1043
|
+
* @param history The previous state to retrieve history
|
|
1044
|
+
* @param resolve Whether state nodes should resolve to initial child state nodes
|
|
1045
|
+
*/
|
|
1046
|
+
public getRelativeStateNodes(
|
|
1047
|
+
relativeStateId: string | string[],
|
|
1048
|
+
historyValue?: HistoryValue,
|
|
1049
|
+
resolve: boolean = true
|
|
1050
|
+
): StateNode[] {
|
|
884
1051
|
if (typeof relativeStateId === 'string' && isStateId(relativeStateId)) {
|
|
885
|
-
|
|
1052
|
+
const unresolvedStateNode = this.getStateNodeById(relativeStateId);
|
|
1053
|
+
|
|
1054
|
+
return resolve
|
|
1055
|
+
? unresolvedStateNode.history
|
|
1056
|
+
? unresolvedStateNode.resolveHistory(historyValue)
|
|
1057
|
+
: unresolvedStateNode.initialStateNodes
|
|
1058
|
+
: [unresolvedStateNode];
|
|
886
1059
|
}
|
|
887
1060
|
|
|
888
1061
|
const statePath = toStatePath(relativeStateId, this.delimiter);
|
|
889
1062
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
)
|
|
902
|
-
|
|
1063
|
+
const rootStateNode = this.parent || this;
|
|
1064
|
+
|
|
1065
|
+
const unresolvedStateNodes = rootStateNode.getFromRelativePath(
|
|
1066
|
+
statePath,
|
|
1067
|
+
historyValue
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
if (!resolve) {
|
|
1071
|
+
return unresolvedStateNodes;
|
|
1072
|
+
}
|
|
1073
|
+
return flatMap(
|
|
1074
|
+
unresolvedStateNodes.map(stateNode => stateNode.initialStateNodes)
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
public get initialStateNodes(): StateNode[] {
|
|
1078
|
+
// todo - isLeafNode or something
|
|
1079
|
+
if (!this.parallel && !this.initial) {
|
|
1080
|
+
return [this];
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const { initialState } = this;
|
|
1084
|
+
const initialStateNodePaths = toStatePaths(initialState.value);
|
|
1085
|
+
return flatMap(
|
|
1086
|
+
initialStateNodePaths.map(initialPath =>
|
|
1087
|
+
this.getFromRelativePath(initialPath)
|
|
1088
|
+
)
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Retrieves state nodes from a relative path to this state node.
|
|
1093
|
+
*
|
|
1094
|
+
* @param relativePath The relative path from this state node
|
|
1095
|
+
* @param historyValue
|
|
1096
|
+
*/
|
|
1097
|
+
public getFromRelativePath(
|
|
1098
|
+
relativePath: string[],
|
|
1099
|
+
historyValue?: HistoryValue
|
|
1100
|
+
): StateNode[] {
|
|
1101
|
+
if (!relativePath.length) {
|
|
1102
|
+
return [this];
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const [x, ...xs] = relativePath;
|
|
1106
|
+
|
|
1107
|
+
if (!this.states) {
|
|
903
1108
|
throw new Error(
|
|
904
|
-
`
|
|
1109
|
+
`Cannot retrieve subPath '${x}' from node with no states`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// TODO: remove (4.0)
|
|
1114
|
+
if (x === HISTORY_KEY) {
|
|
1115
|
+
if (!historyValue) {
|
|
1116
|
+
return [this];
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const subHistoryValue = nestedPath<HistoryValue>(this.path, 'states')(
|
|
1120
|
+
historyValue
|
|
1121
|
+
).current;
|
|
1122
|
+
|
|
1123
|
+
if (typeof subHistoryValue === 'string') {
|
|
1124
|
+
return this.states[subHistoryValue].getFromRelativePath(
|
|
1125
|
+
xs,
|
|
1126
|
+
historyValue
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
return flatMap(
|
|
1131
|
+
Object.keys(subHistoryValue!).map(key => {
|
|
1132
|
+
return this.states[key].getFromRelativePath(xs, historyValue);
|
|
1133
|
+
})
|
|
905
1134
|
);
|
|
906
1135
|
}
|
|
1136
|
+
|
|
1137
|
+
const childStateNode = this.getStateNode(x);
|
|
1138
|
+
|
|
1139
|
+
if (childStateNode.history) {
|
|
1140
|
+
return childStateNode.resolveHistory(historyValue);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (!this.states[x]) {
|
|
1144
|
+
throw new Error(`Child state '${x}' does not exist on '${this.id}'`);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
return this.states[x].getFromRelativePath(xs, historyValue);
|
|
1148
|
+
}
|
|
1149
|
+
public static updateHistoryValue(
|
|
1150
|
+
hist: HistoryValue,
|
|
1151
|
+
stateValue: StateValue
|
|
1152
|
+
): HistoryValue {
|
|
1153
|
+
function update(
|
|
1154
|
+
_hist: HistoryValue,
|
|
1155
|
+
_sv: StateValue
|
|
1156
|
+
): Record<string, HistoryValue | undefined> {
|
|
1157
|
+
return mapValues(_hist.states, (subHist, key) => {
|
|
1158
|
+
if (!subHist) {
|
|
1159
|
+
return undefined;
|
|
1160
|
+
}
|
|
1161
|
+
const subStateValue =
|
|
1162
|
+
(typeof _sv === 'string' ? undefined : _sv[key]) ||
|
|
1163
|
+
(subHist ? subHist.current : undefined);
|
|
1164
|
+
|
|
1165
|
+
if (!subStateValue) {
|
|
1166
|
+
return undefined;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
return {
|
|
1170
|
+
current: subStateValue,
|
|
1171
|
+
states: update(subHist, subStateValue)
|
|
1172
|
+
};
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
return {
|
|
1176
|
+
current: stateValue,
|
|
1177
|
+
states: update(hist, stateValue)
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
public historyValue(
|
|
1181
|
+
relativeStateValue?: StateValue | undefined
|
|
1182
|
+
): HistoryValue | undefined {
|
|
1183
|
+
if (!Object.keys(this.states).length) {
|
|
1184
|
+
return undefined;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
return {
|
|
1188
|
+
current: relativeStateValue || this.initialStateValue,
|
|
1189
|
+
states: mapFilterValues(
|
|
1190
|
+
this.states,
|
|
1191
|
+
(stateNode, key) => {
|
|
1192
|
+
if (!relativeStateValue) {
|
|
1193
|
+
return stateNode.historyValue();
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const subStateValue =
|
|
1197
|
+
typeof relativeStateValue === 'string'
|
|
1198
|
+
? undefined
|
|
1199
|
+
: relativeStateValue[key];
|
|
1200
|
+
|
|
1201
|
+
return stateNode.historyValue(
|
|
1202
|
+
subStateValue || stateNode.initialStateValue
|
|
1203
|
+
);
|
|
1204
|
+
},
|
|
1205
|
+
stateNode => !stateNode.history
|
|
1206
|
+
)
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Resolves to the historical value(s) of the parent state node,
|
|
1211
|
+
* represented by state nodes.
|
|
1212
|
+
*
|
|
1213
|
+
* @param historyValue
|
|
1214
|
+
*/
|
|
1215
|
+
private resolveHistory(historyValue?: HistoryValue): StateNode[] {
|
|
1216
|
+
if (!this.history) {
|
|
1217
|
+
return [this];
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const parent = this.parent!;
|
|
1221
|
+
|
|
1222
|
+
if (!historyValue) {
|
|
1223
|
+
return this.target
|
|
1224
|
+
? flatMap(
|
|
1225
|
+
toStatePaths(this.target).map(relativeChildPath =>
|
|
1226
|
+
parent.getFromRelativePath(relativeChildPath)
|
|
1227
|
+
)
|
|
1228
|
+
)
|
|
1229
|
+
: this.parent!.initialStateNodes;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const subHistoryValue = nestedPath<HistoryValue>(parent.path, 'states')(
|
|
1233
|
+
historyValue
|
|
1234
|
+
).current;
|
|
1235
|
+
|
|
1236
|
+
if (typeof subHistoryValue === 'string') {
|
|
1237
|
+
return [parent.getStateNode(subHistoryValue)];
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
return flatMap(
|
|
1241
|
+
toStatePaths(subHistoryValue!).map(subStatePath => {
|
|
1242
|
+
return this.history === 'deep'
|
|
1243
|
+
? parent.getFromRelativePath(subStatePath)
|
|
1244
|
+
: [parent.states[subStatePath[0]]];
|
|
1245
|
+
})
|
|
1246
|
+
);
|
|
907
1247
|
}
|
|
908
1248
|
get events(): EventType[] {
|
|
909
1249
|
if (this.__cache.events) {
|
|
@@ -925,6 +1265,33 @@ class StateNode implements StateNodeConfig {
|
|
|
925
1265
|
|
|
926
1266
|
return (this.__cache.events = Array.from(events));
|
|
927
1267
|
}
|
|
1268
|
+
private formatTransition(
|
|
1269
|
+
targets: string[],
|
|
1270
|
+
transitionConfig?: TransitionConfig
|
|
1271
|
+
): TargetTransitionConfig {
|
|
1272
|
+
let internal = transitionConfig ? transitionConfig.internal : false;
|
|
1273
|
+
|
|
1274
|
+
// Format targets to their full string path
|
|
1275
|
+
const formattedTargets = targets.map(target => {
|
|
1276
|
+
const internalTarget =
|
|
1277
|
+
typeof target === 'string' && target[0] === this.delimiter;
|
|
1278
|
+
internal = internal || internalTarget;
|
|
1279
|
+
|
|
1280
|
+
// If internal target is defined on machine,
|
|
1281
|
+
// do not include machine key on target
|
|
1282
|
+
if (internalTarget && !this.parent) {
|
|
1283
|
+
return target.slice(1);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
return internalTarget ? this.key + target : target;
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
return {
|
|
1290
|
+
...transitionConfig,
|
|
1291
|
+
target: formattedTargets,
|
|
1292
|
+
internal
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
928
1295
|
private formatTransitions(
|
|
929
1296
|
onConfig: Record<string, Transition | undefined>
|
|
930
1297
|
): Record<string, ConditionalTransitionConfig> {
|
|
@@ -934,27 +1301,36 @@ class StateNode implements StateNodeConfig {
|
|
|
934
1301
|
}
|
|
935
1302
|
|
|
936
1303
|
if (Array.isArray(value)) {
|
|
937
|
-
return value
|
|
1304
|
+
return value.map(targetTransitionConfig =>
|
|
1305
|
+
this.formatTransition(
|
|
1306
|
+
([] as string[]).concat(targetTransitionConfig.target),
|
|
1307
|
+
targetTransitionConfig
|
|
1308
|
+
)
|
|
1309
|
+
);
|
|
938
1310
|
}
|
|
939
1311
|
|
|
940
1312
|
if (typeof value === 'string') {
|
|
941
|
-
return [
|
|
1313
|
+
return [this.formatTransition([value])];
|
|
942
1314
|
}
|
|
943
1315
|
|
|
944
1316
|
return Object.keys(value).map(target => {
|
|
945
|
-
return
|
|
946
|
-
target,
|
|
947
|
-
...value[target]
|
|
948
|
-
};
|
|
1317
|
+
return this.formatTransition([target], value[target]);
|
|
949
1318
|
});
|
|
950
1319
|
});
|
|
951
1320
|
}
|
|
952
1321
|
}
|
|
953
1322
|
|
|
954
|
-
export function Machine
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1323
|
+
export function Machine<
|
|
1324
|
+
T extends StandardMachineConfig | ParallelMachineConfig
|
|
1325
|
+
>(
|
|
1326
|
+
config: T,
|
|
1327
|
+
options?: MachineOptions
|
|
1328
|
+
): T extends ParallelMachineConfig
|
|
1329
|
+
? ParallelMachine
|
|
1330
|
+
: T extends StandardMachineConfig ? StandardMachine : never {
|
|
1331
|
+
return new StateNode(config, options) as T extends ParallelMachineConfig
|
|
1332
|
+
? ParallelMachine
|
|
1333
|
+
: T extends StandardMachineConfig ? StandardMachine : never;
|
|
958
1334
|
}
|
|
959
1335
|
|
|
960
1336
|
export { StateNode };
|