UNPKG

ngx-dynamic-hooks

Version:

Automatically insert live Angular components into a dynamic string of content (based on their selector or any pattern of your choice) and render the result in the DOM.

1 lines 366 kB
{"version":3,"file":"ngx-dynamic-hooks.mjs","sources":["../../../projects/ngx-dynamic-hooks/src/lib/interfaces.ts","../../../projects/ngx-dynamic-hooks/src/lib/constants/core.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/settings/settings.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/settings/options.ts","../../../projects/ngx-dynamic-hooks/src/lib/constants/regexes.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/utils/utils.ts","../../../projects/ngx-dynamic-hooks/src/lib/parsers/selector/text/textSelectorHookParser.ts","../../../projects/ngx-dynamic-hooks/src/lib/parsers/selector/element/elementSelectorHookParser.ts","../../../projects/ngx-dynamic-hooks/src/lib/parsers/selector/selectorHookParserConfig.ts","../../../projects/ngx-dynamic-hooks/src/lib/parsers/selector/selectorHookParserConfigResolver.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/platform/platformService.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/platform/defaultPlatformService.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/platform/autoPlatformService.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/utils/logger.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/utils/hookFinder.ts","../../../projects/ngx-dynamic-hooks/src/lib/parsers/selector/text/tagHookFinder.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/utils/dataTypeEncoder.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/utils/dataTypeParser.ts","../../../projects/ngx-dynamic-hooks/src/lib/parsers/selector/bindingsValueManager.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/settings/parserEntryResolver.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/settings/settingsResolver.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/core/textHookFinder.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/core/elementHookFinder.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/utils/contentSanitizer.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/utils/deepComparer.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/core/componentUpdater.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/core/componentCreator.ts","../../../projects/ngx-dynamic-hooks/src/lib/services/dynamicHooksService.ts","../../../projects/ngx-dynamic-hooks/src/lib/dynamicHooksProviders.ts","../../../projects/ngx-dynamic-hooks/src/lib/standalone.ts","../../../projects/ngx-dynamic-hooks/src/lib/standaloneHelper.ts","../../../projects/ngx-dynamic-hooks/src/lib/components/dynamicHooksComponent.ts","../../../projects/ngx-dynamic-hooks/src/lib/components/dynamicSingleComponent.ts","../../../projects/ngx-dynamic-hooks/src/public-api.ts","../../../projects/ngx-dynamic-hooks/src/ngx-dynamic-hooks.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { DynamicHooksSettings } from './services/settings/settings';\n\n/**\n * Custom injector tokens that are used for varous internal communication purposes\n */\nexport const DYNAMICHOOKS_ALLSETTINGS = new InjectionToken<DynamicHooksSettings[]>('All of the settings registered in the whole app.');\nexport const DYNAMICHOOKS_ANCESTORSETTINGS = new InjectionToken<DynamicHooksSettings[]>('The settings collected from all ancestor injectors');\nexport const DYNAMICHOOKS_MODULESETTINGS = new InjectionToken<DynamicHooksSettings>('The settings for the currently loaded module.');\n\nexport interface SavedBindings {\n inputs?: {[key: string]: RichBindingData};\n outputs?: {[key: string]: RichBindingData};\n}\n\n/**\n * A detailed information object for a single binding, containing the raw unparsed binding,\n * its parsed value and all used context variables, if any\n */\nexport interface RichBindingData {\n raw: string;\n parsed: boolean;\n value: any;\n boundContextVariables: {[key: string]: any};\n}\n","export const contentElementAttr = '__ngx_dynamic_hooks_content'\nexport const anchorElementTag = 'dynamic-component-anchor';\nexport const anchorAttrHookId = '__ngx_dynamic_hooks_anchor_id';\nexport const anchorAttrParseToken = '__ngx_dynamic_hooks_anchor_parsetoken';\nexport const voidElementTags = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];","import { HookParserEntry } from './parserEntry';\nimport { HookParser } from '../../interfacesPublic';\nimport { ParseOptions } from './options';\n\nexport enum DynamicHooksInheritance {\n /**\n * Merges with settings from all injectors in the app.\n */\n All,\n\n /**\n * (Default) Only merges with settings from direct ancestor injectors (such a father and grandfather injectors, but not \"uncle\" injectors).\n */\n Linear,\n\n /**\n * Does not merge at all. Injector only uses own settings.\n */\n None\n}\n\n/**\n * The interface for users to define the global options\n */\nexport interface DynamicHooksSettings {\n\n /**\n * A list of parsers to use globally\n */\n parsers?: HookParserEntry[];\n\n /**\n * Options to use globally\n */\n options?: ParseOptions;\n\n /**\n * Used for providing child settings in child injector contexts\n */\n inheritance?: DynamicHooksInheritance;\n}","/**\n * Options that allow you to customize the parsing process\n */\nexport interface ParseOptions {\n /**\n * Whether to use Angular's `DomSanitizer` to sanitize the content (hooks are unaffected by this). Defaults to `true` if content is a string, `false` if its an HTML element.\n */\n sanitize?: boolean;\n\n /**\n * Whether to replace HTML entities like `&amp;` with normal characters.\n */\n convertHTMLEntities?: boolean;\n\n /**\n * When using a WYSIWYG-editor, enclosing text hooks may collide with its generated HTML (the `<p>`-tag starting before the hook and the corresponding `</p>`-tag ending inside, and vice versa). This will result in faulty HTML when rendered in a browser. This setting removes these ripped-apart tags.\n */\n fixParagraphTags?: boolean;\n\n /**\n * Whether to update the bindings of dynamic components only when the context object passed to the `DynamicHooksComponent` changes by reference.\n */\n updateOnPushOnly?: boolean;\n\n /**\n * Whether to deeply-compare inputs for dynamic components by their value instead of by their reference on updates.\n */\n compareInputsByValue?: boolean;\n\n /**\n * Whether to deeply-compare outputs for dynamic components by their value instead of by their reference on updates.\n */\n compareOutputsByValue?: boolean;\n\n /**\n * When comparing by value, how many levels deep to compare them (may impact performance).\n */\n compareByValueDepth?: number;\n\n /**\n * Whether to emit CustomEvents from the component host elements when an output emits. The event name will be the output name. Defaults to true in standalone mode, otherwise false.\n */\n triggerDOMEvents?: boolean;\n\n /**\n * Whether to ignore input aliases like `@Input('someAlias')` in dynamic components and use the actual property names instead.\n */\n ignoreInputAliases?: boolean;\n\n /**\n * Whether to ignore output aliases like `@Output('someAlias')` in dynamic components and use the actual property names instead.\n */\n ignoreOutputAliases?: boolean;\n\n /**\n * Whether to disregard `@Input()`-decorators completely and allow passing in values to any property in dynamic components.\n */\n acceptInputsForAnyProperty?: boolean;\n\n /**\n * Whether to disregard `@Output()`-decorators completely and allow subscribing to any `Observable` in dynamic components.\n */\n acceptOutputsForAnyObservable?: boolean;\n\n /**\n * Accepts a `LogOptions` object to customize when to log text, warnings and errors.\n */\n logOptions?: LogOptions;\n}\n\nexport interface LogOptions {\n\n /**\n * Whether to enable logging when in dev mode\n */\n dev?: boolean;\n\n /**\n * Whether to enable logging when in prod mode\n */\n prod?: boolean;\n\n /**\n * Whether to enable logging during Server-Side-Rendering\n */\n ssr?: boolean;\n}\n\n/**\n * Returns the default values for the ParseOptions\n */\nexport const getParseOptionDefaults: () => ParseOptions = () => {\n return {\n sanitize: true,\n convertHTMLEntities: true,\n fixParagraphTags: true,\n updateOnPushOnly: false,\n compareInputsByValue: false,\n compareOutputsByValue: false,\n compareByValueDepth: 5,\n triggerDOMEvents: false,\n ignoreInputAliases: false,\n ignoreOutputAliases: false,\n acceptInputsForAnyProperty: false,\n acceptOutputsForAnyObservable: false,\n logOptions: {\n dev: true,\n prod: false,\n ssr: false\n }\n };\n}\n\n\n","export const regexes: any = {};\n\n// General\nconst variableName = '[a-zA-Z_$]+[a-zA-Z0-9_$]*';\nconst attributeName = '[a-zA-Z$\\\\-_:][a-zA-Z$\\\\-_:0-9\\\\.]*';\n\n// Attribute regex\nregexes.attributeNameNoBracketsRegex = '(' + attributeName + ')';\nregexes.attributeNameBracketsRegex = '\\\\[(' + attributeName + ')\\\\]';\nregexes.attributeNameRoundBracketsRegex = '\\\\((' + attributeName + ')\\\\)';\nregexes.attributeNameRegex = '(?:' + regexes.attributeNameNoBracketsRegex + '|' + regexes.attributeNameBracketsRegex + '|' + regexes.attributeNameRoundBracketsRegex + ')';\nregexes.attributeValueDoubleQuotesRegex = '\\\"((?:\\\\\\\\.|[^\\\"])*?)\\\"'; // Clever bit of regex to allow escaped chars in strings: https://stackoverflow.com/a/1016356/3099523\nregexes.attributeValueSingleQuotesRegex = '\\'((?:\\\\\\\\.|[^\\'])*?)\\'';\n\n// Context var regex examples: https://regex101.com/r/zSbY7M/4\n// Supports the dot notation, the [] notation as well as function calls () for building variable paths\nregexes.variablePathDotNotation = '\\\\.' + variableName;\nregexes.variableBracketsNotation = '\\\\[[^\\\\]]*\\\\]'; // Relies on nested '[]'brackets being encoded\nregexes.variablePathFunctionCall = '\\\\([^\\\\)]*\\\\)'; // Relies on nested '()'-brackets being encoded.\nregexes.variablePathPartRegex = '(?:' + regexes.variablePathDotNotation + '|' + regexes.variableBracketsNotation + '|' + regexes.variablePathFunctionCall + ')';\nregexes.contextVariableRegex = 'context' + regexes.variablePathPartRegex + '*';\n\nregexes.placeholderVariablePathDotNotation = '\\\\@@@cxtDot@@@' + variableName;\nregexes.placeholderVariableBracketsNotation = '@@@cxtOpenSquareBracket@@@[^\\\\]]*@@@cxtCloseSquareBracket@@@';\nregexes.placeholderVariablePathFunctionCall = '@@@cxtOpenRoundBracket@@@[^\\\\)]*@@@cxtCloseRoundBracket@@@';\nregexes.placeholderVariablePathPartRegex = '(?:' + regexes.placeholderVariablePathDotNotation + '|' + regexes.placeholderVariableBracketsNotation + '|' + regexes.placeholderVariablePathFunctionCall + ')';\nregexes.placeholderContextVariableRegex = '__CXT__' + regexes.placeholderVariablePathPartRegex + '*';","\n/**\n * Polyfill for String.prototype.matchAll() from the ES2020 spec\n *\n * Note: The 'string.prototype.matchall' npm package was unstable for me so providing my own version here\n *\n * @param text - The text to search\n * @param regExp - The RegExp object to use\n */\nexport function matchAll(text: string, regExp: RegExp): {[index: number]: string, index: number, input: string}[] {\n // Must be global\n if (!regExp.global) {\n throw Error('TypeError: matchAll called with a non-global RegExp argument');\n }\n\n // Get matches\n const result = [];\n let match = regExp.exec(text);\n while (match !== null) {\n result.push(match);\n match = regExp.exec(text);\n }\n\n // Reset internal index\n regExp.lastIndex = 0;\n\n return result;\n}\n\n/**\n * Sort elements/nodes based on the order of their appearance in the document\n *\n * @param arr - The array to sort\n * @param sortCallback - The callback to use to sort the elements\n * @param getElement - An optional callback that returns the element to compare from each arr entry\n */\nexport function sortElements<T>(arr: T[], sortCallback: (a: any, b: any) => number, getElementCallback: (entry: T) => any): T[] {\n const result = [...arr];\n return result.sort(function(a, b) {\n\n if (typeof getElementCallback === 'function') {\n a = getElementCallback(a);\n b = getElementCallback(b);\n }\n\n return sortCallback(a, b);\n });\n}\n\n /**\n * Indicates if an element is either a component host element or part of a component's view/template\n * \n * @param element - The element to inspect\n */\n export function isAngularManagedElement(element: any): boolean {\n // Angular gives component host and view elements the following property, so can simply check for that\n return element?.__ngContext__ !== undefined;\n }","import { RichBindingData, SavedBindings } from '../../../interfaces';\nimport { HookParser, HookPosition, HookValue, HookComponentData, HookBindings } from '../../../interfacesPublic';\nimport { TagHookFinder } from './tagHookFinder';\nimport { BindingsValueManager } from '../bindingsValueManager';\nimport { SelectorHookParserConfig } from '../selectorHookParserConfig';\nimport { SelectorHookParserConfigResolver } from '../selectorHookParserConfigResolver';\nimport { regexes } from '../../../constants/regexes';\nimport { matchAll } from '../../../services/utils/utils';\nimport { ParseOptions } from '../../../services/settings/options';\n\n/**\n * A text parser to load components with their bindings like in Angular templates.\n */\nexport class TextSelectorHookParser implements HookParser {\n name: string|undefined;\n config: SelectorHookParserConfig;\n savedBindings: {[key: number]: SavedBindings} = {};\n\n constructor(config: SelectorHookParserConfig, private configResolver: SelectorHookParserConfigResolver, private tagHookFinder: TagHookFinder, private bindingsValueManager: BindingsValueManager) {\n this.config = this.configResolver.processConfig(config);\n this.name = this.config.name;\n }\n\n public findHooks(content: string, context: any, options: ParseOptions): HookPosition[] {\n let hookPositions = this.config.enclosing ?\n this.tagHookFinder.findEnclosingTags(content, this.config.selector!, this.config.bracketStyle, options) :\n this.tagHookFinder.findSingleTags(content, this.config.selector!, this.config.bracketStyle, options);\n\n if (this.config.allowSelfClosing) {\n hookPositions = [\n ...hookPositions, \n ...this.tagHookFinder.findSelfClosingTags(content, this.config.selector!, this.config.bracketStyle, options)\n ];\n hookPositions.sort((a, b) => a.openingTagStartIndex - b.openingTagStartIndex);\n }\n \n return hookPositions;\n }\n\n public loadComponent(hookId: number, hookValue: HookValue, context: any, childNodes: any[], options: ParseOptions): HookComponentData {\n return {\n component: this.config.component,\n hostElementTag: this.config.hostElementTag || this.config.selector, // If no hostElementTag specified, use selector (which in the case of TextSelectorHookParser is only allowed to be tag name)\n injector: this.config.injector,\n environmentInjector: this.config.environmentInjector\n };\n }\n\n public getBindings(hookId: number, hookValue: HookValue, context: any, options: ParseOptions): HookBindings {\n let hookBindings = this.savedBindings[hookId];\n\n // Parse bindings once from hookValue, then reuse on subsequent runs\n if (hookBindings === undefined) {\n hookBindings = this.createBindings(hookValue.openingTag!);\n this.savedBindings[hookId] = hookBindings;\n }\n\n // (Re)evaluate if needed\n this.bindingsValueManager.checkInputBindings(hookBindings.inputs!, context, this.config, options);\n this.bindingsValueManager.checkOutputBindings(hookBindings.outputs!, this.config, options);\n\n return {\n inputs: this.getValuesFromSavedBindings(hookBindings.inputs!),\n outputs: this.getValuesFromSavedBindings(hookBindings.outputs!)\n };\n }\n\n // Bindings\n // --------------------------------------------------------------------------\n\n /**\n * Returns RichBindingData for Angular-style inputs & output attrs from an openingTag\n * \n * @param openingTag - The openingTag to inspect\n */\n createBindings(openingTag: string): SavedBindings {\n const rawInputs = this.collectRawInputs(openingTag!);\n const inputBindings: {[key: string]: RichBindingData} = {};\n for (const [rawInputKey, rawInputValue] of Object.entries(rawInputs)) {\n inputBindings[rawInputKey] = {raw: rawInputValue, parsed: false, value: null, boundContextVariables: {}};\n }\n\n const rawOutputs = this.collectRawOutputs(openingTag!);\n const outputBindings: {[key: string]: RichBindingData} = {};\n for (const [rawOutputKey, rawOutputValue] of Object.entries(rawOutputs)) {\n outputBindings[rawOutputKey] = {raw: rawOutputValue, parsed: false, value: null, boundContextVariables: {}};\n }\n\n return {\n inputs: inputBindings,\n outputs: outputBindings\n };\n }\n\n /**\n * Collects Angular-style inputs from an openingTag\n *\n * @param openingTag - The openingTag to inspect\n */\n collectRawInputs (openingTag: string): {[key: string]: any} {\n const rawNoBracketInputs = this.getBindingsFromOpeningTag(openingTag, 'noBracketInputs', this.config.inputsBlacklist || null, this.config.inputsWhitelist || null);\n const rawBracketInputs = this.getBindingsFromOpeningTag(openingTag, 'bracketInputs', this.config.inputsBlacklist || null, this.config.inputsWhitelist || null);\n\n // NoBracketInputs are to be interpreted as plain strings, so wrap them in quotes\n for (const [noBracketInputName, noBracketInputValue] of Object.entries(rawNoBracketInputs)) {\n rawNoBracketInputs[noBracketInputName] = \"'\" + noBracketInputValue + \"'\";\n }\n\n // Merge both input objects\n return {...rawNoBracketInputs, ...rawBracketInputs};\n }\n\n /**\n * Collects Angular-style outputs from an openingTag\n *\n * @param openingTag - The openingTag to inspect\n */\n collectRawOutputs(openingTag: string): {[key: string]: any} {\n return this.getBindingsFromOpeningTag(openingTag!, 'outputs', this.config.outputsBlacklist || null, this.config.outputsWhitelist || null);\n }\n\n /**\n * Collects Angular-style inputs or outputs from an openingTag\n *\n * @param type - What kind of bindings to extract\n * @param openingTag - The opening tag to inspect\n * @param blacklist - A list of inputs/outputs to blacklist\n * @param whitelist - A list of inputs/outputs to whitelist\n */\n private getBindingsFromOpeningTag(openingTag: string, type: 'noBracketInputs'|'bracketInputs'|'outputs', blacklist: string[]|null, whitelist: string[]|null): {[key: string]: any} {\n const bindings: {[key: string]: any} = {};\n \n // Examples: https://regex101.com/r/17x3cc/16\n const attributeValuesOR = '(?:' + regexes.attributeValueDoubleQuotesRegex + '|' + regexes.attributeValueSingleQuotesRegex + ')';\n let attributeNameRegex;\n switch (type) {\n case 'noBracketInputs': attributeNameRegex = regexes.attributeNameNoBracketsRegex; break;\n case 'bracketInputs': attributeNameRegex = regexes.attributeNameBracketsRegex; break;\n case 'outputs': attributeNameRegex = regexes.attributeNameRoundBracketsRegex; break;\n }\n const attributeRegex = attributeNameRegex + '\\=' + attributeValuesOR;\n const attributePattern = new RegExp(attributeRegex, 'gim');\n const attributeMatches = matchAll(openingTag, attributePattern);\n \n // Collect raw bindings\n for (const match of attributeMatches) {\n // Could be either of the attribute value capturing groups\n let rawBindingValue = match[2] || match[3];\n // If value is empty (someInput=\"\"), it will return undefined for it. When using noBracketInputs, return empty string instead.\n if (rawBindingValue === undefined && type === 'noBracketInputs') {\n rawBindingValue = '';\n }\n bindings[match[1]] = rawBindingValue;\n }\n \n // Filter bindings\n const filteredBindings: {[key: string]: any} = {};\n for (const [bindingName, bindingValue] of Object.entries(bindings)) {\n if (blacklist && blacklist.includes(bindingName)) {\n continue;\n }\n if (whitelist && !whitelist.includes(bindingName)) {\n continue;\n }\n filteredBindings[bindingName] = bindingValue;\n }\n \n return filteredBindings;\n }\n\n /**\n * Transforms a RichBindingData object into a normal bindings object\n *\n * @param richBindingsObject - The object containing the RichBindingData\n */\n private getValuesFromSavedBindings(richBindingsObject: {[key: string]: RichBindingData}): {[key: string]: any} {\n const result: {[key: string]: any} = {};\n for (const [key, value] of Object.entries(richBindingsObject)) {\n result[key] = value.value;\n }\n return result;\n }\n}\n","import { RichBindingData, SavedBindings } from '../../../interfaces';\nimport { HookParser, HookValue, HookComponentData, HookBindings } from '../../../interfacesPublic';\nimport { BindingsValueManager } from '../bindingsValueManager';\nimport { SelectorHookParserConfig } from '../selectorHookParserConfig';\nimport { SelectorHookParserConfigResolver } from '../selectorHookParserConfigResolver';\nimport { AutoPlatformService } from '../../../services/platform/autoPlatformService';\nimport { ParseOptions } from '../../../services/settings/options';\n\n/**\n * An element parser to load components with their bindings like in Angular templates.\n */\nexport class ElementSelectorHookParser implements HookParser {\n name: string|undefined;\n config: SelectorHookParserConfig;\n savedBindings: {[key: number]: SavedBindings} = {};\n\n constructor(config: SelectorHookParserConfig, private configResolver: SelectorHookParserConfigResolver, private platformService: AutoPlatformService, private bindingsValueManager: BindingsValueManager) {\n this.config = this.configResolver.processConfig(config);\n this.name = this.config.name;\n }\n\n public findHookElements(contentElement: any, context: any, options: ParseOptions): any[] {\n return Array.from(this.platformService.querySelectorAll(contentElement, this.config.selector!));\n }\n\n public loadComponent(hookId: number, hookValue: HookValue, context: any, childNodes: any[], options: ParseOptions): HookComponentData {\n\n // Always scrub potential []-input- and ()-output-attrs from anchor elements \n this.scrubAngularBindingAttrs(hookValue.element);\n\n return {\n component: this.config.component,\n hostElementTag: this.config.hostElementTag,\n injector: this.config.injector,\n environmentInjector: this.config.environmentInjector\n };\n }\n\n public getBindings(hookId: number, hookValue: HookValue, context: any, options: ParseOptions): HookBindings {\n let hookBindings = this.savedBindings[hookId];\n\n // Parse bindings once from hookValue, then reuse on subsequent runs (raw values will never change as hookValue.element is a snapshot)\n if (hookBindings === undefined) {\n hookBindings = this.createBindings(hookValue.elementSnapshot!);\n this.savedBindings[hookId] = hookBindings;\n }\n\n // (Re)evaluate if needed\n this.bindingsValueManager.checkInputBindings(hookBindings.inputs!, context, this.config, options);\n this.bindingsValueManager.checkOutputBindings(hookBindings.outputs!, this.config, options);\n\n return {\n inputs: this.getValuesFromSavedBindings(hookBindings.inputs!),\n outputs: this.getValuesFromSavedBindings(hookBindings.outputs!)\n };\n }\n\n // Bindings\n // --------------------------------------------------------------------------\n\n /**\n * Always removes angular-typical template attrs like []-input and ()-outputs from anchors\n *\n * @param anchorElement - The element to strub\n */\n scrubAngularBindingAttrs(anchorElement: any) {\n const attrsToScrub = Array.from(anchorElement.attributes)\n .map((attrObj: any) => attrObj.name)\n .filter((attr: string) => \n (attr.startsWith('[') && attr.endsWith(']')) ||\n (attr.startsWith('(') && attr.endsWith(')'))\n );\n\n for (const attr of attrsToScrub) {\n this.platformService.removeAttribute(anchorElement, attr);\n }\n }\n\n /**\n * Returns RichBindingData for Angular-style inputs & output attrs from an element\n * \n * @param element - The element to inspect\n */\n createBindings(element: any): SavedBindings {\n const rawInputs = this.collectRawBindings(element!, 'inputs', this.config.inputsBlacklist || null, this.config.inputsWhitelist || null);\n const inputBindings: {[key: string]: RichBindingData} = {};\n for (const [rawInputKey, rawInputValue] of Object.entries(rawInputs)) {\n inputBindings[rawInputKey] = {raw: rawInputValue, parsed: false, value: null, boundContextVariables: {}};\n }\n\n const rawOutputs = this.collectRawBindings(element!, 'outputs', this.config.outputsBlacklist || null, this.config.outputsWhitelist || null);\n const outputBindings: {[key: string]: RichBindingData} = {};\n for (const [rawOutputKey, rawOutputValue] of Object.entries(rawOutputs)) {\n outputBindings[rawOutputKey] = {raw: rawOutputValue, parsed: false, value: null, boundContextVariables: {}};\n }\n\n return {\n inputs: inputBindings,\n outputs: outputBindings\n };\n }\n\n /**\n * Returns Angular-style inputs or output attrs from an element\n * \n * @param element - The element to inspect\n * @param type - Whether to return the inputs or outputs\n * @param blacklist - A list of inputs/outputs to blacklist\n * @param whitelist - A list of inputs/outputs to whitelist\n */\n collectRawBindings (element: any, type: 'inputs'|'outputs', blacklist: string[]|null, whitelist: string[]|null): {[key: string]: any} {\n const bindings: {[key: string]: any} = {};\n\n // Collect raw bindings\n const attrNames = this.platformService.getAttributeNames(element);\n for (let attrName of attrNames) {\n if (\n type === 'inputs' && (!attrName.startsWith('(') || !attrName.endsWith(')')) ||\n type === 'outputs' && (attrName.startsWith('(') && attrName.endsWith(')'))\n ) {\n let binding: any = this.platformService.getAttribute(element, attrName);\n\n // If input has []-brackets: Transform empty attr to undefined\n if (type === 'inputs' && attrName.startsWith('[') && attrName.endsWith(']') && binding === '') {\n binding = undefined;\n }\n\n // If input has no []-brackets: Should be interpreted as plain strings, so wrap in quotes\n if (type === 'inputs' && (!attrName.startsWith('[') || !attrName.endsWith(']'))) {\n binding = `'${binding}'`;\n }\n\n // Trim [] and () brackets from attr name\n attrName = attrName.replace(/^\\[|^\\(|\\]$|\\)$/g, '');\n\n bindings[attrName] = binding;\n }\n }\n \n // Filter bindings\n const filteredBindings: {[key: string]: any} = {};\n for (const [bindingName, bindingValue] of Object.entries(bindings)) {\n if (blacklist && blacklist.includes(bindingName)) {\n continue;\n }\n if (whitelist && !whitelist.includes(bindingName)) {\n continue;\n }\n filteredBindings[bindingName] = bindingValue;\n }\n\n return filteredBindings;\n }\n\n /**\n * Transforms a RichBindingData object into a normal bindings object\n *\n * @param richBindingsObject - The object containing the RichBindingData\n */\n private getValuesFromSavedBindings(richBindingsObject: {[key: string]: RichBindingData}): {[key: string]: any} {\n const result: {[key: string]: any} = {};\n for (const [key, value] of Object.entries(richBindingsObject)) {\n result[key] = value.value;\n }\n return result;\n }\n}\n","import { EnvironmentInjector, Injector } from '@angular/core';\nimport { ComponentConfig } from '../../interfacesPublic';\n\n/**\n * Several options to configure and instantiate a `SelectorHookParser` with\n */\nexport interface SelectorHookParserConfig {\n /**\n * The component to be used. Can be its class or a `LazyLoadComponentConfig`.\n */\n component: ComponentConfig;\n\n /**\n * The name of the parser. Only required if you want to black- or whitelist it.\n */\n name?: string;\n\n /**\n * The selector to use to find the hook.\n */\n selector?: string;\n\n /**\n * A custom tag to be used for the component host element.\n */\n hostElementTag?: string;\n\n /**\n * Whether to use regular expressions rather than HTML/DOM-based methods to find the hook elements\n */\n parseWithRegex?: boolean;\n\n /**\n * Whether to allow using self-closing selector tags (`<hook/>`) in addition to enclosing tags (`<hook>...</hook>`)\n */\n allowSelfClosing?: boolean;\n\n /**\n * @deprecated Whether the selector is enclosing (`<hook>...</hook>`) or not (`<hook>`). Use the \"allowSelfClosing\" option for a more modern approach.\n */\n enclosing?: boolean;\n\n /**\n * The brackets to use for the selector.\n */\n bracketStyle?: {opening: string, closing: string};\n\n /**\n * Whether to parse inputs into data types or leave them as strings.\n */\n parseInputs?: boolean;\n\n /**\n * Whether to remove escaping backslashes from inputs.\n */\n unescapeStrings?: boolean;\n\n /**\n * The Injector to create the component with.\n */\n injector?: Injector;\n\n /**\n * The EnvironmentInjector to create the component with.\n */\n environmentInjector?: EnvironmentInjector;\n\n /**\n * A list of inputs to ignore.\n */\n inputsBlacklist?: string[];\n\n /**\n * A list of inputs to allow exclusively.\n */\n inputsWhitelist?: string[];\n\n /**\n * A list of outputs to ignore.\n */\n outputsBlacklist?: string[];\n\n /**\n * A list of outputs to allow exclusively.\n */\n outputsWhitelist?: string[];\n\n /**\n * Whether to allow the use of context object variables in inputs and outputs.\n */\n allowContextInBindings?: boolean;\n\n /**\n * Whether to allow calling context object functions in inputs and outputs.\n */\n allowContextFunctionCalls?: boolean;\n}\n\n// Overwrites SelectorHookParserConfig so some values can be undefined for the defaults. If still undefined after merging with user config, throws error programmatically.\nexport type SelectorHookParserConfigDefaults = Omit<SelectorHookParserConfig, 'component'> & { component: ComponentConfig|undefined };\n\n/**\n * The default values for the SelectorHookParserConfig\n */\nexport const selectorHookParserConfigDefaults: SelectorHookParserConfigDefaults = {\n component: undefined,\n name: undefined,\n parseWithRegex: false,\n selector: undefined,\n hostElementTag: undefined,\n injector: undefined,\n allowSelfClosing: true,\n enclosing: true,\n bracketStyle: {opening: '<', closing: '>'},\n parseInputs: true,\n unescapeStrings: true,\n inputsBlacklist: undefined,\n inputsWhitelist: undefined,\n outputsBlacklist: undefined,\n outputsWhitelist: undefined,\n allowContextInBindings: true,\n allowContextFunctionCalls: true\n};\n","import { Injectable, reflectComponentType } from '@angular/core';\nimport { SelectorHookParserConfig, SelectorHookParserConfigDefaults, selectorHookParserConfigDefaults } from './selectorHookParserConfig';\n\n/**\n * A helper class for resolving a SelectorHookParserConfig\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class SelectorHookParserConfigResolver {\n\n constructor() {\n }\n\n /**\n * Overwrites the default parser config with a (partial) SelectorHookParserConfig object and returns the result\n *\n * @param userParserConfig - The (partial) SelectorHookParserConfig object\n */\n processConfig(userParserConfig: SelectorHookParserConfig): SelectorHookParserConfig {\n const parserConfig: SelectorHookParserConfigDefaults = JSON.parse(JSON.stringify(selectorHookParserConfigDefaults));\n\n // component\n if (!userParserConfig || !userParserConfig.hasOwnProperty('component')) {\n throw Error('Missing the required \"component\" property for the SelectorHookParserConfig. Must be either the component class or a LazyLoadComponentConfig.');\n }\n parserConfig.component = userParserConfig.component;\n\n // If is class\n if (userParserConfig.component.hasOwnProperty('prototype')) {\n const compMeta = reflectComponentType(userParserConfig.component as (new(...args: any[]) => any))!;\n parserConfig.selector = compMeta.selector;\n\n // If is LazyLoadingComponentConfig\n } else if (userParserConfig.component.hasOwnProperty('importPromise') && userParserConfig.component.hasOwnProperty('importName')) {\n if (!userParserConfig.hasOwnProperty('selector')) {\n throw Error(`When using lazy-loaded dynamic components, you have to specify the \"selector\" property in the parser config, as the real selector can't be known before the component is loaded.`); \n }\n // If is neither\n } else {\n throw Error('The \"component\" property in the SelectorHookParserConfig must either contain the component class or a LazyLoadComponentConfig.');\n }\n\n // name\n if (userParserConfig.hasOwnProperty('name')) {\n if (typeof userParserConfig.name !== 'string') { throw Error('The submitted \"name\" property in the SelectorHookParserConfig must be of type string, was ' + typeof userParserConfig.name); }\n parserConfig.name = userParserConfig.name;\n }\n\n // selector (defaults to component selector)\n if (userParserConfig.hasOwnProperty('selector')) {\n if (typeof userParserConfig.selector !== 'string') { throw Error('The submitted \"selector\" property in the SelectorHookParserConfig must be of type string, was ' + typeof userParserConfig.selector); }\n parserConfig.selector = userParserConfig.selector;\n }\n\n // hostElementTag\n if (userParserConfig.hasOwnProperty('hostElementTag')) {\n if (typeof userParserConfig.hostElementTag !== 'string') { throw Error('The submitted \"hostElementTag\" property in the SelectorHookParserConfig must be of type string, was ' + typeof userParserConfig.hostElementTag); }\n parserConfig.hostElementTag = userParserConfig.hostElementTag;\n }\n\n // parseWithRegex\n if (userParserConfig.hasOwnProperty('parseWithRegex')) {\n if (typeof userParserConfig.parseWithRegex !== 'boolean') { throw Error('The submitted \"parseWithRegex\" property in the SelectorHookParserConfig must be of type boolean, was ' + typeof userParserConfig.parseWithRegex); }\n parserConfig.parseWithRegex = userParserConfig.parseWithRegex;\n }\n\n // allowSelfClosing\n if (userParserConfig.hasOwnProperty('allowSelfClosing')) {\n if (typeof userParserConfig.allowSelfClosing !== 'boolean') { throw Error('The submitted \"allowSelfClosing\" property in the SelectorHookParserConfig must be of type boolean, was ' + typeof userParserConfig.allowSelfClosing); }\n parserConfig.allowSelfClosing = userParserConfig.allowSelfClosing;\n }\n\n // enclosing\n if (userParserConfig.hasOwnProperty('enclosing')) {\n if (typeof userParserConfig.enclosing !== 'boolean') { throw Error('The submitted \"enclosing\" property in the SelectorHookParserConfig must be of type boolean, was ' + typeof userParserConfig.enclosing); }\n parserConfig.enclosing = userParserConfig.enclosing;\n }\n\n // bracketStyle\n if (userParserConfig.hasOwnProperty('bracketStyle')) {\n if (typeof userParserConfig.bracketStyle !== 'object' || typeof userParserConfig.bracketStyle.opening !== 'string' || typeof userParserConfig.bracketStyle.closing !== 'string') {\n throw Error('The submitted \"bracketStyle\" property in the SelectorHookParserConfig must have the form {opening: string, closing: string}');\n }\n parserConfig.bracketStyle = userParserConfig.bracketStyle;\n }\n\n // injector (defaults to undefined)\n if (userParserConfig.hasOwnProperty('injector')) {\n parserConfig.injector = userParserConfig.injector;\n }\n\n // environmentInjector (defaults to undefined)\n if (userParserConfig.hasOwnProperty('environmentInjector')) {\n parserConfig.environmentInjector = userParserConfig.environmentInjector;\n }\n\n // unescapeStrings\n if (userParserConfig.hasOwnProperty('unescapeStrings')) {\n if (typeof userParserConfig.unescapeStrings !== 'boolean') { throw Error('The submitted \"unescapeStrings\" property in the SelectorHookParserConfig must be of type boolean, was ' + typeof userParserConfig.unescapeStrings); }\n parserConfig.unescapeStrings = userParserConfig.unescapeStrings;\n }\n\n // parseInputs\n if (userParserConfig.hasOwnProperty('parseInputs')) {\n if (typeof userParserConfig.parseInputs !== 'boolean') { throw Error('The submitted \"parseInputs\" property in the SelectorHookParserConfig must be of type boolean, was ' + typeof userParserConfig.parseInputs); }\n parserConfig.parseInputs = userParserConfig.parseInputs;\n }\n\n // inputsBlacklist\n if (userParserConfig.hasOwnProperty('inputsBlacklist')) {\n if (!Array.isArray(userParserConfig.inputsBlacklist)) { throw Error('The submitted \"inputsBlacklist\" property in the SelectorHookParserConfig must be an array of strings.'); }\n for (const entry of userParserConfig.inputsBlacklist) {\n if (typeof entry !== 'string') { throw Error('All entries of the submitted \"inputsBlacklist\" property in the SelectorHookParserConfig must be of type string, ' + typeof entry + ' found.'); }\n }\n parserConfig.inputsBlacklist = userParserConfig.inputsBlacklist;\n }\n\n // inputsWhitelist\n if (userParserConfig.hasOwnProperty('inputsWhitelist')) {\n if (!Array.isArray(userParserConfig.inputsWhitelist)) { throw Error('The submitted \"inputsWhitelist\" property in the SelectorHookParserConfig must be an array of strings.'); }\n for (const entry of userParserConfig.inputsWhitelist) {\n if (typeof entry !== 'string') { throw Error('All entries of the submitted \"inputsWhitelist\" property in the SelectorHookParserConfig must be of type string, ' + typeof entry + ' found.'); }\n }\n parserConfig.inputsWhitelist = userParserConfig.inputsWhitelist;\n }\n\n // outputsBlacklist\n if (userParserConfig.hasOwnProperty('outputsBlacklist')) {\n if (!Array.isArray(userParserConfig.outputsBlacklist)) { throw Error('The submitted \"outputsBlacklist\" property in the SelectorHookParserConfig must be an array of strings.'); }\n for (const entry of userParserConfig.outputsBlacklist) {\n if (typeof entry !== 'string') { throw Error('All entries of the submitted \"outputsBlacklist\" property in the SelectorHookParserConfig must be of type string, ' + typeof entry + ' found.'); }\n }\n parserConfig.outputsBlacklist = userParserConfig.outputsBlacklist;\n }\n\n // outputsWhitelist\n if (userParserConfig.hasOwnProperty('outputsWhitelist')) {\n if (!Array.isArray(userParserConfig.outputsWhitelist)) { throw Error('The submitted \"outputsWhitelist\" property in the SelectorHookParserConfig must be an array of strings.'); }\n for (const entry of userParserConfig.outputsWhitelist) {\n if (typeof entry !== 'string') { throw Error('All entries of the submitted \"outputsWhitelist\" property in the SelectorHookParserConfig must be of type string, ' + typeof entry + ' found.'); }\n }\n parserConfig.outputsWhitelist = userParserConfig.outputsWhitelist;\n }\n\n // allowContextInBindings\n if (userParserConfig.hasOwnProperty('allowContextInBindings')) {\n if (typeof userParserConfig.allowContextInBindings !== 'boolean') { throw Error('The submitted \"allowContextInBindings\" property in the SelectorHookParserConfig must be of type boolean, was ' + typeof userParserConfig.allowContextInBindings); }\n parserConfig.allowContextInBindings = userParserConfig.allowContextInBindings;\n }\n\n // allowContextFunctionCalls\n if (userParserConfig.hasOwnProperty('allowContextFunctionCalls')) {\n if (typeof userParserConfig.allowContextFunctionCalls !== 'boolean') { throw Error('The submitted \"allowContextFunctionCalls\" property in the SelectorHookParserConfig must be of type boolean, was ' + typeof userParserConfig.allowContextFunctionCalls); }\n parserConfig.allowContextFunctionCalls = userParserConfig.allowContextFunctionCalls;\n }\n\n return parserConfig as SelectorHookParserConfig;\n }\n}\n","import { InjectionToken } from \"@angular/core\";\n\nexport type PlatformService = Partial<CompletePlatformService>;\n\nexport const PLATFORM_SERVICE = new InjectionToken<PlatformService>('An injection token to retrieve an optionally user-provided PlatformService');\n\n/**\n * Extend this service to implement custom platform.\n */\nexport interface CompletePlatformService {\n\n /**\n * Returns the Angular Version.\n * Returns null when it couldn't be retrieved\n */\n getNgVersion(): number|null;\n\n /**\n * Sanitizes a string of arbitrary html content to be safe for use in innerHTML\n * Returns the sanitized html string\n * @param content The content to be sanitized.\n */\n sanitize(content: string): string;\n\n /**\n * Creates an element and returns it\n * @param tagName The name of the element\n */\n createElement(tagName: string): any;\n\n /**\n * Given two elements, return a number indicating which one comes first\n * @param a - The first element\n * @param b - The second element\n * @returns - 1 if b comes before a, -1 if a comes before b, 0 if equivalent\n */\n sortElements(a: any, b: any): number\n\n /**\n * Return a shallow clone of an element (just the element itself, not its children)\n *\n * @param element - The element to clone\n */\n cloneElement(element: any): any\n\n /**\n * Returns the tag name of an element\n * @param element An element\n */\n getTagName(element: any): string;\n\n /**\n * Returns the opening tag of an element as a string\n * @param element An element\n */\n getOpeningTag(element: any): string;\n\n /**\n * Returns the closing tag of an element as a string\n * @param element An element\n */\n getClosingTag(element: any): string;\n\n /**\n * Returns the names of all existing attributes of an element\n * Return an emtpy array if none exist\n * @param element The element\n */\n getAttributeNames(element: any): string[];\n\n /**\n * Returns the value of an element attribute.\n * Returns null when the attribute doesn't exist\n * @param element The element\n * @param attributeName Attribute Name\n */\n getAttribute(element: any, attributeName: string): string|null;\n\n /**\n * Sets the value of an element attribute.\n * @param element The element\n * @param attributeName Attribute Name\n * @param value The attribute value\n */\n setAttribute(element: any, attributeName: string, value: string): void;\n\n /**\n * Removes the value of an element attribute.\n * @param element The element\n * @param attributeName Attribute Name\n */\n removeAttribute(element: any, attributeName: string): void;\n\n /**\n * Returns the parent of a node.\n * Returns null when a parent node doesn't exist\n * @param parentany The parent element\n */\n getParentNode(parentNode: any): any|null;\n\n /**\n * Returns child elements of a parent element that match a certain css selector\n * Returns an empty array of none could be found\n * @param parentElement The parent element\n * @param selector A css-style selector (like \"div.myClass\")\n */\n querySelectorAll(parentElement: any, selector: string): any[];\n\n /**\n * Returns an array of child nodes.\n * Returns an empty array if none exist\n * @param parentNode A node\n */\n getChildNodes(parentNode: any): any[];\n\n /**\n * Appends a child node to a parent.\n * @param parentNode The parent node\n * @param childNode The child node to be removed\n */\n appendChild(parentNode: any, childNode: any): void;\n\n /**\n * Inserts a child node before another child node of a parent node.\n * @param parentNode The parent node\n * @param childNode The child node to be inserted\n * @param referenceNode The existing node before which childNode is inserted\n */\n insertBefore(parentNode: any, childNode: any, referenceNode: any): void;\n\n /**\n * Removes all child nodes from a parent node.\n * @param parentNode The parent node\n */\n clearChildNodes(parentNode: any): void;\n\n /**\n * Removes a child node from its parent.\n * @param parentNode The parent node\n * @param childNode The child node to be removed\n */\n removeChild(parentNode: any, childNode: any): void;\n\n /**\n * Returns the inner content of an element (like HTMLElement.innerHTML)\n * @param element An element\n */\n getInnerContent(element: any): string;\n\n /**\n * Sets the content of an element.\n * @param element An element\n * @param content The element content\n */\n setInnerContent(element: any, content: string): void;\n\n /**\n * Returns a boolean determining whether an element is a text node or not\n * @param element An element\n */\n isTextNode(element: any): boolean;\n\n /**\n * Creates a text node and returns it\n * @param content The text content of the node\n */\n createTextNode(content: string): any;\n\n /**\n * Returns the pure text content of an element (like Node.textContent)\n * @param element An element\n */\n getTextContent(element: any): string|null;\n\n /**\n * Dispatches a event from an element\n * @param element The element\n * @param name The event name\n * @param payload The event content\n */\n dispatchEvent(element: any, name: string, payload: any): void;\n\n}\n","import { Inject, Injectable, Renderer2, RendererFactory2, SecurityContext } from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { CompletePlatformService } from './platformService';\nimport { DOCUMENT } from '@angular/common';\n\n/**\n * General implementation of PlatformService suited for both the standard browser and server environments\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class DefaultPlatformService implements CompletePlatformService {\n private renderer: Renderer2;\n\n constructor(@Inject(DOCUMENT) private document: Document, private rendererFactory: RendererFactory2, private sanitizer: DomSanitizer) { \n this.renderer = this.rendererFactory.createRenderer(null, null);\n }\n\n getNgVersion() {\n if (typeof this.document !== \"undefined\") {\n const versionElement = this.querySelectorAll(this.document, '[ng-version]')?.[0];\n const versionAttr = versionElement?.getAttribute('ng-version');\n if (versionAttr) {\n return parseInt(versionAttr, 10);\n }\n }\n\n return null;\n }\n \n sanitize(content: string) {\n return this.sanitizer.sanitize(SecurityContext.HTML, content) || '';\n }\n\n createElement(tagName: string): Element {\n return this.renderer.createElement(tagName);\n }\n\n sortElements(a: Element, b: Element): number {\n if ( a === b) return 0;\n\n if ( !a.compareDocumentPosition) {\n // support for IE8 and below\n return (a as any).sourceIndex - (b as any).sourceIndex;\n }\n\n if ( a.compareDocumentPosition(b) & 2) {\n // b comes before a\n return 1;\n }\n\n return -1;\n }\n\n cloneElement(element: Element) {\n return element.cloneNode(true);\n }\n\n getTagName(element: Element) {\n return element.tagName;\n }\n\n getOpeningTag(element: any) {\n // Approach by: https://stackoverflow.com/a/55859966/3099523\n const innerLength = element.innerHTML.length\n const outerLength = element.outerHTML.length;\n \n // Check for self-closing elements\n const openingTagLength = element.outerHTML[outerLength - 2] === '/' ?\n outerLength :\n outerLength - innerLength - element.tagName.length - 3;\n \n return element.outerHTML.slice(0, openingTagLength);\n }\n\n getClosingTag(element: any) {\n return element.outerHTML.slice(element.outerHTML.length - element.tagName.length - 3);\n }\n\n getAttributeNames(element: Node) {\n return typeof (element as any).getAttributeNames === 'function' ? (element as any).getAttributeNames() : [];\n }\n\n getAttribute(element: Element, attributeName: string) {\n return typeof (element as any).getAttribute === 'function' ? (element as any).getAttribute(attributeName) : null;\n }\n\n setAttribute(element: Element, attributeName: string, value: string) {\n this.renderer.setAttribute(element, attributeName, value);\n }\n\n removeAttribute(element: any, attributeName: string) {\n this.renderer.removeAttribute(element, attributeName);\n }\n\n getParentNode(element: Node): Node|null {\n try {\n return this.renderer.parentNode(element);\n } catch (e) {\n return null;\n }\n }\n\n querySelectorAll(parentElement: Document|Element, selector: string): Element[] {\n return Array.from(parentElement.querySelectorAll(selector));\n }\n\n getCh