@player-ui/data-change-listener-plugin 0.11.0-next.0 → 0.11.0-next.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/data-change-listener/core/src/index.ts"],"sourcesContent":["import type {\n Player,\n DataController,\n PlayerPlugin,\n View,\n ViewController,\n ExpressionType,\n ExpressionEvaluator,\n BindingInstance,\n BindingParser,\n ValidationController,\n} from \"@player-ui/player\";\nimport { isExpressionNode } from \"@player-ui/player\";\n\nconst LISTENER_TYPES = {\n dataChange: \"dataChange.\",\n};\n\nconst WILDCARD_REGEX = /\\._\\.|\\._$/;\n\n/** View with view level listeners that can do arbitrary expression */\ninterface ViewWithListener extends View {\n /** a list of listeners */\n listeners?: {\n /** the key specifies what type of listener */\n [key: string]: ExpressionType;\n };\n}\n\nexport type ViewListenerHandler = (\n context: {\n /** a means of evaluating an expression */\n expressionEvaluator: ExpressionEvaluator;\n },\n binding: BindingInstance,\n) => void;\n\n/** Sub out any _index_ refs with the ones from the supplied list */\nfunction replaceExpressionIndexes(\n expression: ExpressionType,\n indexes: Array<string | number>,\n): ExpressionType {\n if (indexes.length === 0) {\n return expression;\n }\n\n if (isExpressionNode(expression)) {\n return expression;\n }\n\n if (Array.isArray(expression)) {\n return expression.map((subExp) =>\n replaceExpressionIndexes(subExp, indexes),\n ) as any;\n }\n\n let workingExp = String(expression);\n\n for (\n let replacementIndex = 0;\n replacementIndex < indexes.length;\n replacementIndex += 1\n ) {\n const regex = new RegExp(\n `_index${replacementIndex === 0 ? \"\" : replacementIndex.toString()}_`,\n \"g\",\n );\n\n workingExp = workingExp.replace(\n regex,\n indexes[replacementIndex].toString(),\n );\n }\n\n return workingExp;\n}\n\n/**\n * Create a handler for view listeners with wildcard (._.) placeholders\n */\nfunction createWildcardHandler(\n listenerBinding: string,\n listenerExp: ExpressionType,\n bindingParser: BindingParser,\n): ViewListenerHandler {\n // The index of the start of the wildcard placeholder (foo._.bar)\n const wildCardIndex = listenerBinding.search(WILDCARD_REGEX);\n const parsedListenerBinding = bindingParser.parse(listenerBinding);\n\n // The top binding that we care about\n const topLevelBinding = bindingParser.parse(\n listenerBinding.substr(0, wildCardIndex),\n );\n\n /** Compute an updated expression (resolving _index_'s), or nothing if the binding update doesn't match */\n const getUpdatedExpressionToRun = (\n updatedBinding: BindingInstance,\n ): ExpressionType | undefined => {\n // what to replace _index_, _index1_, etc.\n const indexes: Array<number | string> = [];\n\n // walk down both bindings, and match up the ._. substitutions.\n // If we hit a placeholder, sub it out with the right value from the _actual_ binding\n // If we hit a non-placeholder, make sure the keys match up\n\n for (\n let bindingPartIndex = 0;\n bindingPartIndex < parsedListenerBinding.asArray().length;\n bindingPartIndex += 1\n ) {\n const listenerBindingPart =\n parsedListenerBinding.asArray()[bindingPartIndex];\n const updatedBindingPart = updatedBinding.asArray()[bindingPartIndex];\n\n if (listenerBindingPart === \"_\") {\n indexes.push(updatedBindingPart);\n } else if (updatedBindingPart !== listenerBindingPart) {\n // We are listening for a binding that isn't this one\n // foo._.bar vs. foo._.baz\n // bail out\n return;\n }\n }\n\n // sub out all of the _index_ values in the expression with our real ones.\n return replaceExpressionIndexes(listenerExp, indexes);\n };\n\n return (context, binding) => {\n if (topLevelBinding.contains(binding)) {\n // Check if the sub-listener is also a match\n const expToRun = getUpdatedExpressionToRun(binding);\n\n if (expToRun) {\n context.expressionEvaluator.evaluate(expToRun);\n }\n }\n };\n}\n\n/**\n * Extract data change listener from the view\n *\n * @param view - initial view with optional listener\n */\nfunction extractDataChangeListeners(\n view: ViewWithListener,\n bindingParser: BindingParser,\n): Array<ViewListenerHandler> {\n if (!view?.listeners) {\n return [];\n }\n\n const { listeners } = view;\n\n return Object.entries(listeners).reduce<Array<ViewListenerHandler>>(\n (allListeners, [listenerKey, listenerExp]) => {\n if (\n typeof listenerKey !== \"string\" ||\n !listenerKey.startsWith(LISTENER_TYPES.dataChange)\n ) {\n return allListeners;\n }\n\n const listenerRawBinding = listenerKey.slice(\n LISTENER_TYPES.dataChange.length,\n );\n\n if (listenerKey.match(WILDCARD_REGEX)) {\n allListeners.push(\n createWildcardHandler(listenerRawBinding, listenerExp, bindingParser),\n );\n return allListeners;\n }\n\n const parsedOriginalBinding = bindingParser.parse(listenerRawBinding);\n\n allListeners.push((context, binding) => {\n if (parsedOriginalBinding.contains(binding)) {\n context.expressionEvaluator.evaluate(listenerExp);\n }\n });\n return allListeners;\n },\n [],\n );\n}\n\n/**\n * this plugin processes the view level dataChange and evaluates custom expressions.\n */\nexport class DataChangeListenerPlugin implements PlayerPlugin {\n name = \"data-change-listener-plugin\";\n\n apply(player: Player) {\n let expressionEvaluator: ExpressionEvaluator;\n let dataChangeListeners: Array<ViewListenerHandler> = [];\n let validationController: ValidationController;\n\n player.hooks.expressionEvaluator.tap(\n this.name,\n (expEvaluator: ExpressionEvaluator) => {\n expressionEvaluator = expEvaluator;\n },\n );\n\n /**\n * This function handles checking if the updated field requires an expression to be evaluated,\n * One of the view-level listeners is attached to the field\n *\n * @param updates - field updates\n */\n const onFieldUpdateHandler = (updates: Array<BindingInstance>) => {\n if (\n updates.length === 0 ||\n !expressionEvaluator ||\n dataChangeListeners.length === 0\n ) {\n return;\n }\n\n updates.forEach((binding) => {\n dataChangeListeners.forEach((handler) => {\n handler(\n {\n expressionEvaluator,\n },\n binding,\n );\n });\n });\n };\n\n player.hooks.dataController.tap(this.name, (dc: DataController) =>\n dc.hooks.onUpdate.tap(this.name, (updates, options) => {\n const { silent = false } = options || {};\n if (silent) return;\n const validUpdates = updates.filter((update) => {\n return !validationController\n .getValidationForBinding(update.binding)\n ?.getAll().length;\n });\n onFieldUpdateHandler(validUpdates.map((t) => t.binding));\n }),\n );\n\n /**\n * Adding an interceptor instead of tapping to make intention clear. This plugin is not going to change the resolution of a view\n * so do not want to tap into the resolveView hook. This will just intercept and extract required dependencies\n * There are other hooks that can be used:\n * 1) view -> onUpdate : the update object is the updated assets but this gets called upon every data update\n * 2) view -> parser -> onParseObject: this gets called once per view but the input is a node within the Content but since the listener is only supported at the view level, this would be excessive\n * 3) view -> resolve -> : all resolve hooks are called every update - the listeners should not change between data updates.\n */\n const resolveViewInterceptor = {\n call: (view: View | undefined) => {\n const playerState = player.getState();\n\n if (playerState.status !== \"in-progress\" || !view) {\n return;\n }\n\n dataChangeListeners = extractDataChangeListeners(\n view,\n playerState.controllers.binding,\n );\n },\n };\n\n player.hooks.viewController.tap(\n this.name,\n (viewController: ViewController) => {\n viewController.hooks.resolveView.intercept(resolveViewInterceptor);\n\n // remove listeners after extracting so that it does not get triggered in subsequent view updates\n viewController.hooks.resolveView.tap(this.name, (view) => {\n const { listeners, ...withoutListeners } = view as any;\n return withoutListeners;\n });\n },\n );\n\n player.hooks.validationController.tap(this.name, (vc) => {\n validationController = vc;\n });\n\n player.hooks.flowController.tap(this.name, (flowController) => {\n flowController.hooks.flow.tap(this.name, (flow) => {\n flow.hooks.transition.tap(this.name, (from, to) => {\n if (to.value.state_type !== \"VIEW\") {\n dataChangeListeners = [];\n }\n });\n });\n });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/data-change-listener/core/src/index.ts"],"sourcesContent":["import type {\n Player,\n DataController,\n PlayerPlugin,\n View,\n ViewController,\n ExpressionType,\n ExpressionEvaluator,\n BindingInstance,\n BindingParser,\n ValidationController,\n} from \"@player-ui/player\";\nimport { isExpressionNode } from \"@player-ui/player\";\n\nconst LISTENER_TYPES = {\n dataChange: \"dataChange.\",\n};\n\nconst WILDCARD_REGEX = /\\._\\.|\\._$/;\n\n/** View with view level listeners that can do arbitrary expression */\ninterface ViewWithListener extends View {\n /** a list of listeners */\n listeners?: {\n /** the key specifies what type of listener */\n [key: string]: ExpressionType;\n };\n}\n\nexport type ViewListenerHandler = (\n context: {\n /** a means of evaluating an expression */\n expressionEvaluator: ExpressionEvaluator;\n },\n binding: BindingInstance,\n) => void;\n\n/** Sub out any _index_ refs with the ones from the supplied list */\nfunction replaceExpressionIndexes(\n expression: ExpressionType,\n indexes: Array<string | number>,\n): ExpressionType {\n if (indexes.length === 0) {\n return expression;\n }\n\n if (isExpressionNode(expression)) {\n return expression;\n }\n\n if (Array.isArray(expression)) {\n return expression.map((subExp) =>\n replaceExpressionIndexes(subExp, indexes),\n ) as any;\n }\n\n let workingExp = String(expression);\n\n for (\n let replacementIndex = 0;\n replacementIndex < indexes.length;\n replacementIndex += 1\n ) {\n const regex = new RegExp(\n `_index${replacementIndex === 0 ? \"\" : replacementIndex.toString()}_`,\n \"g\",\n );\n\n workingExp = workingExp.replace(\n regex,\n indexes[replacementIndex].toString(),\n );\n }\n\n return workingExp;\n}\n\n/**\n * Create a handler for view listeners with wildcard (._.) placeholders\n */\nfunction createWildcardHandler(\n listenerBinding: string,\n listenerExp: ExpressionType,\n bindingParser: BindingParser,\n): ViewListenerHandler {\n // The index of the start of the wildcard placeholder (foo._.bar)\n const wildCardIndex = listenerBinding.search(WILDCARD_REGEX);\n const parsedListenerBinding = bindingParser.parse(listenerBinding);\n\n // The top binding that we care about\n const topLevelBinding = bindingParser.parse(\n listenerBinding.substr(0, wildCardIndex),\n );\n\n /** Compute an updated expression (resolving _index_'s), or nothing if the binding update doesn't match */\n const getUpdatedExpressionToRun = (\n updatedBinding: BindingInstance,\n ): ExpressionType | undefined => {\n // what to replace _index_, _index1_, etc.\n const indexes: Array<number | string> = [];\n\n // walk down both bindings, and match up the ._. substitutions.\n // If we hit a placeholder, sub it out with the right value from the _actual_ binding\n // If we hit a non-placeholder, make sure the keys match up\n\n for (\n let bindingPartIndex = 0;\n bindingPartIndex < parsedListenerBinding.asArray().length;\n bindingPartIndex += 1\n ) {\n const listenerBindingPart =\n parsedListenerBinding.asArray()[bindingPartIndex];\n const updatedBindingPart = updatedBinding.asArray()[bindingPartIndex];\n\n if (listenerBindingPart === \"_\") {\n indexes.push(updatedBindingPart);\n } else if (updatedBindingPart !== listenerBindingPart) {\n // We are listening for a binding that isn't this one\n // foo._.bar vs. foo._.baz\n // bail out\n return;\n }\n }\n\n // sub out all of the _index_ values in the expression with our real ones.\n return replaceExpressionIndexes(listenerExp, indexes);\n };\n\n return (context, binding) => {\n if (topLevelBinding.contains(binding)) {\n // Check if the sub-listener is also a match\n const expToRun = getUpdatedExpressionToRun(binding);\n\n if (expToRun) {\n context.expressionEvaluator.evaluate(expToRun);\n }\n }\n };\n}\n\n/**\n * Extract data change listener from the view\n *\n * @param view - initial view with optional listener\n */\nfunction extractDataChangeListeners(\n view: ViewWithListener,\n bindingParser: BindingParser,\n): Array<ViewListenerHandler> {\n if (!view?.listeners) {\n return [];\n }\n\n const { listeners } = view;\n\n return Object.entries(listeners).reduce<Array<ViewListenerHandler>>(\n (allListeners, [listenerKey, listenerExp]) => {\n if (\n typeof listenerKey !== \"string\" ||\n !listenerKey.startsWith(LISTENER_TYPES.dataChange)\n ) {\n return allListeners;\n }\n\n const listenerRawBinding = listenerKey.slice(\n LISTENER_TYPES.dataChange.length,\n );\n\n if (listenerKey.match(WILDCARD_REGEX)) {\n allListeners.push(\n createWildcardHandler(listenerRawBinding, listenerExp, bindingParser),\n );\n return allListeners;\n }\n\n const parsedOriginalBinding = bindingParser.parse(listenerRawBinding);\n\n allListeners.push((context, binding) => {\n if (parsedOriginalBinding.contains(binding)) {\n context.expressionEvaluator.evaluate(listenerExp);\n }\n });\n return allListeners;\n },\n [],\n );\n}\n\n/**\n * this plugin processes the view level dataChange and evaluates custom expressions.\n */\nexport class DataChangeListenerPlugin implements PlayerPlugin {\n name = \"data-change-listener-plugin\";\n\n apply(player: Player) {\n let expressionEvaluator: ExpressionEvaluator;\n let dataChangeListeners: Array<ViewListenerHandler> = [];\n let validationController: ValidationController;\n\n player.hooks.expressionEvaluator.tap(\n this.name,\n (expEvaluator: ExpressionEvaluator) => {\n expressionEvaluator = expEvaluator;\n },\n );\n\n /**\n * This function handles checking if the updated field requires an expression to be evaluated,\n * One of the view-level listeners is attached to the field\n *\n * @param updates - field updates\n */\n const onFieldUpdateHandler = (updates: Array<BindingInstance>) => {\n if (\n updates.length === 0 ||\n !expressionEvaluator ||\n dataChangeListeners.length === 0\n ) {\n return;\n }\n\n updates.forEach((binding) => {\n dataChangeListeners.forEach((handler) => {\n handler(\n {\n expressionEvaluator,\n },\n binding,\n );\n });\n });\n };\n\n player.hooks.dataController.tap(this.name, (dc: DataController) =>\n dc.hooks.onUpdate.tap(this.name, (updates, options) => {\n const { silent = false } = options || {};\n if (silent) return;\n const validUpdates = updates.filter((update) => {\n return !validationController\n .getValidationForBinding(update.binding)\n ?.getAll().length;\n });\n onFieldUpdateHandler(validUpdates.map((t) => t.binding));\n }),\n );\n\n /**\n * Adding an interceptor instead of tapping to make intention clear. This plugin is not going to change the resolution of a view\n * so do not want to tap into the resolveView hook. This will just intercept and extract required dependencies\n * There are other hooks that can be used:\n * 1) view -> onUpdate : the update object is the updated assets but this gets called upon every data update\n * 2) view -> parser -> onParseObject: this gets called once per view but the input is a node within the Content but since the listener is only supported at the view level, this would be excessive\n * 3) view -> resolve -> : all resolve hooks are called every update - the listeners should not change between data updates.\n */\n const resolveViewInterceptor = {\n call: (view: View | undefined) => {\n const playerState = player.getState();\n\n if (playerState.status !== \"in-progress\" || !view) {\n return;\n }\n\n dataChangeListeners = extractDataChangeListeners(\n view,\n playerState.controllers.binding,\n );\n },\n };\n\n player.hooks.viewController.tap(\n this.name,\n (viewController: ViewController) => {\n viewController.hooks.resolveView.intercept(resolveViewInterceptor);\n\n // remove listeners after extracting so that it does not get triggered in subsequent view updates\n viewController.hooks.resolveView.tap(this.name, (view) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { listeners, ...withoutListeners } = view as any;\n return withoutListeners;\n });\n },\n );\n\n player.hooks.validationController.tap(this.name, (vc) => {\n validationController = vc;\n });\n\n player.hooks.flowController.tap(this.name, (flowController) => {\n flowController.hooks.flow.tap(this.name, (flow) => {\n flow.hooks.transition.tap(this.name, (from, to) => {\n if (to.value.state_type !== \"VIEW\") {\n dataChangeListeners = [];\n }\n });\n });\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/data-change-listener/core/src/index.ts"],"sourcesContent":["import type {\n Player,\n DataController,\n PlayerPlugin,\n View,\n ViewController,\n ExpressionType,\n ExpressionEvaluator,\n BindingInstance,\n BindingParser,\n ValidationController,\n} from \"@player-ui/player\";\nimport { isExpressionNode } from \"@player-ui/player\";\n\nconst LISTENER_TYPES = {\n dataChange: \"dataChange.\",\n};\n\nconst WILDCARD_REGEX = /\\._\\.|\\._$/;\n\n/** View with view level listeners that can do arbitrary expression */\ninterface ViewWithListener extends View {\n /** a list of listeners */\n listeners?: {\n /** the key specifies what type of listener */\n [key: string]: ExpressionType;\n };\n}\n\nexport type ViewListenerHandler = (\n context: {\n /** a means of evaluating an expression */\n expressionEvaluator: ExpressionEvaluator;\n },\n binding: BindingInstance,\n) => void;\n\n/** Sub out any _index_ refs with the ones from the supplied list */\nfunction replaceExpressionIndexes(\n expression: ExpressionType,\n indexes: Array<string | number>,\n): ExpressionType {\n if (indexes.length === 0) {\n return expression;\n }\n\n if (isExpressionNode(expression)) {\n return expression;\n }\n\n if (Array.isArray(expression)) {\n return expression.map((subExp) =>\n replaceExpressionIndexes(subExp, indexes),\n ) as any;\n }\n\n let workingExp = String(expression);\n\n for (\n let replacementIndex = 0;\n replacementIndex < indexes.length;\n replacementIndex += 1\n ) {\n const regex = new RegExp(\n `_index${replacementIndex === 0 ? \"\" : replacementIndex.toString()}_`,\n \"g\",\n );\n\n workingExp = workingExp.replace(\n regex,\n indexes[replacementIndex].toString(),\n );\n }\n\n return workingExp;\n}\n\n/**\n * Create a handler for view listeners with wildcard (._.) placeholders\n */\nfunction createWildcardHandler(\n listenerBinding: string,\n listenerExp: ExpressionType,\n bindingParser: BindingParser,\n): ViewListenerHandler {\n // The index of the start of the wildcard placeholder (foo._.bar)\n const wildCardIndex = listenerBinding.search(WILDCARD_REGEX);\n const parsedListenerBinding = bindingParser.parse(listenerBinding);\n\n // The top binding that we care about\n const topLevelBinding = bindingParser.parse(\n listenerBinding.substr(0, wildCardIndex),\n );\n\n /** Compute an updated expression (resolving _index_'s), or nothing if the binding update doesn't match */\n const getUpdatedExpressionToRun = (\n updatedBinding: BindingInstance,\n ): ExpressionType | undefined => {\n // what to replace _index_, _index1_, etc.\n const indexes: Array<number | string> = [];\n\n // walk down both bindings, and match up the ._. substitutions.\n // If we hit a placeholder, sub it out with the right value from the _actual_ binding\n // If we hit a non-placeholder, make sure the keys match up\n\n for (\n let bindingPartIndex = 0;\n bindingPartIndex < parsedListenerBinding.asArray().length;\n bindingPartIndex += 1\n ) {\n const listenerBindingPart =\n parsedListenerBinding.asArray()[bindingPartIndex];\n const updatedBindingPart = updatedBinding.asArray()[bindingPartIndex];\n\n if (listenerBindingPart === \"_\") {\n indexes.push(updatedBindingPart);\n } else if (updatedBindingPart !== listenerBindingPart) {\n // We are listening for a binding that isn't this one\n // foo._.bar vs. foo._.baz\n // bail out\n return;\n }\n }\n\n // sub out all of the _index_ values in the expression with our real ones.\n return replaceExpressionIndexes(listenerExp, indexes);\n };\n\n return (context, binding) => {\n if (topLevelBinding.contains(binding)) {\n // Check if the sub-listener is also a match\n const expToRun = getUpdatedExpressionToRun(binding);\n\n if (expToRun) {\n context.expressionEvaluator.evaluate(expToRun);\n }\n }\n };\n}\n\n/**\n * Extract data change listener from the view\n *\n * @param view - initial view with optional listener\n */\nfunction extractDataChangeListeners(\n view: ViewWithListener,\n bindingParser: BindingParser,\n): Array<ViewListenerHandler> {\n if (!view?.listeners) {\n return [];\n }\n\n const { listeners } = view;\n\n return Object.entries(listeners).reduce<Array<ViewListenerHandler>>(\n (allListeners, [listenerKey, listenerExp]) => {\n if (\n typeof listenerKey !== \"string\" ||\n !listenerKey.startsWith(LISTENER_TYPES.dataChange)\n ) {\n return allListeners;\n }\n\n const listenerRawBinding = listenerKey.slice(\n LISTENER_TYPES.dataChange.length,\n );\n\n if (listenerKey.match(WILDCARD_REGEX)) {\n allListeners.push(\n createWildcardHandler(listenerRawBinding, listenerExp, bindingParser),\n );\n return allListeners;\n }\n\n const parsedOriginalBinding = bindingParser.parse(listenerRawBinding);\n\n allListeners.push((context, binding) => {\n if (parsedOriginalBinding.contains(binding)) {\n context.expressionEvaluator.evaluate(listenerExp);\n }\n });\n return allListeners;\n },\n [],\n );\n}\n\n/**\n * this plugin processes the view level dataChange and evaluates custom expressions.\n */\nexport class DataChangeListenerPlugin implements PlayerPlugin {\n name = \"data-change-listener-plugin\";\n\n apply(player: Player) {\n let expressionEvaluator: ExpressionEvaluator;\n let dataChangeListeners: Array<ViewListenerHandler> = [];\n let validationController: ValidationController;\n\n player.hooks.expressionEvaluator.tap(\n this.name,\n (expEvaluator: ExpressionEvaluator) => {\n expressionEvaluator = expEvaluator;\n },\n );\n\n /**\n * This function handles checking if the updated field requires an expression to be evaluated,\n * One of the view-level listeners is attached to the field\n *\n * @param updates - field updates\n */\n const onFieldUpdateHandler = (updates: Array<BindingInstance>) => {\n if (\n updates.length === 0 ||\n !expressionEvaluator ||\n dataChangeListeners.length === 0\n ) {\n return;\n }\n\n updates.forEach((binding) => {\n dataChangeListeners.forEach((handler) => {\n handler(\n {\n expressionEvaluator,\n },\n binding,\n );\n });\n });\n };\n\n player.hooks.dataController.tap(this.name, (dc: DataController) =>\n dc.hooks.onUpdate.tap(this.name, (updates, options) => {\n const { silent = false } = options || {};\n if (silent) return;\n const validUpdates = updates.filter((update) => {\n return !validationController\n .getValidationForBinding(update.binding)\n ?.getAll().length;\n });\n onFieldUpdateHandler(validUpdates.map((t) => t.binding));\n }),\n );\n\n /**\n * Adding an interceptor instead of tapping to make intention clear. This plugin is not going to change the resolution of a view\n * so do not want to tap into the resolveView hook. This will just intercept and extract required dependencies\n * There are other hooks that can be used:\n * 1) view -> onUpdate : the update object is the updated assets but this gets called upon every data update\n * 2) view -> parser -> onParseObject: this gets called once per view but the input is a node within the Content but since the listener is only supported at the view level, this would be excessive\n * 3) view -> resolve -> : all resolve hooks are called every update - the listeners should not change between data updates.\n */\n const resolveViewInterceptor = {\n call: (view: View | undefined) => {\n const playerState = player.getState();\n\n if (playerState.status !== \"in-progress\" || !view) {\n return;\n }\n\n dataChangeListeners = extractDataChangeListeners(\n view,\n playerState.controllers.binding,\n );\n },\n };\n\n player.hooks.viewController.tap(\n this.name,\n (viewController: ViewController) => {\n viewController.hooks.resolveView.intercept(resolveViewInterceptor);\n\n // remove listeners after extracting so that it does not get triggered in subsequent view updates\n viewController.hooks.resolveView.tap(this.name, (view) => {\n const { listeners, ...withoutListeners } = view as any;\n return withoutListeners;\n });\n },\n );\n\n player.hooks.validationController.tap(this.name, (vc) => {\n validationController = vc;\n });\n\n player.hooks.flowController.tap(this.name, (flowController) => {\n flowController.hooks.flow.tap(this.name, (flow) => {\n flow.hooks.transition.tap(this.name, (from, to) => {\n if (to.value.state_type !== \"VIEW\") {\n dataChangeListeners = [];\n }\n });\n });\n });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/data-change-listener/core/src/index.ts"],"sourcesContent":["import type {\n Player,\n DataController,\n PlayerPlugin,\n View,\n ViewController,\n ExpressionType,\n ExpressionEvaluator,\n BindingInstance,\n BindingParser,\n ValidationController,\n} from \"@player-ui/player\";\nimport { isExpressionNode } from \"@player-ui/player\";\n\nconst LISTENER_TYPES = {\n dataChange: \"dataChange.\",\n};\n\nconst WILDCARD_REGEX = /\\._\\.|\\._$/;\n\n/** View with view level listeners that can do arbitrary expression */\ninterface ViewWithListener extends View {\n /** a list of listeners */\n listeners?: {\n /** the key specifies what type of listener */\n [key: string]: ExpressionType;\n };\n}\n\nexport type ViewListenerHandler = (\n context: {\n /** a means of evaluating an expression */\n expressionEvaluator: ExpressionEvaluator;\n },\n binding: BindingInstance,\n) => void;\n\n/** Sub out any _index_ refs with the ones from the supplied list */\nfunction replaceExpressionIndexes(\n expression: ExpressionType,\n indexes: Array<string | number>,\n): ExpressionType {\n if (indexes.length === 0) {\n return expression;\n }\n\n if (isExpressionNode(expression)) {\n return expression;\n }\n\n if (Array.isArray(expression)) {\n return expression.map((subExp) =>\n replaceExpressionIndexes(subExp, indexes),\n ) as any;\n }\n\n let workingExp = String(expression);\n\n for (\n let replacementIndex = 0;\n replacementIndex < indexes.length;\n replacementIndex += 1\n ) {\n const regex = new RegExp(\n `_index${replacementIndex === 0 ? \"\" : replacementIndex.toString()}_`,\n \"g\",\n );\n\n workingExp = workingExp.replace(\n regex,\n indexes[replacementIndex].toString(),\n );\n }\n\n return workingExp;\n}\n\n/**\n * Create a handler for view listeners with wildcard (._.) placeholders\n */\nfunction createWildcardHandler(\n listenerBinding: string,\n listenerExp: ExpressionType,\n bindingParser: BindingParser,\n): ViewListenerHandler {\n // The index of the start of the wildcard placeholder (foo._.bar)\n const wildCardIndex = listenerBinding.search(WILDCARD_REGEX);\n const parsedListenerBinding = bindingParser.parse(listenerBinding);\n\n // The top binding that we care about\n const topLevelBinding = bindingParser.parse(\n listenerBinding.substr(0, wildCardIndex),\n );\n\n /** Compute an updated expression (resolving _index_'s), or nothing if the binding update doesn't match */\n const getUpdatedExpressionToRun = (\n updatedBinding: BindingInstance,\n ): ExpressionType | undefined => {\n // what to replace _index_, _index1_, etc.\n const indexes: Array<number | string> = [];\n\n // walk down both bindings, and match up the ._. substitutions.\n // If we hit a placeholder, sub it out with the right value from the _actual_ binding\n // If we hit a non-placeholder, make sure the keys match up\n\n for (\n let bindingPartIndex = 0;\n bindingPartIndex < parsedListenerBinding.asArray().length;\n bindingPartIndex += 1\n ) {\n const listenerBindingPart =\n parsedListenerBinding.asArray()[bindingPartIndex];\n const updatedBindingPart = updatedBinding.asArray()[bindingPartIndex];\n\n if (listenerBindingPart === \"_\") {\n indexes.push(updatedBindingPart);\n } else if (updatedBindingPart !== listenerBindingPart) {\n // We are listening for a binding that isn't this one\n // foo._.bar vs. foo._.baz\n // bail out\n return;\n }\n }\n\n // sub out all of the _index_ values in the expression with our real ones.\n return replaceExpressionIndexes(listenerExp, indexes);\n };\n\n return (context, binding) => {\n if (topLevelBinding.contains(binding)) {\n // Check if the sub-listener is also a match\n const expToRun = getUpdatedExpressionToRun(binding);\n\n if (expToRun) {\n context.expressionEvaluator.evaluate(expToRun);\n }\n }\n };\n}\n\n/**\n * Extract data change listener from the view\n *\n * @param view - initial view with optional listener\n */\nfunction extractDataChangeListeners(\n view: ViewWithListener,\n bindingParser: BindingParser,\n): Array<ViewListenerHandler> {\n if (!view?.listeners) {\n return [];\n }\n\n const { listeners } = view;\n\n return Object.entries(listeners).reduce<Array<ViewListenerHandler>>(\n (allListeners, [listenerKey, listenerExp]) => {\n if (\n typeof listenerKey !== \"string\" ||\n !listenerKey.startsWith(LISTENER_TYPES.dataChange)\n ) {\n return allListeners;\n }\n\n const listenerRawBinding = listenerKey.slice(\n LISTENER_TYPES.dataChange.length,\n );\n\n if (listenerKey.match(WILDCARD_REGEX)) {\n allListeners.push(\n createWildcardHandler(listenerRawBinding, listenerExp, bindingParser),\n );\n return allListeners;\n }\n\n const parsedOriginalBinding = bindingParser.parse(listenerRawBinding);\n\n allListeners.push((context, binding) => {\n if (parsedOriginalBinding.contains(binding)) {\n context.expressionEvaluator.evaluate(listenerExp);\n }\n });\n return allListeners;\n },\n [],\n );\n}\n\n/**\n * this plugin processes the view level dataChange and evaluates custom expressions.\n */\nexport class DataChangeListenerPlugin implements PlayerPlugin {\n name = \"data-change-listener-plugin\";\n\n apply(player: Player) {\n let expressionEvaluator: ExpressionEvaluator;\n let dataChangeListeners: Array<ViewListenerHandler> = [];\n let validationController: ValidationController;\n\n player.hooks.expressionEvaluator.tap(\n this.name,\n (expEvaluator: ExpressionEvaluator) => {\n expressionEvaluator = expEvaluator;\n },\n );\n\n /**\n * This function handles checking if the updated field requires an expression to be evaluated,\n * One of the view-level listeners is attached to the field\n *\n * @param updates - field updates\n */\n const onFieldUpdateHandler = (updates: Array<BindingInstance>) => {\n if (\n updates.length === 0 ||\n !expressionEvaluator ||\n dataChangeListeners.length === 0\n ) {\n return;\n }\n\n updates.forEach((binding) => {\n dataChangeListeners.forEach((handler) => {\n handler(\n {\n expressionEvaluator,\n },\n binding,\n );\n });\n });\n };\n\n player.hooks.dataController.tap(this.name, (dc: DataController) =>\n dc.hooks.onUpdate.tap(this.name, (updates, options) => {\n const { silent = false } = options || {};\n if (silent) return;\n const validUpdates = updates.filter((update) => {\n return !validationController\n .getValidationForBinding(update.binding)\n ?.getAll().length;\n });\n onFieldUpdateHandler(validUpdates.map((t) => t.binding));\n }),\n );\n\n /**\n * Adding an interceptor instead of tapping to make intention clear. This plugin is not going to change the resolution of a view\n * so do not want to tap into the resolveView hook. This will just intercept and extract required dependencies\n * There are other hooks that can be used:\n * 1) view -> onUpdate : the update object is the updated assets but this gets called upon every data update\n * 2) view -> parser -> onParseObject: this gets called once per view but the input is a node within the Content but since the listener is only supported at the view level, this would be excessive\n * 3) view -> resolve -> : all resolve hooks are called every update - the listeners should not change between data updates.\n */\n const resolveViewInterceptor = {\n call: (view: View | undefined) => {\n const playerState = player.getState();\n\n if (playerState.status !== \"in-progress\" || !view) {\n return;\n }\n\n dataChangeListeners = extractDataChangeListeners(\n view,\n playerState.controllers.binding,\n );\n },\n };\n\n player.hooks.viewController.tap(\n this.name,\n (viewController: ViewController) => {\n viewController.hooks.resolveView.intercept(resolveViewInterceptor);\n\n // remove listeners after extracting so that it does not get triggered in subsequent view updates\n viewController.hooks.resolveView.tap(this.name, (view) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { listeners, ...withoutListeners } = view as any;\n return withoutListeners;\n });\n },\n );\n\n player.hooks.validationController.tap(this.name, (vc) => {\n validationController = vc;\n });\n\n player.hooks.flowController.tap(this.name, (flowController) => {\n flowController.hooks.flow.tap(this.name, (flow) => {\n flow.hooks.transition.tap(this.name, (from, to) => {\n if (to.value.state_type !== \"VIEW\") {\n dataChangeListeners = [];\n }\n });\n });\n });\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -6,10 +6,10 @@
6
6
  "types"
7
7
  ],
8
8
  "name": "@player-ui/data-change-listener-plugin",
9
- "version": "0.11.0-next.0",
9
+ "version": "0.11.0-next.1",
10
10
  "main": "dist/cjs/index.cjs",
11
11
  "peerDependencies": {
12
- "@player-ui/player": "0.11.0-next.0"
12
+ "@player-ui/player": "0.11.0-next.1"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@player-ui/partial-match-registry": "workspace:*",
package/src/index.ts CHANGED
@@ -274,6 +274,7 @@ export class DataChangeListenerPlugin implements PlayerPlugin {
274
274
 
275
275
  // remove listeners after extracting so that it does not get triggered in subsequent view updates
276
276
  viewController.hooks.resolveView.tap(this.name, (view) => {
277
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
277
278
  const { listeners, ...withoutListeners } = view as any;
278
279
  return withoutListeners;
279
280
  });