@player-ui/player 0.4.0-next.8 → 0.4.0-next.9
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 +622 -325
- package/dist/index.d.ts +188 -72
- package/dist/index.esm.js +619 -327
- package/dist/player.dev.js +623 -326
- package/dist/player.prod.js +1 -1
- package/package.json +11 -3
- package/src/binding/binding.ts +8 -0
- package/src/binding/index.ts +1 -1
- package/src/controllers/constants/index.ts +9 -5
- package/src/controllers/data.ts +49 -52
- package/src/controllers/flow/controller.ts +16 -12
- package/src/controllers/flow/flow.ts +6 -1
- package/src/controllers/validation/binding-tracker.ts +42 -19
- package/src/controllers/validation/controller.ts +265 -85
- package/src/controllers/view/asset-transform.ts +4 -1
- package/src/controllers/view/controller.ts +19 -2
- package/src/data/dependency-tracker.ts +14 -0
- package/src/data/local-model.ts +25 -1
- package/src/data/model.ts +55 -8
- package/src/data/noop-model.ts +2 -0
- package/src/expressions/evaluator-functions.ts +24 -2
- package/src/expressions/evaluator.ts +35 -31
- package/src/expressions/types.ts +17 -5
- package/src/expressions/utils.ts +19 -0
- package/src/player.ts +26 -29
- package/src/plugins/flow-exp-plugin.ts +2 -2
- package/src/string-resolver/index.ts +7 -2
- package/src/types.ts +1 -4
- package/src/validator/binding-map-splice.ts +59 -0
- package/src/validator/index.ts +1 -0
- package/src/validator/types.ts +11 -3
- package/src/validator/validation-middleware.ts +34 -3
- package/src/view/parser/index.ts +44 -2
- package/src/view/plugins/applicability.ts +1 -1
- package/src/view/plugins/string-resolver.ts +8 -4
- package/src/view/plugins/template-plugin.ts +1 -6
- package/src/view/resolver/index.ts +119 -54
- package/src/view/resolver/types.ts +48 -7
package/src/data/local-model.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import get from 'dlv';
|
|
2
|
-
import { setIn } from 'timm';
|
|
2
|
+
import { setIn, omit, removeAt } from 'timm';
|
|
3
3
|
import type { BindingInstance } from '../binding';
|
|
4
4
|
import type { BatchSetTransaction, DataModelImpl, Updates } from './model';
|
|
5
5
|
|
|
@@ -38,4 +38,28 @@ export class LocalModel implements DataModelImpl {
|
|
|
38
38
|
});
|
|
39
39
|
return effectiveOperations;
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
public delete(binding: BindingInstance) {
|
|
43
|
+
const parentBinding = binding.parent();
|
|
44
|
+
|
|
45
|
+
if (parentBinding) {
|
|
46
|
+
const parentValue = this.get(parentBinding);
|
|
47
|
+
|
|
48
|
+
if (parentValue !== undefined) {
|
|
49
|
+
if (Array.isArray(parentValue)) {
|
|
50
|
+
this.model = setIn(
|
|
51
|
+
this.model,
|
|
52
|
+
parentBinding.asArray(),
|
|
53
|
+
removeAt(parentValue, binding.key() as number)
|
|
54
|
+
) as any;
|
|
55
|
+
} else {
|
|
56
|
+
this.model = setIn(
|
|
57
|
+
this.model,
|
|
58
|
+
parentBinding.asArray(),
|
|
59
|
+
omit(parentValue, binding.key() as string)
|
|
60
|
+
) as any;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
41
65
|
}
|
package/src/data/model.ts
CHANGED
|
@@ -56,11 +56,13 @@ export interface DataModelOptions {
|
|
|
56
56
|
export interface DataModelWithParser<Options = DataModelOptions> {
|
|
57
57
|
get(binding: BindingLike, options?: Options): any;
|
|
58
58
|
set(transaction: [BindingLike, any][], options?: Options): Updates;
|
|
59
|
+
delete(binding: BindingLike, options?: Options): void;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export interface DataModelImpl<Options = DataModelOptions> {
|
|
62
63
|
get(binding: BindingInstance, options?: Options): any;
|
|
63
64
|
set(transaction: BatchSetTransaction, options?: Options): Updates;
|
|
65
|
+
delete(binding: BindingInstance, options?: Options): void;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
export interface DataModelMiddleware {
|
|
@@ -72,11 +74,19 @@ export interface DataModelMiddleware {
|
|
|
72
74
|
options?: DataModelOptions,
|
|
73
75
|
next?: DataModelImpl
|
|
74
76
|
): Updates;
|
|
77
|
+
|
|
75
78
|
get(
|
|
76
79
|
binding: BindingInstance,
|
|
77
80
|
options?: DataModelOptions,
|
|
78
81
|
next?: DataModelImpl
|
|
79
82
|
): any;
|
|
83
|
+
|
|
84
|
+
delete?(
|
|
85
|
+
binding: BindingInstance,
|
|
86
|
+
options?: DataModelOptions,
|
|
87
|
+
next?: DataModelImpl
|
|
88
|
+
): void;
|
|
89
|
+
|
|
80
90
|
reset?(): void;
|
|
81
91
|
}
|
|
82
92
|
|
|
@@ -86,12 +96,16 @@ export function withParser<Options = unknown>(
|
|
|
86
96
|
parseBinding: BindingFactory
|
|
87
97
|
): DataModelWithParser<Options> {
|
|
88
98
|
/** Parse something into a binding if it requires it */
|
|
89
|
-
function maybeParse(
|
|
99
|
+
function maybeParse(
|
|
100
|
+
binding: BindingLike,
|
|
101
|
+
readOnly: boolean
|
|
102
|
+
): BindingInstance {
|
|
90
103
|
const parsed = isBinding(binding)
|
|
91
104
|
? binding
|
|
92
105
|
: parseBinding(binding, {
|
|
93
106
|
get: model.get,
|
|
94
107
|
set: model.set,
|
|
108
|
+
readOnly,
|
|
95
109
|
});
|
|
96
110
|
|
|
97
111
|
if (!parsed) {
|
|
@@ -103,14 +117,17 @@ export function withParser<Options = unknown>(
|
|
|
103
117
|
|
|
104
118
|
return {
|
|
105
119
|
get(binding, options?: Options) {
|
|
106
|
-
return model.get(maybeParse(binding), options);
|
|
120
|
+
return model.get(maybeParse(binding, true), options);
|
|
107
121
|
},
|
|
108
122
|
set(transaction, options?: Options) {
|
|
109
123
|
return model.set(
|
|
110
|
-
transaction.map(([key, val]) => [maybeParse(key), val]),
|
|
124
|
+
transaction.map(([key, val]) => [maybeParse(key, false), val]),
|
|
111
125
|
options
|
|
112
126
|
);
|
|
113
127
|
},
|
|
128
|
+
delete(binding, options?: Options) {
|
|
129
|
+
return model.delete(maybeParse(binding, false), options);
|
|
130
|
+
},
|
|
114
131
|
};
|
|
115
132
|
}
|
|
116
133
|
|
|
@@ -125,10 +142,33 @@ export function toModel(
|
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
return {
|
|
128
|
-
get: (binding: BindingInstance, options?: DataModelOptions) =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
middleware.
|
|
145
|
+
get: (binding: BindingInstance, options?: DataModelOptions) => {
|
|
146
|
+
const resolvedOptions = options ?? defaultOptions;
|
|
147
|
+
|
|
148
|
+
if (middleware.get) {
|
|
149
|
+
return middleware.get(binding, resolvedOptions, next);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return next?.get(binding, resolvedOptions);
|
|
153
|
+
},
|
|
154
|
+
set: (transaction: BatchSetTransaction, options?: DataModelOptions) => {
|
|
155
|
+
const resolvedOptions = options ?? defaultOptions;
|
|
156
|
+
|
|
157
|
+
if (middleware.set) {
|
|
158
|
+
return middleware.set(transaction, resolvedOptions, next);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return next?.set(transaction, resolvedOptions);
|
|
162
|
+
},
|
|
163
|
+
delete: (binding: BindingInstance, options?: DataModelOptions) => {
|
|
164
|
+
const resolvedOptions = options ?? defaultOptions;
|
|
165
|
+
|
|
166
|
+
if (middleware.delete) {
|
|
167
|
+
return middleware.delete(binding, resolvedOptions, next);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return next?.delete(binding, resolvedOptions);
|
|
171
|
+
},
|
|
132
172
|
};
|
|
133
173
|
}
|
|
134
174
|
|
|
@@ -145,7 +185,7 @@ export function constructModelForPipeline(
|
|
|
145
185
|
}
|
|
146
186
|
|
|
147
187
|
if (pipeline.length === 1) {
|
|
148
|
-
return pipeline[0];
|
|
188
|
+
return toModel(pipeline[0]);
|
|
149
189
|
}
|
|
150
190
|
|
|
151
191
|
/** Default and propagate the options into the nested calls */
|
|
@@ -166,6 +206,9 @@ export function constructModelForPipeline(
|
|
|
166
206
|
set: (transaction, options) => {
|
|
167
207
|
return createModelWithOptions(options)?.set(transaction, options);
|
|
168
208
|
},
|
|
209
|
+
delete: (binding, options) => {
|
|
210
|
+
return createModelWithOptions(options)?.delete(binding, options);
|
|
211
|
+
},
|
|
169
212
|
};
|
|
170
213
|
}
|
|
171
214
|
|
|
@@ -218,4 +261,8 @@ export class PipelinedDataModel implements DataModelImpl {
|
|
|
218
261
|
public get(binding: BindingInstance, options?: DataModelOptions): any {
|
|
219
262
|
return this.effectiveDataModel.get(binding, options);
|
|
220
263
|
}
|
|
264
|
+
|
|
265
|
+
public delete(binding: BindingInstance, options?: DataModelOptions): void {
|
|
266
|
+
return this.effectiveDataModel.delete(binding, options);
|
|
267
|
+
}
|
|
221
268
|
}
|
package/src/data/noop-model.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { Binding } from '@player-ui/types';
|
|
2
2
|
|
|
3
3
|
import type { BindingLike } from '../binding';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ExpressionHandler,
|
|
6
|
+
ExpressionContext,
|
|
7
|
+
ExpressionNode,
|
|
8
|
+
} from './types';
|
|
5
9
|
|
|
6
10
|
/** Sets a value to the data-model */
|
|
7
11
|
export const setDataVal: ExpressionHandler<[Binding, any], any> = (
|
|
@@ -25,5 +29,23 @@ export const deleteDataVal: ExpressionHandler<[Binding], void> = (
|
|
|
25
29
|
_context: ExpressionContext,
|
|
26
30
|
binding
|
|
27
31
|
) => {
|
|
28
|
-
return _context.model.
|
|
32
|
+
return _context.model.delete(binding);
|
|
29
33
|
};
|
|
34
|
+
|
|
35
|
+
/** Conditional expression handler */
|
|
36
|
+
export const conditional: ExpressionHandler<
|
|
37
|
+
[ExpressionNode, ExpressionNode, ExpressionNode?]
|
|
38
|
+
> = (ctx, condition, ifTrue, ifFalse) => {
|
|
39
|
+
const resolution = ctx.evaluate(condition);
|
|
40
|
+
if (resolution) {
|
|
41
|
+
return ctx.evaluate(ifTrue);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (ifFalse) {
|
|
45
|
+
return ctx.evaluate(ifFalse);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
conditional.resolveParams = false;
|
|
@@ -2,6 +2,7 @@ import { SyncWaterfallHook, SyncBailHook } from 'tapable-ts';
|
|
|
2
2
|
import { parseExpression } from './parser';
|
|
3
3
|
import * as DEFAULT_EXPRESSION_HANDLERS from './evaluator-functions';
|
|
4
4
|
import { isExpressionNode } from './types';
|
|
5
|
+
import { isObjectExpression } from './utils';
|
|
5
6
|
import type {
|
|
6
7
|
ExpressionNode,
|
|
7
8
|
BinaryOperator,
|
|
@@ -71,6 +72,11 @@ const DEFAULT_UNARY_OPERATORS: Record<string, UnaryOperator> = {
|
|
|
71
72
|
export interface HookOptions extends ExpressionContext {
|
|
72
73
|
/** Given an expression node */
|
|
73
74
|
resolveNode: (node: ExpressionNode) => any;
|
|
75
|
+
|
|
76
|
+
/** Enabling this flag skips calling the onError hook, and just throws errors back to the caller.
|
|
77
|
+
* The caller is responsible for handling the error.
|
|
78
|
+
*/
|
|
79
|
+
throwErrors?: boolean;
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
export type ExpressionEvaluatorOptions = Omit<
|
|
@@ -92,6 +98,12 @@ export class ExpressionEvaluator {
|
|
|
92
98
|
/** Resolve an AST node for an expression to a value */
|
|
93
99
|
resolve: new SyncWaterfallHook<[any, ExpressionNode, HookOptions]>(),
|
|
94
100
|
|
|
101
|
+
/** Gets the options that will be passed in calls to the resolve hook */
|
|
102
|
+
resolveOptions: new SyncWaterfallHook<[HookOptions]>(),
|
|
103
|
+
|
|
104
|
+
/** Allows users to change the expression to be evaluated before processing */
|
|
105
|
+
beforeEvaluate: new SyncWaterfallHook<[ExpressionType, HookOptions]>(),
|
|
106
|
+
|
|
95
107
|
/**
|
|
96
108
|
* An optional means of handling an error in the expression execution
|
|
97
109
|
* Return true if handled, to stop propagation of the error
|
|
@@ -128,14 +140,22 @@ export class ExpressionEvaluator {
|
|
|
128
140
|
}
|
|
129
141
|
|
|
130
142
|
public evaluate(
|
|
131
|
-
|
|
143
|
+
expr: ExpressionType,
|
|
132
144
|
options?: ExpressionEvaluatorOptions
|
|
133
145
|
): any {
|
|
134
|
-
const
|
|
146
|
+
const resolvedOpts = this.hooks.resolveOptions.call({
|
|
135
147
|
...this.defaultHookOptions,
|
|
136
148
|
...options,
|
|
137
|
-
resolveNode: (node: ExpressionNode) => this._execAST(node,
|
|
138
|
-
};
|
|
149
|
+
resolveNode: (node: ExpressionNode) => this._execAST(node, resolvedOpts),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
let expression = this.hooks.beforeEvaluate.call(expr, resolvedOpts) ?? expr;
|
|
153
|
+
|
|
154
|
+
// Unwrap any returned expression type
|
|
155
|
+
// Since this could also be an object type, we need to recurse through it until we find the end
|
|
156
|
+
while (isObjectExpression(expression)) {
|
|
157
|
+
expression = expression.value;
|
|
158
|
+
}
|
|
139
159
|
|
|
140
160
|
// Check for literals
|
|
141
161
|
if (
|
|
@@ -149,21 +169,17 @@ export class ExpressionEvaluator {
|
|
|
149
169
|
|
|
150
170
|
// Skip doing anything with objects that are _actually_ just parsed expression nodes
|
|
151
171
|
if (isExpressionNode(expression)) {
|
|
152
|
-
return this._execAST(expression,
|
|
172
|
+
return this._execAST(expression, resolvedOpts);
|
|
153
173
|
}
|
|
154
174
|
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
? expression
|
|
158
|
-
: Object.values(expression);
|
|
159
|
-
|
|
160
|
-
return values.reduce(
|
|
175
|
+
if (Array.isArray(expression)) {
|
|
176
|
+
return expression.reduce(
|
|
161
177
|
(_nothing, exp) => this.evaluate(exp, options),
|
|
162
178
|
null
|
|
163
179
|
);
|
|
164
180
|
}
|
|
165
181
|
|
|
166
|
-
return this._execString(String(expression),
|
|
182
|
+
return this._execString(String(expression), resolvedOpts);
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
public addExpressionFunction<T extends readonly unknown[], R>(
|
|
@@ -217,8 +233,8 @@ export class ExpressionEvaluator {
|
|
|
217
233
|
|
|
218
234
|
return this._execAST(expAST, options);
|
|
219
235
|
} catch (e: any) {
|
|
220
|
-
if (!this.hooks.onError.call(e)) {
|
|
221
|
-
// Only throw the error if it's not handled by the hook
|
|
236
|
+
if (options.throwErrors || !this.hooks.onError.call(e)) {
|
|
237
|
+
// Only throw the error if it's not handled by the hook, or throwErrors is true
|
|
222
238
|
throw e;
|
|
223
239
|
}
|
|
224
240
|
}
|
|
@@ -305,35 +321,23 @@ export class ExpressionEvaluator {
|
|
|
305
321
|
if (node.type === 'CallExpression') {
|
|
306
322
|
const expressionName = node.callTarget.name;
|
|
307
323
|
|
|
308
|
-
// Treat the conditional operator as special.
|
|
309
|
-
// Don't exec the arguments that don't apply
|
|
310
|
-
if (expressionName === 'conditional') {
|
|
311
|
-
const condition = resolveNode(node.args[0]);
|
|
312
|
-
|
|
313
|
-
if (condition) {
|
|
314
|
-
return resolveNode(node.args[1]);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (node.args[2]) {
|
|
318
|
-
return resolveNode(node.args[2]);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
324
|
const operator = this.operators.expressions.get(expressionName);
|
|
325
325
|
|
|
326
326
|
if (!operator) {
|
|
327
327
|
throw new Error(`Unknown expression function: ${expressionName}`);
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
if ('resolveParams' in operator && operator.resolveParams === false) {
|
|
331
|
+
return operator(expressionContext, ...node.args);
|
|
332
|
+
}
|
|
333
|
+
|
|
330
334
|
const args = node.args.map((n) => resolveNode(n));
|
|
331
335
|
|
|
332
336
|
return operator(expressionContext, ...args);
|
|
333
337
|
}
|
|
334
338
|
|
|
335
339
|
if (node.type === 'ModelRef') {
|
|
336
|
-
return model.get(node.ref);
|
|
340
|
+
return model.get(node.ref, { context: { model: options.model } });
|
|
337
341
|
}
|
|
338
342
|
|
|
339
343
|
if (node.type === 'MemberExpression') {
|
package/src/expressions/types.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import type { DataModelWithParser } from '../data';
|
|
2
2
|
import type { Logger } from '../logger';
|
|
3
3
|
|
|
4
|
+
export type ExpressionObjectType = {
|
|
5
|
+
/** The expression to eval */
|
|
6
|
+
value: BasicExpressionTypes;
|
|
7
|
+
};
|
|
8
|
+
|
|
4
9
|
export type ExpressionLiteralType =
|
|
5
10
|
| string
|
|
6
11
|
| number
|
|
7
12
|
| boolean
|
|
8
13
|
| undefined
|
|
9
14
|
| null;
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
|
|
16
|
+
export type BasicExpressionTypes =
|
|
12
17
|
| ExpressionLiteralType
|
|
13
|
-
|
|
|
14
|
-
|
|
|
18
|
+
| ExpressionObjectType
|
|
19
|
+
| Array<ExpressionLiteralType | ExpressionObjectType>;
|
|
20
|
+
|
|
21
|
+
export type ExpressionType = BasicExpressionTypes | ExpressionNode;
|
|
15
22
|
|
|
16
23
|
export interface OperatorProcessingOptions {
|
|
17
24
|
/**
|
|
@@ -53,7 +60,12 @@ export const ExpNodeOpaqueIdentifier = Symbol('Expression Node ID');
|
|
|
53
60
|
|
|
54
61
|
/** Checks if the input is an already processed Expression node */
|
|
55
62
|
export function isExpressionNode(x: any): x is ExpressionNode {
|
|
56
|
-
return
|
|
63
|
+
return (
|
|
64
|
+
typeof x === 'object' &&
|
|
65
|
+
x !== null &&
|
|
66
|
+
!Array.isArray(x) &&
|
|
67
|
+
x.__id === ExpNodeOpaqueIdentifier
|
|
68
|
+
);
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
export interface NodePosition {
|
package/src/expressions/utils.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { isExpressionNode } from './types';
|
|
1
2
|
import type {
|
|
2
3
|
ExpressionHandler,
|
|
3
4
|
ExpressionNode,
|
|
5
|
+
ExpressionObjectType,
|
|
6
|
+
ExpressionType,
|
|
4
7
|
NodeLocation,
|
|
5
8
|
NodePosition,
|
|
6
9
|
} from './types';
|
|
@@ -129,3 +132,19 @@ export function findClosestNodeAtPosition(
|
|
|
129
132
|
return node;
|
|
130
133
|
}
|
|
131
134
|
}
|
|
135
|
+
|
|
136
|
+
/** Checks if the expression is a simple type */
|
|
137
|
+
export function isObjectExpression(
|
|
138
|
+
expr: ExpressionType
|
|
139
|
+
): expr is ExpressionObjectType {
|
|
140
|
+
if (isExpressionNode(expr)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
typeof expr === 'object' &&
|
|
146
|
+
expr !== null &&
|
|
147
|
+
!Array.isArray(expr) &&
|
|
148
|
+
'value' in expr
|
|
149
|
+
);
|
|
150
|
+
}
|
package/src/player.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { setIn } from 'timm';
|
|
2
2
|
import deferred from 'p-defer';
|
|
3
|
-
import queueMicrotask from 'queue-microtask';
|
|
4
3
|
import type { Flow as FlowType, FlowResult } from '@player-ui/types';
|
|
5
4
|
|
|
6
5
|
import { SyncHook, SyncWaterfallHook } from 'tapable-ts';
|
|
7
6
|
import type { Logger } from './logger';
|
|
8
7
|
import { TapableLogger } from './logger';
|
|
9
|
-
import type {
|
|
8
|
+
import type { ExpressionType } from './expressions';
|
|
10
9
|
import { ExpressionEvaluator } from './expressions';
|
|
11
10
|
import { SchemaController } from './schema';
|
|
12
11
|
import { BindingParser } from './binding';
|
|
@@ -31,8 +30,8 @@ import type {
|
|
|
31
30
|
import { NOT_STARTED_STATE } from './types';
|
|
32
31
|
|
|
33
32
|
// Variables injected at build time
|
|
34
|
-
const PLAYER_VERSION = '0.4.0-next.
|
|
35
|
-
const COMMIT = '
|
|
33
|
+
const PLAYER_VERSION = '0.4.0-next.9';
|
|
34
|
+
const COMMIT = 'e7681a2757fe0ab15d0c0e27c11d0ed33334d63f';
|
|
36
35
|
|
|
37
36
|
export interface PlayerPlugin {
|
|
38
37
|
/**
|
|
@@ -289,10 +288,11 @@ export class Player {
|
|
|
289
288
|
});
|
|
290
289
|
|
|
291
290
|
/** Resolve any data references in a string */
|
|
292
|
-
function resolveStrings<T>(val: T) {
|
|
291
|
+
function resolveStrings<T>(val: T, formatted?: boolean) {
|
|
293
292
|
return resolveDataRefs(val, {
|
|
294
293
|
model: dataController,
|
|
295
294
|
evaluate: expressionEvaluator.evaluate,
|
|
295
|
+
formatted,
|
|
296
296
|
});
|
|
297
297
|
}
|
|
298
298
|
|
|
@@ -306,7 +306,7 @@ export class Player {
|
|
|
306
306
|
if (typeof state.onEnd === 'object' && 'exp' in state.onEnd) {
|
|
307
307
|
expressionEvaluator?.evaluate(state.onEnd.exp);
|
|
308
308
|
} else {
|
|
309
|
-
expressionEvaluator?.evaluate(state.onEnd);
|
|
309
|
+
expressionEvaluator?.evaluate(state.onEnd as ExpressionType);
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
312
|
|
|
@@ -353,7 +353,7 @@ export class Player {
|
|
|
353
353
|
newState = setIn(
|
|
354
354
|
state,
|
|
355
355
|
['param'],
|
|
356
|
-
resolveStrings(state.param)
|
|
356
|
+
resolveStrings(state.param, false)
|
|
357
357
|
) as any;
|
|
358
358
|
}
|
|
359
359
|
|
|
@@ -361,26 +361,18 @@ export class Player {
|
|
|
361
361
|
});
|
|
362
362
|
|
|
363
363
|
flow.hooks.transition.tap('player', (_oldState, newState) => {
|
|
364
|
-
if (newState.value.state_type
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
377
|
-
} catch (error) {
|
|
378
|
-
const state = this.getState();
|
|
379
|
-
if (error instanceof Error && state.status === 'in-progress') {
|
|
380
|
-
state.fail(error);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
});
|
|
364
|
+
if (newState.value.state_type !== 'VIEW') {
|
|
365
|
+
validationController.reset();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
flow.hooks.afterTransition.tap('player', (flowInstance) => {
|
|
370
|
+
const value = flowInstance.currentState?.value;
|
|
371
|
+
if (value && value.state_type === 'ACTION') {
|
|
372
|
+
const { exp } = value;
|
|
373
|
+
flowController?.transition(
|
|
374
|
+
String(expressionEvaluator?.evaluate(exp))
|
|
375
|
+
);
|
|
384
376
|
}
|
|
385
377
|
|
|
386
378
|
expressionEvaluator.reset();
|
|
@@ -402,6 +394,11 @@ export class Player {
|
|
|
402
394
|
parseBinding,
|
|
403
395
|
transition: flowController.transition,
|
|
404
396
|
model: dataController,
|
|
397
|
+
utils: {
|
|
398
|
+
findPlugin: <Plugin = unknown>(pluginSymbol: symbol) => {
|
|
399
|
+
return this.findPlugin(pluginSymbol) as unknown as Plugin;
|
|
400
|
+
},
|
|
401
|
+
},
|
|
405
402
|
logger: this.logger,
|
|
406
403
|
flowController,
|
|
407
404
|
schema,
|
|
@@ -419,6 +416,7 @@ export class Player {
|
|
|
419
416
|
...validationController.forView(parseBinding),
|
|
420
417
|
type: (b) => schema.getType(parseBinding(b)),
|
|
421
418
|
},
|
|
419
|
+
constants: this.constantsController,
|
|
422
420
|
});
|
|
423
421
|
viewController.hooks.view.tap('player', (view) => {
|
|
424
422
|
validationController.onView(view);
|
|
@@ -432,7 +430,7 @@ export class Player {
|
|
|
432
430
|
.start()
|
|
433
431
|
.then((endState) => {
|
|
434
432
|
const flowResult: FlowResult = {
|
|
435
|
-
endState: resolveStrings(endState),
|
|
433
|
+
endState: resolveStrings(endState, false),
|
|
436
434
|
data: dataController.serialize(),
|
|
437
435
|
};
|
|
438
436
|
|
|
@@ -503,7 +501,6 @@ export class Player {
|
|
|
503
501
|
ref,
|
|
504
502
|
status: 'completed',
|
|
505
503
|
flow: state.flow,
|
|
506
|
-
dataModel: state.controllers.data.getModel(),
|
|
507
504
|
} as const;
|
|
508
505
|
|
|
509
506
|
return maybeUpdateState({
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ExpressionObject,
|
|
4
4
|
NavigationFlowState,
|
|
5
5
|
} from '@player-ui/types';
|
|
6
|
-
import type { ExpressionEvaluator } from '../expressions';
|
|
6
|
+
import type { ExpressionEvaluator, ExpressionType } from '../expressions';
|
|
7
7
|
import type { FlowInstance } from '../controllers';
|
|
8
8
|
import type { Player, PlayerPlugin } from '../player';
|
|
9
9
|
|
|
@@ -28,7 +28,7 @@ export class FlowExpPlugin implements PlayerPlugin {
|
|
|
28
28
|
if (typeof exp === 'object' && 'exp' in exp) {
|
|
29
29
|
expressionEvaluator?.evaluate(exp.exp);
|
|
30
30
|
} else {
|
|
31
|
-
expressionEvaluator?.evaluate(exp);
|
|
31
|
+
expressionEvaluator?.evaluate(exp as ExpressionType);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
};
|
|
@@ -17,6 +17,11 @@ export interface Options {
|
|
|
17
17
|
* Passing `false` will skip trying to evaluate any expressions (@[ foo() ]@)
|
|
18
18
|
*/
|
|
19
19
|
evaluate: false | ((exp: Expression) => any);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Optionaly resolve binding without formatting in case Type format applies
|
|
23
|
+
*/
|
|
24
|
+
formatted?: boolean;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
/** Search the given string for the coordinates of the next expression to resolve */
|
|
@@ -116,7 +121,7 @@ export function resolveExpressionsInString(
|
|
|
116
121
|
|
|
117
122
|
/** Return a string with all data model references resolved */
|
|
118
123
|
export function resolveDataRefsInString(val: string, options: Options): string {
|
|
119
|
-
const { model } = options;
|
|
124
|
+
const { model, formatted = true } = options;
|
|
120
125
|
let workingString = resolveExpressionsInString(val, options);
|
|
121
126
|
|
|
122
127
|
if (
|
|
@@ -144,7 +149,7 @@ export function resolveDataRefsInString(val: string, options: Options): string {
|
|
|
144
149
|
)
|
|
145
150
|
.trim();
|
|
146
151
|
|
|
147
|
-
const evaledVal = model.get(binding, { formatted
|
|
152
|
+
const evaledVal = model.get(binding, { formatted });
|
|
148
153
|
|
|
149
154
|
// Exit early if the string is _just_ a model lookup
|
|
150
155
|
// If the result is a string, we may need further processing for nested bindings
|
package/src/types.ts
CHANGED
|
@@ -85,10 +85,7 @@ export type InProgressState = BaseFlowState<'in-progress'> &
|
|
|
85
85
|
/** The flow completed properly */
|
|
86
86
|
export type CompletedState = BaseFlowState<'completed'> &
|
|
87
87
|
PlayerFlowExecutionData &
|
|
88
|
-
FlowResult
|
|
89
|
-
/** The top-level data-model for the flow */
|
|
90
|
-
dataModel: DataModelWithParser;
|
|
91
|
-
};
|
|
88
|
+
FlowResult;
|
|
92
89
|
|
|
93
90
|
/** The flow finished but not successfully */
|
|
94
91
|
export type ErrorState = BaseFlowState<'error'> & {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { BindingInstance } from '../binding';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remove a binding, and any children from from the map
|
|
5
|
+
* If the binding is an array-item, then it will be spliced from the array and the others will be shifted down
|
|
6
|
+
*
|
|
7
|
+
* @param sourceMap - A map of bindings to values
|
|
8
|
+
* @param binding - The binding to remove from the map
|
|
9
|
+
*/
|
|
10
|
+
export function removeBindingAndChildrenFromMap<T>(
|
|
11
|
+
sourceMap: Map<BindingInstance, T>,
|
|
12
|
+
binding: BindingInstance
|
|
13
|
+
): Map<BindingInstance, T> {
|
|
14
|
+
const targetMap = new Map(sourceMap);
|
|
15
|
+
|
|
16
|
+
const parentBinding = binding.parent();
|
|
17
|
+
const property = binding.key();
|
|
18
|
+
|
|
19
|
+
// Clear out any that are sub-bindings of this binding
|
|
20
|
+
|
|
21
|
+
targetMap.forEach((_value, trackedBinding) => {
|
|
22
|
+
if (binding === trackedBinding || binding.contains(trackedBinding)) {
|
|
23
|
+
targetMap.delete(trackedBinding);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (typeof property === 'number') {
|
|
28
|
+
// Splice out this index from the rest
|
|
29
|
+
|
|
30
|
+
// Order matters here b/c we are shifting items in the array
|
|
31
|
+
// Start with the smallest index and work our way down
|
|
32
|
+
const bindingsToRewrite = Array.from(sourceMap.keys())
|
|
33
|
+
.filter((b) => {
|
|
34
|
+
if (parentBinding.contains(b)) {
|
|
35
|
+
const [childIndex] = b.relative(parentBinding);
|
|
36
|
+
return typeof childIndex === 'number' && childIndex > property;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return false;
|
|
40
|
+
})
|
|
41
|
+
.sort();
|
|
42
|
+
|
|
43
|
+
bindingsToRewrite.forEach((trackedBinding) => {
|
|
44
|
+
// If the tracked binding is a sub-binding of the parent binding, then we need to
|
|
45
|
+
// update the path to reflect the new index
|
|
46
|
+
|
|
47
|
+
const [childIndex, ...childPath] = trackedBinding.relative(parentBinding);
|
|
48
|
+
|
|
49
|
+
if (typeof childIndex === 'number') {
|
|
50
|
+
const newSegments = [childIndex - 1, ...childPath];
|
|
51
|
+
const newChildBinding = parentBinding.descendent(newSegments);
|
|
52
|
+
targetMap.set(newChildBinding, targetMap.get(trackedBinding) as T);
|
|
53
|
+
targetMap.delete(trackedBinding);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return targetMap;
|
|
59
|
+
}
|
package/src/validator/index.ts
CHANGED