@player-ui/player 0.3.1-next.1 → 0.4.0-next.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/dist/index.cjs.js +197 -39
- package/dist/index.d.ts +38 -13
- package/dist/index.esm.js +197 -40
- package/package.json +5 -4
- package/src/binding/resolver.ts +1 -3
- package/src/controllers/data.ts +4 -2
- package/src/controllers/validation/controller.ts +26 -13
- package/src/data/model.ts +5 -0
- package/src/expressions/evaluator.ts +1 -1
- package/src/expressions/parser.ts +64 -18
- package/src/expressions/types.ts +27 -12
- package/src/expressions/utils.ts +124 -1
- package/src/player.ts +28 -13
- package/src/string-resolver/index.ts +1 -0
- package/src/validator/validation-middleware.ts +21 -3
- package/src/view/plugins/string-resolver.ts +27 -5
package/src/player.ts
CHANGED
|
@@ -30,8 +30,8 @@ import type {
|
|
|
30
30
|
import { NOT_STARTED_STATE } from './types';
|
|
31
31
|
|
|
32
32
|
// Variables injected at build time
|
|
33
|
-
const PLAYER_VERSION = '0.
|
|
34
|
-
const COMMIT = '
|
|
33
|
+
const PLAYER_VERSION = '0.4.0-next.0';
|
|
34
|
+
const COMMIT = '6f7a8b3424cb819dfcbe830586a92711adf130ba';
|
|
35
35
|
|
|
36
36
|
export interface PlayerPlugin {
|
|
37
37
|
/**
|
|
@@ -286,10 +286,11 @@ export class Player {
|
|
|
286
286
|
|
|
287
287
|
flowController.hooks.flow.tap('player', (flow: FlowInstance) => {
|
|
288
288
|
flow.hooks.beforeTransition.tap('player', (state, transitionVal) => {
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
289
|
+
/** Checks to see if there are any transitions for a specific transition state (i.e. next, back). If not, it will default to * */
|
|
290
|
+
const computedTransitionVal = state.transitions[transitionVal]
|
|
291
|
+
? transitionVal
|
|
292
|
+
: '*';
|
|
293
|
+
if (state.onEnd && state.transitions[computedTransitionVal]) {
|
|
293
294
|
if (typeof state.onEnd === 'object' && 'exp' in state.onEnd) {
|
|
294
295
|
expressionEvaluator?.evaluate(state.onEnd.exp);
|
|
295
296
|
} else {
|
|
@@ -297,14 +298,19 @@ export class Player {
|
|
|
297
298
|
}
|
|
298
299
|
}
|
|
299
300
|
|
|
300
|
-
|
|
301
|
+
/** If the transition does not exist, then do not resolve any expressions */
|
|
302
|
+
if (
|
|
303
|
+
!('transitions' in state) ||
|
|
304
|
+
!state.transitions[computedTransitionVal]
|
|
305
|
+
) {
|
|
301
306
|
return state;
|
|
302
307
|
}
|
|
303
308
|
|
|
309
|
+
/** resolves and sets the transition to the computed exp */
|
|
304
310
|
return setIn(
|
|
305
311
|
state,
|
|
306
|
-
['transitions',
|
|
307
|
-
resolveStrings(state.transitions[
|
|
312
|
+
['transitions', computedTransitionVal],
|
|
313
|
+
resolveStrings(state.transitions[computedTransitionVal])
|
|
308
314
|
) as any;
|
|
309
315
|
});
|
|
310
316
|
|
|
@@ -348,11 +354,20 @@ export class Player {
|
|
|
348
354
|
|
|
349
355
|
// The nested transition call would trigger another round of the flow transition hooks to be called.
|
|
350
356
|
// This created a weird timing where this nested transition would happen before the view had a chance to respond to the first one
|
|
351
|
-
|
|
357
|
+
|
|
358
|
+
// Additionally, because we are using queueMicrotask, errors could get swallowed in the detached queue
|
|
359
|
+
// Use a try catch and fail player explicitly if any errors are caught in the nested transition/state
|
|
352
360
|
queueMicrotask(() => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
361
|
+
try {
|
|
362
|
+
flowController?.transition(
|
|
363
|
+
String(expressionEvaluator?.evaluate(exp))
|
|
364
|
+
);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
const state = this.getState();
|
|
367
|
+
if (error instanceof Error && state.status === 'in-progress') {
|
|
368
|
+
state.fail(error);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
356
371
|
});
|
|
357
372
|
}
|
|
358
373
|
|
|
@@ -12,13 +12,23 @@ import type { Logger } from '../logger';
|
|
|
12
12
|
|
|
13
13
|
import type { ValidationResponse } from './types';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* A BindingInstance with an indicator of whether or not it's a strong binding
|
|
17
|
+
*/
|
|
18
|
+
export type StrongOrWeakBinding = {
|
|
19
|
+
/** BindingInstance in question */
|
|
20
|
+
binding: BindingInstance;
|
|
21
|
+
/** Boolean indicating whether the relevant BindingInstance is a strong binding */
|
|
22
|
+
isStrong: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
15
25
|
/**
|
|
16
26
|
* Returns a validation object if the data is invalid or an set of BindingsInstances if the binding itself is a weak ref of another invalid validation
|
|
17
27
|
*/
|
|
18
28
|
export type MiddlewareChecker = (
|
|
19
29
|
binding: BindingInstance,
|
|
20
30
|
model: DataModelImpl
|
|
21
|
-
) => ValidationResponse | Set<
|
|
31
|
+
) => ValidationResponse | Set<StrongOrWeakBinding> | undefined;
|
|
22
32
|
|
|
23
33
|
/**
|
|
24
34
|
* Middleware for the data-model that caches the results of invalid data
|
|
@@ -60,7 +70,12 @@ export class ValidationMiddleware implements DataModelMiddleware {
|
|
|
60
70
|
if (validations === undefined) {
|
|
61
71
|
nextTransaction.push([binding, value]);
|
|
62
72
|
} else if (validations instanceof Set) {
|
|
63
|
-
|
|
73
|
+
validations.forEach((validation) => {
|
|
74
|
+
invalidBindings.push(validation.binding);
|
|
75
|
+
if (!validation.isStrong) {
|
|
76
|
+
nextTransaction.push([validation.binding, value]);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
64
79
|
} else {
|
|
65
80
|
this.logger?.debug(
|
|
66
81
|
`Invalid value for path: ${binding.asString()} - ${
|
|
@@ -75,7 +90,10 @@ export class ValidationMiddleware implements DataModelMiddleware {
|
|
|
75
90
|
nextTransaction.forEach(([binding]) =>
|
|
76
91
|
this.shadowModelPaths.delete(binding)
|
|
77
92
|
);
|
|
78
|
-
|
|
93
|
+
const result = next.set(nextTransaction, options);
|
|
94
|
+
if (invalidBindings.length === 0) {
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
return invalidBindings.map((binding) => {
|
|
@@ -108,6 +108,12 @@ const findBasePath = (node: Node.Node): Node.PathSegment[] => {
|
|
|
108
108
|
|
|
109
109
|
/** A plugin that resolves all string references for each node */
|
|
110
110
|
export default class StringResolverPlugin implements ViewPlugin {
|
|
111
|
+
private propertiesToSkipCache: Map<string, Set<string>>;
|
|
112
|
+
|
|
113
|
+
constructor() {
|
|
114
|
+
this.propertiesToSkipCache = new Map();
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
applyResolver(resolver: Resolver) {
|
|
112
118
|
resolver.hooks.resolve.tap('string-resolver', (value, node, options) => {
|
|
113
119
|
if (node.type === NodeType.Empty || node.type === NodeType.Unknown) {
|
|
@@ -120,11 +126,27 @@ export default class StringResolverPlugin implements ViewPlugin {
|
|
|
120
126
|
node.type === NodeType.View
|
|
121
127
|
) {
|
|
122
128
|
/** Use specified properties to skip during string resolution, or default */
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
let propsToSkip: Set<string>;
|
|
130
|
+
if (node.type === NodeType.Asset || node.type === NodeType.View) {
|
|
131
|
+
propsToSkip = new Set(
|
|
132
|
+
node.plugins?.stringResolver?.propertiesToSkip ?? ['exp']
|
|
133
|
+
);
|
|
134
|
+
if (node.value?.id) {
|
|
135
|
+
this.propertiesToSkipCache.set(node.value.id, propsToSkip);
|
|
136
|
+
}
|
|
137
|
+
} else if (
|
|
138
|
+
node.parent?.type === NodeType.MultiNode &&
|
|
139
|
+
(node.parent?.parent?.type === NodeType.Asset ||
|
|
140
|
+
node.parent?.parent?.type === NodeType.View) &&
|
|
141
|
+
node.parent.parent.value?.id &&
|
|
142
|
+
this.propertiesToSkipCache.has(node.parent.parent.value.id)
|
|
143
|
+
) {
|
|
144
|
+
propsToSkip = this.propertiesToSkipCache.get(
|
|
145
|
+
node.parent.parent.value.id
|
|
146
|
+
) as Set<string>;
|
|
147
|
+
} else {
|
|
148
|
+
propsToSkip = new Set(['exp']);
|
|
149
|
+
}
|
|
128
150
|
|
|
129
151
|
const nodePath = findBasePath(node);
|
|
130
152
|
|