@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/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@player-ui/player",
3
- "version": "0.4.0-next.11",
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",
11
- "@player-ui/types": "0.4.0-next.11",
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",
@@ -1,5 +1,5 @@
1
1
  import { SyncBailHook, SyncWaterfallHook } from 'tapable-ts';
2
- import NestedError from 'nested-error-stacks';
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, Required<NormalizedResult> & ResolveBindingASTOptions]
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
- path.match(SIMPLE_BINDING_REGEX) &&
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;
@@ -1,4 +1,4 @@
1
- import NestedError from 'nested-error-stacks';
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 (/[\w\-@]+/) */
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
- return (
38
- (charCode >= 48 && charCode <= 57) || // 0 - 9
39
- (charCode >= 65 && charCode <= 90) || // A-Z
40
- (charCode >= 97 && charCode <= 122) || // a-z
41
- charCode === 95 || // _
42
- charCode === 45 || // -
43
- charCode === 64 // @
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((obj) => {
211
- if (obj.state === 'dismissed') {
212
- // Don't rerun any dismissed warnings
213
- return obj;
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
- const blocking =
217
- obj.value.blocking ??
218
- ((obj.value.severity === 'warning' && 'once') || true);
219
-
220
- const isBlockingNavigation =
221
- blocking === true || (blocking === 'once' && !canDismiss);
222
-
223
- const dismissable = canDismiss && blocking === 'once';
224
-
225
- if (
226
- this.currentPhase === 'navigation' &&
227
- obj.state === 'active' &&
228
- dismissable
229
- ) {
230
- if (obj.value.severity === 'warning') {
231
- const warn = obj as ActiveWarning;
232
- if (warn.dismissable && warn.response.dismiss) {
233
- warn.response.dismiss();
234
- } else {
235
- warn.dismissable = true;
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
- return obj;
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
- const response = runner(obj.value);
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
- ...this.applicableValidations,
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 (this.setCompare(lastActiveBindings, activeBindings)) {
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.11';
34
- const COMMIT = '843876eed97f2f08d6ade9a2d86984fc9e4bd916';
33
+ const PLAYER_VERSION = '0.4.0-next.13';
34
+ const COMMIT = '80a0ff72b097eff2e227c7effef17923655c7499';
35
35
 
36
36
  export interface PlayerPlugin {
37
37
  /**