UNPKG

@ni/spright-components

Version:

NI Spright Components

1,324 lines (1,305 loc) 3.68 MB
(function () { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } function __asyncGenerator(thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i; function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; } function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } } function __asyncDelegator(o) { var i, p; return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; } } function __asyncValues(o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; /** * A reference to globalThis, with support * for browsers that don't yet support the spec. * @public */ const $global = (function () { if (typeof globalThis !== "undefined") { // We're running in a modern environment. return globalThis; } if (typeof global !== "undefined") { // We're running in NodeJS return global; } if (typeof self !== "undefined") { // We're running in a worker. return self; } if (typeof window !== "undefined") { // We're running in the browser's main thread. return window; } try { // Hopefully we never get here... // Not all environments allow eval and Function. Use only as a last resort: // eslint-disable-next-line no-new-func return new Function("return this")(); } catch (_a) { // If all fails, give up and create an object. // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return {}; } })(); // API-only Polyfill for trustedTypes if ($global.trustedTypes === void 0) { $global.trustedTypes = { createPolicy: (n, r) => r }; } const propConfig = { configurable: false, enumerable: false, writable: false, }; if ($global.FAST === void 0) { Reflect.defineProperty($global, "FAST", Object.assign({ value: Object.create(null) }, propConfig)); } /** * The FAST global. * @internal */ const FAST = $global.FAST; if (FAST.getById === void 0) { const storage = Object.create(null); Reflect.defineProperty(FAST, "getById", Object.assign({ value(id, initialize) { let found = storage[id]; if (found === void 0) { found = initialize ? (storage[id] = initialize()) : null; } return found; } }, propConfig)); } /** * A readonly, empty array. * @remarks * Typically returned by APIs that return arrays when there are * no actual items to return. * @internal */ const emptyArray = Object.freeze([]); /** * Creates a function capable of locating metadata associated with a type. * @returns A metadata locator function. * @internal */ function createMetadataLocator() { const metadataLookup = new WeakMap(); return function (target) { let metadata = metadataLookup.get(target); if (metadata === void 0) { let currentTarget = Reflect.getPrototypeOf(target); while (metadata === void 0 && currentTarget !== null) { metadata = metadataLookup.get(currentTarget); currentTarget = Reflect.getPrototypeOf(currentTarget); } metadata = metadata === void 0 ? [] : metadata.slice(0); metadataLookup.set(target, metadata); } return metadata; }; } const updateQueue = $global.FAST.getById(1 /* KernelServiceId.updateQueue */, () => { const tasks = []; const pendingErrors = []; function throwFirstError() { if (pendingErrors.length) { throw pendingErrors.shift(); } } function tryRunTask(task) { try { task.call(); } catch (error) { pendingErrors.push(error); setTimeout(throwFirstError, 0); } } function process() { const capacity = 1024; let index = 0; while (index < tasks.length) { tryRunTask(tasks[index]); index++; // Prevent leaking memory for long chains of recursive calls to `DOM.queueUpdate`. // If we call `DOM.queueUpdate` within a task scheduled by `DOM.queueUpdate`, the queue will // grow, but to avoid an O(n) walk for every task we execute, we don't // shift tasks off the queue after they have been executed. // Instead, we periodically shift 1024 tasks off the queue. if (index > capacity) { // Manually shift all values starting at the index back to the // beginning of the queue. for (let scan = 0, newLength = tasks.length - index; scan < newLength; scan++) { tasks[scan] = tasks[scan + index]; } tasks.length -= index; index = 0; } } tasks.length = 0; } function enqueue(callable) { if (tasks.length < 1) { $global.requestAnimationFrame(process); } tasks.push(callable); } return Object.freeze({ enqueue, process, }); }); /* eslint-disable */ const fastHTMLPolicy = $global.trustedTypes.createPolicy("fast-html", { createHTML: html => html, }); /* eslint-enable */ let htmlPolicy = fastHTMLPolicy; const marker = `fast-${Math.random().toString(36).substring(2, 8)}`; /** @internal */ const _interpolationStart = `${marker}{`; /** @internal */ const _interpolationEnd = `}${marker}`; /** * Common DOM APIs. * @public */ const DOM = Object.freeze({ /** * Indicates whether the DOM supports the adoptedStyleSheets feature. */ supportsAdoptedStyleSheets: Array.isArray(document.adoptedStyleSheets) && "replace" in CSSStyleSheet.prototype, /** * Sets the HTML trusted types policy used by the templating engine. * @param policy - The policy to set for HTML. * @remarks * This API can only be called once, for security reasons. It should be * called by the application developer at the start of their program. */ setHTMLPolicy(policy) { if (htmlPolicy !== fastHTMLPolicy) { throw new Error("The HTML policy can only be set once."); } htmlPolicy = policy; }, /** * Turns a string into trusted HTML using the configured trusted types policy. * @param html - The string to turn into trusted HTML. * @remarks * Used internally by the template engine when creating templates * and setting innerHTML. */ createHTML(html) { return htmlPolicy.createHTML(html); }, /** * Determines if the provided node is a template marker used by the runtime. * @param node - The node to test. */ isMarker(node) { return node && node.nodeType === 8 && node.data.startsWith(marker); }, /** * Given a marker node, extract the {@link HTMLDirective} index from the placeholder. * @param node - The marker node to extract the index from. */ extractDirectiveIndexFromMarker(node) { return parseInt(node.data.replace(`${marker}:`, "")); }, /** * Creates a placeholder string suitable for marking out a location *within* * an attribute value or HTML content. * @param index - The directive index to create the placeholder for. * @remarks * Used internally by binding directives. */ createInterpolationPlaceholder(index) { return `${_interpolationStart}${index}${_interpolationEnd}`; }, /** * Creates a placeholder that manifests itself as an attribute on an * element. * @param attributeName - The name of the custom attribute. * @param index - The directive index to create the placeholder for. * @remarks * Used internally by attribute directives such as `ref`, `slotted`, and `children`. */ createCustomAttributePlaceholder(attributeName, index) { return `${attributeName}="${this.createInterpolationPlaceholder(index)}"`; }, /** * Creates a placeholder that manifests itself as a marker within the DOM structure. * @param index - The directive index to create the placeholder for. * @remarks * Used internally by structural directives such as `repeat`. */ createBlockPlaceholder(index) { return `<!--${marker}:${index}-->`; }, /** * Schedules DOM update work in the next async batch. * @param callable - The callable function or object to queue. */ queueUpdate: updateQueue.enqueue, /** * Immediately processes all work previously scheduled * through queueUpdate. * @remarks * This also forces nextUpdate promises * to resolve. */ processUpdates: updateQueue.process, /** * Resolves with the next DOM update. */ nextUpdate() { return new Promise(updateQueue.enqueue); }, /** * Sets an attribute value on an element. * @param element - The element to set the attribute value on. * @param attributeName - The attribute name to set. * @param value - The value of the attribute to set. * @remarks * If the value is `null` or `undefined`, the attribute is removed, otherwise * it is set to the provided value using the standard `setAttribute` API. */ setAttribute(element, attributeName, value) { if (value === null || value === undefined) { element.removeAttribute(attributeName); } else { element.setAttribute(attributeName, value); } }, /** * Sets a boolean attribute value. * @param element - The element to set the boolean attribute value on. * @param attributeName - The attribute name to set. * @param value - The value of the attribute to set. * @remarks * If the value is true, the attribute is added; otherwise it is removed. */ setBooleanAttribute(element, attributeName, value) { if (value) { element.setAttribute(attributeName, ""); } else { element.removeAttribute(attributeName); } }, /** * Removes all the child nodes of the provided parent node. * @param parent - The node to remove the children from. */ removeChildNodes(parent) { for (let child = parent.firstChild; child !== null; child = parent.firstChild) { parent.removeChild(child); } }, /** * Creates a TreeWalker configured to walk a template fragment. * @param fragment - The fragment to walk. */ createTemplateWalker(fragment) { return document.createTreeWalker(fragment, 133, // element, text, comment null, false); }, }); /** * An implementation of {@link Notifier} that efficiently keeps track of * subscribers interested in a specific change notification on an * observable source. * * @remarks * This set is optimized for the most common scenario of 1 or 2 subscribers. * With this in mind, it can store a subscriber in an internal field, allowing it to avoid Array#push operations. * If the set ever exceeds two subscribers, it upgrades to an array automatically. * @public */ class SubscriberSet { /** * Creates an instance of SubscriberSet for the specified source. * @param source - The object source that subscribers will receive notifications from. * @param initialSubscriber - An initial subscriber to changes. */ constructor(source, initialSubscriber) { this.sub1 = void 0; this.sub2 = void 0; this.spillover = void 0; this.source = source; this.sub1 = initialSubscriber; } /** * Checks whether the provided subscriber has been added to this set. * @param subscriber - The subscriber to test for inclusion in this set. */ has(subscriber) { return this.spillover === void 0 ? this.sub1 === subscriber || this.sub2 === subscriber : this.spillover.indexOf(subscriber) !== -1; } /** * Subscribes to notification of changes in an object's state. * @param subscriber - The object that is subscribing for change notification. */ subscribe(subscriber) { const spillover = this.spillover; if (spillover === void 0) { if (this.has(subscriber)) { return; } if (this.sub1 === void 0) { this.sub1 = subscriber; return; } if (this.sub2 === void 0) { this.sub2 = subscriber; return; } this.spillover = [this.sub1, this.sub2, subscriber]; this.sub1 = void 0; this.sub2 = void 0; } else { const index = spillover.indexOf(subscriber); if (index === -1) { spillover.push(subscriber); } } } /** * Unsubscribes from notification of changes in an object's state. * @param subscriber - The object that is unsubscribing from change notification. */ unsubscribe(subscriber) { const spillover = this.spillover; if (spillover === void 0) { if (this.sub1 === subscriber) { this.sub1 = void 0; } else if (this.sub2 === subscriber) { this.sub2 = void 0; } } else { const index = spillover.indexOf(subscriber); if (index !== -1) { spillover.splice(index, 1); } } } /** * Notifies all subscribers. * @param args - Data passed along to subscribers during notification. */ notify(args) { const spillover = this.spillover; const source = this.source; if (spillover === void 0) { const sub1 = this.sub1; const sub2 = this.sub2; if (sub1 !== void 0) { sub1.handleChange(source, args); } if (sub2 !== void 0) { sub2.handleChange(source, args); } } else { for (let i = 0, ii = spillover.length; i < ii; ++i) { spillover[i].handleChange(source, args); } } } } /** * An implementation of Notifier that allows subscribers to be notified * of individual property changes on an object. * @public */ class PropertyChangeNotifier { /** * Creates an instance of PropertyChangeNotifier for the specified source. * @param source - The object source that subscribers will receive notifications from. */ constructor(source) { this.subscribers = {}; this.sourceSubscribers = null; this.source = source; } /** * Notifies all subscribers, based on the specified property. * @param propertyName - The property name, passed along to subscribers during notification. */ notify(propertyName) { var _a; const subscribers = this.subscribers[propertyName]; if (subscribers !== void 0) { subscribers.notify(propertyName); } (_a = this.sourceSubscribers) === null || _a === void 0 ? void 0 : _a.notify(propertyName); } /** * Subscribes to notification of changes in an object's state. * @param subscriber - The object that is subscribing for change notification. * @param propertyToWatch - The name of the property that the subscriber is interested in watching for changes. */ subscribe(subscriber, propertyToWatch) { var _a; if (propertyToWatch) { let subscribers = this.subscribers[propertyToWatch]; if (subscribers === void 0) { this.subscribers[propertyToWatch] = subscribers = new SubscriberSet(this.source); } subscribers.subscribe(subscriber); } else { this.sourceSubscribers = (_a = this.sourceSubscribers) !== null && _a !== void 0 ? _a : new SubscriberSet(this.source); this.sourceSubscribers.subscribe(subscriber); } } /** * Unsubscribes from notification of changes in an object's state. * @param subscriber - The object that is unsubscribing from change notification. * @param propertyToUnwatch - The name of the property that the subscriber is no longer interested in watching. */ unsubscribe(subscriber, propertyToUnwatch) { var _a; if (propertyToUnwatch) { const subscribers = this.subscribers[propertyToUnwatch]; if (subscribers !== void 0) { subscribers.unsubscribe(subscriber); } } else { (_a = this.sourceSubscribers) === null || _a === void 0 ? void 0 : _a.unsubscribe(subscriber); } } } /** * Common Observable APIs. * @public */ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => { const volatileRegex = /(:|&&|\|\||if)/; const notifierLookup = new WeakMap(); const queueUpdate = DOM.queueUpdate; let watcher = void 0; let createArrayObserver = (array) => { throw new Error("Must call enableArrayObservation before observing arrays."); }; function getNotifier(source) { let found = source.$fastController || notifierLookup.get(source); if (found === void 0) { if (Array.isArray(source)) { found = createArrayObserver(source); } else { notifierLookup.set(source, (found = new PropertyChangeNotifier(source))); } } return found; } const getAccessors = createMetadataLocator(); class DefaultObservableAccessor { constructor(name) { this.name = name; this.field = `_${name}`; this.callback = `${name}Changed`; } getValue(source) { if (watcher !== void 0) { watcher.watch(source, this.name); } return source[this.field]; } setValue(source, newValue) { const field = this.field; const oldValue = source[field]; if (oldValue !== newValue) { source[field] = newValue; const callback = source[this.callback]; if (typeof callback === "function") { callback.call(source, oldValue, newValue); } getNotifier(source).notify(this.name); } } } class BindingObserverImplementation extends SubscriberSet { constructor(binding, initialSubscriber, isVolatileBinding = false) { super(binding, initialSubscriber); this.binding = binding; this.isVolatileBinding = isVolatileBinding; this.needsRefresh = true; this.needsQueue = true; this.first = this; this.last = null; this.propertySource = void 0; this.propertyName = void 0; this.notifier = void 0; this.next = void 0; } observe(source, context) { if (this.needsRefresh && this.last !== null) { this.disconnect(); } const previousWatcher = watcher; watcher = this.needsRefresh ? this : void 0; this.needsRefresh = this.isVolatileBinding; const result = this.binding(source, context); watcher = previousWatcher; return result; } disconnect() { if (this.last !== null) { let current = this.first; while (current !== void 0) { current.notifier.unsubscribe(this, current.propertyName); current = current.next; } this.last = null; this.needsRefresh = this.needsQueue = true; } } watch(propertySource, propertyName) { const prev = this.last; const notifier = getNotifier(propertySource); const current = prev === null ? this.first : {}; current.propertySource = propertySource; current.propertyName = propertyName; current.notifier = notifier; notifier.subscribe(this, propertyName); if (prev !== null) { if (!this.needsRefresh) { // Declaring the variable prior to assignment below circumvents // a bug in Angular's optimization process causing infinite recursion // of this watch() method. Details https://github.com/microsoft/fast/issues/4969 let prevValue; watcher = void 0; /* eslint-disable-next-line */ prevValue = prev.propertySource[prev.propertyName]; /* eslint-disable-next-line @typescript-eslint/no-this-alias */ watcher = this; if (propertySource === prevValue) { this.needsRefresh = true; } } prev.next = current; } this.last = current; } handleChange() { if (this.needsQueue) { this.needsQueue = false; queueUpdate(this); } } call() { if (this.last !== null) { this.needsQueue = true; this.notify(this); } } records() { let next = this.first; return { next: () => { const current = next; if (current === undefined) { return { value: void 0, done: true }; } else { next = next.next; return { value: current, done: false, }; } }, [Symbol.iterator]: function () { return this; }, }; } } return Object.freeze({ /** * @internal * @param factory - The factory used to create array observers. */ setArrayObserverFactory(factory) { createArrayObserver = factory; }, /** * Gets a notifier for an object or Array. * @param source - The object or Array to get the notifier for. */ getNotifier, /** * Records a property change for a source object. * @param source - The object to record the change against. * @param propertyName - The property to track as changed. */ track(source, propertyName) { if (watcher !== void 0) { watcher.watch(source, propertyName); } }, /** * Notifies watchers that the currently executing property getter or function is volatile * with respect to its observable dependencies. */ trackVolatile() { if (watcher !== void 0) { watcher.needsRefresh = true; } }, /** * Notifies subscribers of a source object of changes. * @param source - the object to notify of changes. * @param args - The change args to pass to subscribers. */ notify(source, args) { getNotifier(source).notify(args); }, /** * Defines an observable property on an object or prototype. * @param target - The target object to define the observable on. * @param nameOrAccessor - The name of the property to define as observable; * or a custom accessor that specifies the property name and accessor implementation. */ defineProperty(target, nameOrAccessor) { if (typeof nameOrAccessor === "string") { nameOrAccessor = new DefaultObservableAccessor(nameOrAccessor); } getAccessors(target).push(nameOrAccessor); Reflect.defineProperty(target, nameOrAccessor.name, { enumerable: true, get: function () { return nameOrAccessor.getValue(this); }, set: function (newValue) { nameOrAccessor.setValue(this, newValue); }, }); }, /** * Finds all the observable accessors defined on the target, * including its prototype chain. * @param target - The target object to search for accessor on. */ getAccessors, /** * Creates a {@link BindingObserver} that can watch the * provided {@link Binding} for changes. * @param binding - The binding to observe. * @param initialSubscriber - An initial subscriber to changes in the binding value. * @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation. */ binding(binding, initialSubscriber, isVolatileBinding = this.isVolatileBinding(binding)) { return new BindingObserverImplementation(binding, initialSubscriber, isVolatileBinding); }, /** * Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated * on every evaluation of the value. * @param binding - The binding to inspect. */ isVolatileBinding(binding) { return volatileRegex.test(binding.toString()); }, }); }); /** * Decorator: Defines an observable property on the target. * @param target - The target to define the observable on. * @param nameOrAccessor - The property name or accessor to define the observable as. * @public */ function observable(target, nameOrAccessor) { Observable.defineProperty(target, nameOrAccessor); } /** * Decorator: Marks a property getter as having volatile observable dependencies. * @param target - The target that the property is defined on. * @param name - The property name. * @param name - The existing descriptor. * @public */ function volatile(target, name, descriptor) { return Object.assign({}, descriptor, { get: function () { Observable.trackVolatile(); return descriptor.get.apply(this); }, }); } const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => { let current = null; return { get() { return current; }, set(event) { current = event; }, }; }); /** * Provides additional contextual information available to behaviors and expressions. * @public */ class ExecutionContext { constructor() { /** * The index of the current item within a repeat context. */ this.index = 0; /** * The length of the current collection within a repeat context. */ this.length = 0; /** * The parent data object within a repeat context. */ this.parent = null; /** * The parent execution context when in nested context scenarios. */ this.parentContext = null; } /** * The current event within an event handler. */ get event() { return contextEvent.get(); } /** * Indicates whether the current item within a repeat context * has an even index. */ get isEven() { return this.index % 2 === 0; } /** * Indicates whether the current item within a repeat context * has an odd index. */ get isOdd() { return this.index % 2 !== 0; } /** * Indicates whether the current item within a repeat context * is the first item in the collection. */ get isFirst() { return this.index === 0; } /** * Indicates whether the current item within a repeat context * is somewhere in the middle of the collection. */ get isInMiddle() { return !this.isFirst && !this.isLast; } /** * Indicates whether the current item within a repeat context * is the last item in the collection. */ get isLast() { return this.index === this.length - 1; } /** * Sets the event for the current execution context. * @param event - The event to set. * @internal */ static setEvent(event) { contextEvent.set(event); } } Observable.defineProperty(ExecutionContext.prototype, "index"); Observable.defineProperty(ExecutionContext.prototype, "length"); /** * The default execution context used in binding expressions. * @public */ const defaultExecutionContext = Object.seal(new ExecutionContext()); /** * Instructs the template engine to apply behavior to a node. * @public */ class HTMLDirective { constructor() { /** * The index of the DOM node to which the created behavior will apply. */ this.targetIndex = 0; } } /** * A {@link HTMLDirective} that targets a named attribute or property on a node. * @public */ class TargetedHTMLDirective extends HTMLDirective { constructor() { super(...arguments); /** * Creates a placeholder string based on the directive's index within the template. * @param index - The index of the directive within the template. */ this.createPlaceholder = DOM.createInterpolationPlaceholder; } } /** * A directive that attaches special behavior to an element via a custom attribute. * @public */ class AttachedBehaviorHTMLDirective extends HTMLDirective { /** * * @param name - The name of the behavior; used as a custom attribute on the element. * @param behavior - The behavior to instantiate and attach to the element. * @param options - Options to pass to the behavior during creation. */ constructor(name, behavior, options) { super(); this.name = name; this.behavior = behavior; this.options = options; } /** * Creates a placeholder string based on the directive's index within the template. * @param index - The index of the directive within the template. * @remarks * Creates a custom attribute placeholder. */ createPlaceholder(index) { return DOM.createCustomAttributePlaceholder(this.name, index); } /** * Creates a behavior for the provided target node. * @param target - The node instance to create the behavior for. * @remarks * Creates an instance of the `behavior` type this directive was constructed with * and passes the target and options to that `behavior`'s constructor. */ createBehavior(target) { return new this.behavior(target, this.options); } } function normalBind(source, context) { this.source = source; this.context = context; if (this.bindingObserver === null) { this.bindingObserver = Observable.binding(this.binding, this, this.isBindingVolatile); } this.updateTarget(this.bindingObserver.observe(source, context)); } function triggerBind(source, context) { this.source = source; this.context = context; this.target.addEventListener(this.targetName, this); } function normalUnbind() { this.bindingObserver.disconnect(); this.source = null; this.context = null; } function contentUnbind() { this.bindingObserver.disconnect(); this.source = null; this.context = null; const view = this.target.$fastView; if (view !== void 0 && view.isComposed) { view.unbind(); view.needsBindOnly = true; } } function triggerUnbind() { this.target.removeEventListener(this.targetName, this); this.source = null; this.context = null; } function updateAttributeTarget(value) { DOM.setAttribute(this.target, this.targetName, value); } function updateBooleanAttributeTarget(value) { DOM.setBooleanAttribute(this.target, this.targetName, value); } function updateContentTarget(value) { // If there's no actual value, then this equates to the // empty string for the purposes of content bindings. if (value === null || value === undefined) { value = ""; } // If the value has a "create" method, then it's a template-like. if (value.create) { this.target.textContent = ""; let view = this.target.$fastView; // If there's no previous view that we might be able to // reuse then create a new view from the template. if (view === void 0) { view = value.create(); } else { // If there is a previous view, but it wasn't created // from the same template as the new value, then we // need to remove the old view if it's still in the DOM // and create a new view from the template. if (this.target.$fastTemplate !== value) { if (view.isComposed) { view.remove(); view.unbind(); } view = value.create(); } } // It's possible that the value is the same as the previous template // and that there's actually no need to compose it. if (!view.isComposed) { view.isComposed = true; view.bind(this.source, this.context); view.insertBefore(this.target); this.target.$fastView = view; this.target.$fastTemplate = value; } else if (view.needsBindOnly) { view.needsBindOnly = false; view.bind(this.source, this.context); } } else { const view = this.target.$fastView; // If there is a view and it's currently composed into // the DOM, then we need to remove it. if (view !== void 0 && view.isComposed) { view.isComposed = false; view.remove(); if (view.needsBindOnly) { view.needsBindOnly = false; } else { view.unbind(); } } this.target.textContent = value; } } function updatePropertyTarget(value) { this.target[this.targetName] = value; } function updateClassTarget(value) { const classVersions = this.classVersions || Object.create(null); const target = this.target; let version = this.version || 0; // Add the classes, tracking the version at which they were added. if (value !== null && value !== undefined && value.length) { const names = value.split(/\s+/); for (let i = 0, ii = names.length; i < ii; ++i) { const currentName = names[i]; if (currentName === "") { continue; } classVersions[currentName] = version; target.classList.add(currentName); } } this.classVersions = classVersions; this.version = version + 1; // If this is the first call to add classes, there's no need to remove old ones. if (version === 0) { return; } // Remove classes from the previous version. version -= 1; for (const name in classVersions) { if (classVersions[name] === version) { target.classList.remove(name); } } } /** * A directive that configures data binding to element content and attributes. * @public */ class HTMLBindingDirective extends TargetedHTMLDirective { /** * Creates an instance of BindingDirective. * @param binding - A binding that returns the data used to update the DOM. */ constructor(binding) { super(); this.binding = binding; this.bind = normalBind; this.unbind = normalUnbind; this.updateTarget = updateAttributeTarget; this.isBindingVolatile = Observable.isVolatileBinding(this.binding); } /** * Gets/sets the name of the attribute or property that this * binding is targeting. */ get targetName() { return this.originalTargetName; } set targetName(value) { this.originalTargetName = value; if (value === void 0) { return; } switch (value[0]) { case ":": this.cleanedTargetName = value.substr(1); this.updateTarget = updatePropertyTarget; if (this.cleanedTargetName === "innerHTML") { const binding = this.binding; this.binding = (s, c) => DOM.createHTML(binding(s, c)); } break; case "?": this.cleanedTargetName = value.substr(1); this.updateTarget = updateBooleanAttributeTarget; break; case "@": this.cleanedTargetName = value.substr(1); this.bind = triggerBind; this.unbind = triggerUnbind; break; default: this.cleanedTargetName = value; if (value === "class") { this.updateTarget = updateClassTarget; } break; } } /** * Makes this binding target the content of an element rather than * a particular attribute or property. */ targetAtContent() { this.updateTarget = updateContentTarget; this.unbind = contentUnbind; } /** * Creates the runtime BindingBehavior instance based on the configuration * information stored in the BindingDirective. * @param target - The target node that the binding behavior should attach to. */ createBehavior(target) { /* eslint-disable-next-line @typescript-eslint/no-use-before-define */ return new BindingBehavior(target, this.binding, this.isBindingVolatile, this.bind, this.unbind, this.updateTarget, this.cleanedTargetName); } } /** * A behavior that updates content and attributes based on a configured * BindingDirective. * @public */ class BindingBehavior { /** * Creates an instance of BindingBehavior. * @param target - The target of the data updates. * @param binding - The binding that returns the latest value for an update. * @param isBindingVolatile - Indicates whether the binding has volatile dependencies. * @param bind - The operation to perform during binding. * @param unbind - The operation to perform during unbinding. * @param updateTarget - The operation to perform when updating. * @param targetName - The name of the target attribute or property to update. */ constructor(target, binding, isBindingVolatile, bind, unbind, updateTarget, targetName) { /** @internal */ this.source = null; /** @internal */ this.context = null; /** @internal */ this.bindingObserver = null; this.target = target; this.binding = binding; this.isBindingVolatile = isBindingVolatile; this.bind = bind; this.unbind = unbind; this.updateTarget = updateTarget; this.targetName = targetName; } /** @internal */ handleChange() { this.updateTarget(this.bindingObserver.observe(this.source, this.context)); } /** @internal */ handleEvent(event) { ExecutionContext.setEvent(event); const result = this.binding(this.source, this.context); ExecutionContext.setEvent(null); if (result !== true) { event.preventDefault(); } } } let sharedContext = null; class CompilationContext { addFactory(factory) { factory.targetIndex = this.targetIndex; this.behaviorFactories.push(factory); } captureContentBinding(directive) { directive.targetAtContent(); this.addFactory(directive); } reset() { this.behaviorFactories = []; this.targetIndex = -1; } release() { /* eslint-disable-next-line @typescript-eslint/no-this-alias */ sharedContext = this; } static borrow(directives) { const shareable = sharedContext || new CompilationContext(); shareable.directives = directives; shareable.reset(); sharedContext = null; return shareable; } } function createAggregateBinding(parts) { if (parts.length === 1) { return parts[0]; } let targetName; const partCount = parts.length; const finalParts = parts.map((x) => { if (typeof x === "string") { return () => x; } targetName = x.targetName || targetName; return x.binding; }); const binding = (scope, context) => { let output = ""; for (let i = 0; i < partCount; ++i) { output += finalParts[i](scope, context); } return output; }; const directive = new HTMLBindingDirective(binding); directive.targetName = targetName; return directive; } const interpolationEndLength = _interpolationEnd.length; function parseContent(context, value) { const valueParts = value.split(_interpolationStart); if (valueParts.length === 1) { return null; } const bindingParts = []; for (let i = 0, ii = valueParts.length; i < ii; ++i) { const current = valueParts[i]; const index = current.indexOf(_interpolationEnd); let literal; if (index === -1) { literal = current; } else { const directiveIndex = parseInt(current.substring(0, index)); bindingParts.push(context.directives[directiveIndex]); literal = current.substring(index + interpolationEndLength); } if (literal !== "") { bindingParts.push(literal); } } return bindingParts; } function compileAttributes(context, node, includeBasicValues = false) { const attributes = node.attributes; for (let i = 0, ii = attributes.length; i < ii; ++i) { const attr = attributes[i]; const attrValue = attr.value; const parseResult = parseContent(context, attrValue); let result = null; if (parseResult === null) { if (includeBasicValues) { result = new HTMLBindingDirective(() => attrValue); result.targetName = attr.name; } } else { result = createAggregateBinding(parseResult); } if (result !== null) { node.removeAttributeNode(attr); i--; ii--; context.addFactory(result); } } } function compileContent(context, node, walker) { const parseResult = parseContent(context, node.textConten