@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/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.3.1-next.1';
34
- const COMMIT = 'b51603be07572111ca68e73d1b710ed2d0aa9412';
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
- state.onEnd &&
291
- (state.transitions[transitionVal] || state.transitions['*'])
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
- if (!('transitions' in state) || !state.transitions[transitionVal]) {
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', transitionVal],
307
- resolveStrings(state.transitions[transitionVal])
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
- // Use a queueMicrotask to make sure the expression transition is outside the scope of the flow hook
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
- flowController?.transition(
354
- String(expressionEvaluator?.evaluate(exp))
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
 
@@ -160,6 +160,7 @@ function traverseObject<T>(val: T, options: Options): T {
160
160
  }
161
161
 
162
162
  case 'object': {
163
+ if (!val) return val;
163
164
  // TODO: Do we care refs in keys?
164
165
  const keys = Object.keys(val);
165
166
  let newVal = val;
@@ -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<BindingInstance> | undefined;
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
- invalidBindings.push(...validations);
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
- return next.set(nextTransaction, options);
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
- const propsToSkip = new Set<string>(
124
- node.plugins?.stringResolver?.propertiesToSkip
125
- ? node.plugins?.stringResolver?.propertiesToSkip
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