@player-ui/player 0.4.0-next.11 → 0.4.0-next.13
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 +39 -27
- package/dist/index.d.ts +4 -3
- package/dist/index.esm.js +36 -24
- package/dist/player.dev.js +142 -1049
- package/dist/player.prod.js +1 -1
- package/package.json +4 -3
- package/src/binding/index.ts +13 -3
- package/src/binding/resolver.ts +1 -1
- package/src/binding-grammar/custom/index.ts +17 -9
- package/src/controllers/validation/controller.ts +97 -63
- package/src/player.ts +2 -2
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@player-ui/player",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.13",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org"
|
|
7
7
|
},
|
|
8
8
|
"peerDependencies": {},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@player-ui/partial-match-registry": "0.4.0-next.
|
|
11
|
-
"@player-ui/types": "0.4.0-next.
|
|
10
|
+
"@player-ui/partial-match-registry": "0.4.0-next.13",
|
|
11
|
+
"@player-ui/types": "0.4.0-next.13",
|
|
12
12
|
"dequal": "^2.0.2",
|
|
13
13
|
"p-defer": "^3.0.0",
|
|
14
14
|
"queue-microtask": "^1.2.3",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"ebnf": "^1.9.0",
|
|
22
22
|
"timm": "^1.6.2",
|
|
23
23
|
"error-polyfill": "^0.1.3",
|
|
24
|
+
"ts-nested-error": "^1.2.1",
|
|
24
25
|
"@babel/runtime": "7.15.4"
|
|
25
26
|
},
|
|
26
27
|
"main": "dist/index.cjs.js",
|
package/src/binding/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SyncBailHook, SyncWaterfallHook } from 'tapable-ts';
|
|
2
|
-
import NestedError from 'nested-error
|
|
2
|
+
import { NestedError } from 'ts-nested-error';
|
|
3
3
|
import type { ParserResult, AnyNode } from '../binding-grammar';
|
|
4
4
|
import {
|
|
5
5
|
// We can swap this with whichever parser we want to use
|
|
@@ -15,6 +15,8 @@ export * from './utils';
|
|
|
15
15
|
export * from './binding';
|
|
16
16
|
|
|
17
17
|
export const SIMPLE_BINDING_REGEX = /^[\w\-@]+(\.[\w\-@]+)*$/;
|
|
18
|
+
export const BINDING_BRACKETS_REGEX = /[\s()*=`{}'"[\]]/;
|
|
19
|
+
const LAZY_BINDING_REGEX = /^[^.]+(\..+)*$/;
|
|
18
20
|
|
|
19
21
|
const DEFAULT_OPTIONS: BindingParserOptions = {
|
|
20
22
|
get: () => {
|
|
@@ -28,6 +30,9 @@ const DEFAULT_OPTIONS: BindingParserOptions = {
|
|
|
28
30
|
},
|
|
29
31
|
};
|
|
30
32
|
|
|
33
|
+
type BeforeResolveNodeContext = Required<NormalizedResult> &
|
|
34
|
+
ResolveBindingASTOptions;
|
|
35
|
+
|
|
31
36
|
/** A parser for creating bindings from a string */
|
|
32
37
|
export class BindingParser {
|
|
33
38
|
private cache: Record<string, BindingInstance>;
|
|
@@ -37,7 +42,7 @@ export class BindingParser {
|
|
|
37
42
|
public hooks = {
|
|
38
43
|
skipOptimization: new SyncBailHook<[string], boolean>(),
|
|
39
44
|
beforeResolveNode: new SyncWaterfallHook<
|
|
40
|
-
[AnyNode,
|
|
45
|
+
[AnyNode, BeforeResolveNodeContext]
|
|
41
46
|
>(),
|
|
42
47
|
};
|
|
43
48
|
|
|
@@ -56,8 +61,13 @@ export class BindingParser {
|
|
|
56
61
|
path: string,
|
|
57
62
|
resolveOptions: ResolveBindingASTOptions
|
|
58
63
|
) {
|
|
64
|
+
/**
|
|
65
|
+
* Ensure no binding characters exist in path and the characters remaining
|
|
66
|
+
* look like a binding format.
|
|
67
|
+
*/
|
|
59
68
|
if (
|
|
60
|
-
|
|
69
|
+
!BINDING_BRACKETS_REGEX.test(path) &&
|
|
70
|
+
LAZY_BINDING_REGEX.test(path) &&
|
|
61
71
|
this.hooks.skipOptimization.call(path) !== true
|
|
62
72
|
) {
|
|
63
73
|
return { path: path.split('.'), updates: undefined } as NormalizedResult;
|
package/src/binding/resolver.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import NestedError from 'nested-error
|
|
1
|
+
import { NestedError } from 'ts-nested-error';
|
|
2
2
|
import type { SyncWaterfallHook } from 'tapable-ts';
|
|
3
3
|
import type { PathNode, AnyNode } from '../binding-grammar';
|
|
4
4
|
import { findInArray, maybeConvertToNum } from './utils';
|
|
@@ -26,7 +26,7 @@ const DOUBLE_QUOTE = '"';
|
|
|
26
26
|
const BACK_TICK = '`';
|
|
27
27
|
// const IDENTIFIER_REGEX = /[\w\-@]+/;
|
|
28
28
|
|
|
29
|
-
/** A _faster_ way to match chars instead of a regex
|
|
29
|
+
/** A _faster_ way to match chars instead of a regex. */
|
|
30
30
|
const isIdentifierChar = (char?: string): boolean => {
|
|
31
31
|
if (!char) {
|
|
32
32
|
return false;
|
|
@@ -34,14 +34,22 @@ const isIdentifierChar = (char?: string): boolean => {
|
|
|
34
34
|
|
|
35
35
|
const charCode = char.charCodeAt(0);
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
charCode ===
|
|
42
|
-
charCode ===
|
|
43
|
-
charCode ===
|
|
44
|
-
|
|
37
|
+
const matches =
|
|
38
|
+
charCode === 32 || // ' '
|
|
39
|
+
charCode === 34 || // "
|
|
40
|
+
charCode === 39 || // '
|
|
41
|
+
charCode === 40 || // (
|
|
42
|
+
charCode === 41 || // )
|
|
43
|
+
charCode === 42 || // *
|
|
44
|
+
charCode === 46 || // .
|
|
45
|
+
charCode === 61 || // =
|
|
46
|
+
charCode === 91 || // [
|
|
47
|
+
charCode === 93 || // ]
|
|
48
|
+
charCode === 96 || // `
|
|
49
|
+
charCode === 123 || // {
|
|
50
|
+
charCode === 125; // }
|
|
51
|
+
|
|
52
|
+
return !matches;
|
|
45
53
|
};
|
|
46
54
|
|
|
47
55
|
/** Parse out a binding AST from a path */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Validation } from '@player-ui/types';
|
|
2
2
|
import { SyncHook, SyncWaterfallHook } from 'tapable-ts';
|
|
3
|
+
import { setIn } from 'timm';
|
|
3
4
|
|
|
4
5
|
import type { BindingInstance, BindingFactory } from '../../binding';
|
|
5
6
|
import { isBinding } from '../../binding';
|
|
@@ -104,6 +105,13 @@ type StatefulError = {
|
|
|
104
105
|
|
|
105
106
|
export type StatefulValidationObject = StatefulWarning | StatefulError;
|
|
106
107
|
|
|
108
|
+
/** Helper function to determin if the subset is within the containingSet */
|
|
109
|
+
function isSubset<T>(subset: Set<T>, containingSet: Set<T>): boolean {
|
|
110
|
+
if (subset.size > containingSet.size) return false;
|
|
111
|
+
for (const entry of subset) if (!containingSet.has(entry)) return false;
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
/** Helper for initializing a validation object that tracks state */
|
|
108
116
|
function createStatefulValidationObject(
|
|
109
117
|
obj: ValidationObjectWithSource
|
|
@@ -207,67 +215,82 @@ class ValidatedBinding {
|
|
|
207
215
|
canDismiss: boolean
|
|
208
216
|
) {
|
|
209
217
|
// If the currentState is not load, skip those
|
|
210
|
-
this.applicableValidations = this.applicableValidations.map(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
218
|
+
this.applicableValidations = this.applicableValidations.map(
|
|
219
|
+
(originalValue) => {
|
|
220
|
+
if (originalValue.state === 'dismissed') {
|
|
221
|
+
// Don't rerun any dismissed warnings
|
|
222
|
+
return originalValue;
|
|
223
|
+
}
|
|
215
224
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
225
|
+
// treat all warnings the same and block it once (unless blocking is true)
|
|
226
|
+
const blocking =
|
|
227
|
+
originalValue.value.blocking ??
|
|
228
|
+
((originalValue.value.severity === 'warning' && 'once') || true);
|
|
229
|
+
|
|
230
|
+
const obj = setIn(
|
|
231
|
+
originalValue,
|
|
232
|
+
['value', 'blocking'],
|
|
233
|
+
blocking
|
|
234
|
+
) as StatefulValidationObject;
|
|
235
|
+
|
|
236
|
+
const isBlockingNavigation =
|
|
237
|
+
blocking === true || (blocking === 'once' && !canDismiss);
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
this.currentPhase === 'navigation' &&
|
|
241
|
+
obj.state === 'active' &&
|
|
242
|
+
obj.value.blocking !== true
|
|
243
|
+
) {
|
|
244
|
+
if (obj.value.severity === 'warning') {
|
|
245
|
+
const warn = obj as ActiveWarning;
|
|
246
|
+
if (
|
|
247
|
+
warn.dismissable &&
|
|
248
|
+
warn.response.dismiss &&
|
|
249
|
+
(warn.response.blocking !== 'once' || !warn.response.blocking)
|
|
250
|
+
) {
|
|
251
|
+
warn.response.dismiss();
|
|
252
|
+
} else {
|
|
253
|
+
if (warn?.response.blocking === 'once') {
|
|
254
|
+
warn.response.blocking = false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
warn.dismissable = true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return warn as StatefulValidationObject;
|
|
236
261
|
}
|
|
262
|
+
}
|
|
237
263
|
|
|
238
|
-
|
|
264
|
+
const response = runner(obj.value);
|
|
265
|
+
|
|
266
|
+
const newState = {
|
|
267
|
+
type: obj.type,
|
|
268
|
+
value: obj.value,
|
|
269
|
+
state: response ? 'active' : 'none',
|
|
270
|
+
isBlockingNavigation,
|
|
271
|
+
dismissable:
|
|
272
|
+
obj.value.severity === 'warning' &&
|
|
273
|
+
this.currentPhase === 'navigation',
|
|
274
|
+
response: response
|
|
275
|
+
? {
|
|
276
|
+
...obj.value,
|
|
277
|
+
message: response.message ?? 'Something is broken',
|
|
278
|
+
severity: obj.value.severity,
|
|
279
|
+
displayTarget: obj.value.displayTarget ?? 'field',
|
|
280
|
+
}
|
|
281
|
+
: undefined,
|
|
282
|
+
} as StatefulValidationObject;
|
|
283
|
+
|
|
284
|
+
if (newState.state === 'active' && obj.value.severity === 'warning') {
|
|
285
|
+
(newState.response as WarningValidationResponse).dismiss = () => {
|
|
286
|
+
(newState as StatefulWarning).state = 'dismissed';
|
|
287
|
+
this.onDismiss?.();
|
|
288
|
+
};
|
|
239
289
|
}
|
|
240
|
-
}
|
|
241
290
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const newState = {
|
|
245
|
-
type: obj.type,
|
|
246
|
-
value: obj.value,
|
|
247
|
-
state: response ? 'active' : 'none',
|
|
248
|
-
isBlockingNavigation,
|
|
249
|
-
dismissable:
|
|
250
|
-
obj.value.severity === 'warning' &&
|
|
251
|
-
this.currentPhase === 'navigation',
|
|
252
|
-
response: response
|
|
253
|
-
? {
|
|
254
|
-
...obj.value,
|
|
255
|
-
message: response.message ?? 'Something is broken',
|
|
256
|
-
severity: obj.value.severity,
|
|
257
|
-
displayTarget: obj.value.displayTarget ?? 'field',
|
|
258
|
-
}
|
|
259
|
-
: undefined,
|
|
260
|
-
} as StatefulValidationObject;
|
|
261
|
-
|
|
262
|
-
if (newState.state === 'active' && obj.value.severity === 'warning') {
|
|
263
|
-
(newState.response as WarningValidationResponse).dismiss = () => {
|
|
264
|
-
(newState as StatefulWarning).state = 'dismissed';
|
|
265
|
-
this.onDismiss?.();
|
|
266
|
-
};
|
|
291
|
+
return newState;
|
|
267
292
|
}
|
|
268
|
-
|
|
269
|
-
return newState;
|
|
270
|
-
});
|
|
293
|
+
);
|
|
271
294
|
}
|
|
272
295
|
|
|
273
296
|
public update(
|
|
@@ -275,6 +298,8 @@ class ValidatedBinding {
|
|
|
275
298
|
canDismiss: boolean,
|
|
276
299
|
runner: ValidationRunner
|
|
277
300
|
) {
|
|
301
|
+
const newApplicableValidations: StatefulValidationObject[] = [];
|
|
302
|
+
|
|
278
303
|
if (phase === 'load' && this.currentPhase !== undefined) {
|
|
279
304
|
// Tried to run the 'load' phase twice. Aborting
|
|
280
305
|
return;
|
|
@@ -301,8 +326,23 @@ class ValidatedBinding {
|
|
|
301
326
|
(this.currentPhase === 'load' || this.currentPhase === 'change')
|
|
302
327
|
) {
|
|
303
328
|
// Can transition to a nav state from a change or load
|
|
329
|
+
|
|
330
|
+
// if there is an non-blocking error that is active then remove the error from applicable validations so it can no longer be shown
|
|
331
|
+
// which is needed if there are additional warnings to become active for that binding after the error is shown
|
|
332
|
+
this.applicableValidations.forEach((element) => {
|
|
333
|
+
if (
|
|
334
|
+
!(
|
|
335
|
+
element.type === 'error' &&
|
|
336
|
+
element.state === 'active' &&
|
|
337
|
+
element.isBlockingNavigation === false
|
|
338
|
+
)
|
|
339
|
+
) {
|
|
340
|
+
newApplicableValidations.push(element);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
304
344
|
this.applicableValidations = [
|
|
305
|
-
...
|
|
345
|
+
...newApplicableValidations,
|
|
306
346
|
...(this.currentPhase === 'load' ? this.validationsByState.change : []),
|
|
307
347
|
...this.validationsByState.navigation,
|
|
308
348
|
];
|
|
@@ -712,18 +752,12 @@ export class ValidationController implements BindingTracker {
|
|
|
712
752
|
if (isNavigationTrigger) {
|
|
713
753
|
// If validations didn't change since last update, dismiss all dismissible validations.
|
|
714
754
|
const { activeBindings } = this;
|
|
715
|
-
if (
|
|
755
|
+
if (isSubset(activeBindings, lastActiveBindings)) {
|
|
716
756
|
updateValidations(true);
|
|
717
757
|
}
|
|
718
758
|
}
|
|
719
759
|
}
|
|
720
760
|
|
|
721
|
-
private setCompare<T>(set1: Set<T>, set2: Set<T>): boolean {
|
|
722
|
-
if (set1.size !== set2.size) return false;
|
|
723
|
-
for (const entry of set1) if (!set2.has(entry)) return false;
|
|
724
|
-
return true;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
761
|
private get activeBindings(): Set<BindingInstance> {
|
|
728
762
|
return new Set(
|
|
729
763
|
Array.from(this.getBindings()).filter(
|
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.4.0-next.
|
|
34
|
-
const COMMIT = '
|
|
33
|
+
const PLAYER_VERSION = '0.4.0-next.13';
|
|
34
|
+
const COMMIT = '80a0ff72b097eff2e227c7effef17923655c7499';
|
|
35
35
|
|
|
36
36
|
export interface PlayerPlugin {
|
|
37
37
|
/**
|