Buckets:
| /** | |
| * @license | |
| * Copyright 2017 Google LLC | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| /** | |
| * Use this module if you want to create your own base class extending | |
| * {@link ReactiveElement}. | |
| * @packageDocumentation | |
| */ | |
| import { getCompatibleStyle, adoptStyles, } from './css-tag.js'; | |
| // In the Node build, this import will be injected by Rollup: | |
| // import {HTMLElement, customElements} from '@lit-labs/ssr-dom-shim'; | |
| export * from './css-tag.js'; | |
| // TODO (justinfagnani): Add `hasOwn` here when we ship ES2022 | |
| const { is, defineProperty, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, getPrototypeOf, } = Object; | |
| const NODE_MODE = false; | |
| // Lets a minifier replace globalThis references with a minified name | |
| const global = globalThis; | |
| if (NODE_MODE) { | |
| global.customElements ??= customElements; | |
| } | |
| const DEV_MODE = true; | |
| let issueWarning; | |
| const trustedTypes = global | |
| .trustedTypes; | |
| // Temporary workaround for https://crbug.com/993268 | |
| // Currently, any attribute starting with "on" is considered to be a | |
| // TrustedScript source. Such boolean attributes must be set to the equivalent | |
| // trusted emptyScript value. | |
| const emptyStringForBooleanAttribute = trustedTypes | |
| ? trustedTypes.emptyScript | |
| : ''; | |
| const polyfillSupport = DEV_MODE | |
| ? global.reactiveElementPolyfillSupportDevMode | |
| : global.reactiveElementPolyfillSupport; | |
| if (DEV_MODE) { | |
| // Ensure warnings are issued only 1x, even if multiple versions of Lit | |
| // are loaded. | |
| global.litIssuedWarnings ??= new Set(); | |
| /** | |
| * Issue a warning if we haven't already, based either on `code` or `warning`. | |
| * Warnings are disabled automatically only by `warning`; disabling via `code` | |
| * can be done by users. | |
| */ | |
| issueWarning = (code, warning) => { | |
| warning += ` See https://lit.dev/msg/${code} for more information.`; | |
| if (!global.litIssuedWarnings.has(warning) && | |
| !global.litIssuedWarnings.has(code)) { | |
| console.warn(warning); | |
| global.litIssuedWarnings.add(warning); | |
| } | |
| }; | |
| queueMicrotask(() => { | |
| issueWarning('dev-mode', `Lit is in dev mode. Not recommended for production!`); | |
| // Issue polyfill support warning. | |
| if (global.ShadyDOM?.inUse && polyfillSupport === undefined) { | |
| issueWarning('polyfill-support-missing', `Shadow DOM is being polyfilled via \`ShadyDOM\` but ` + | |
| `the \`polyfill-support\` module has not been loaded.`); | |
| } | |
| }); | |
| } | |
| /** | |
| * Useful for visualizing and logging insights into what the Lit template system is doing. | |
| * | |
| * Compiled out of prod mode builds. | |
| */ | |
| const debugLogEvent = DEV_MODE | |
| ? (event) => { | |
| const shouldEmit = global | |
| .emitLitDebugLogEvents; | |
| if (!shouldEmit) { | |
| return; | |
| } | |
| global.dispatchEvent(new CustomEvent('lit-debug', { | |
| detail: event, | |
| })); | |
| } | |
| : undefined; | |
| /* | |
| * When using Closure Compiler, JSCompiler_renameProperty(property, object) is | |
| * replaced at compile time by the munged name for object[property]. We cannot | |
| * alias this function, so we have to use a small shim that has the same | |
| * behavior when not compiling. | |
| */ | |
| /*@__INLINE__*/ | |
| const JSCompiler_renameProperty = (prop, _obj) => prop; | |
| export const defaultConverter = { | |
| toAttribute(value, type) { | |
| switch (type) { | |
| case Boolean: | |
| value = value ? emptyStringForBooleanAttribute : null; | |
| break; | |
| case Object: | |
| case Array: | |
| // if the value is `null` or `undefined` pass this through | |
| // to allow removing/no change behavior. | |
| value = value == null ? value : JSON.stringify(value); | |
| break; | |
| } | |
| return value; | |
| }, | |
| fromAttribute(value, type) { | |
| let fromValue = value; | |
| switch (type) { | |
| case Boolean: | |
| fromValue = value !== null; | |
| break; | |
| case Number: | |
| fromValue = value === null ? null : Number(value); | |
| break; | |
| case Object: | |
| case Array: | |
| // Do *not* generate exception when invalid JSON is set as elements | |
| // don't normally complain on being mis-configured. | |
| // TODO(sorvell): Do generate exception in *dev mode*. | |
| try { | |
| // Assert to adhere to Bazel's "must type assert JSON parse" rule. | |
| fromValue = JSON.parse(value); | |
| } | |
| catch (e) { | |
| fromValue = null; | |
| } | |
| break; | |
| } | |
| return fromValue; | |
| }, | |
| }; | |
| /** | |
| * Change function that returns true if `value` is different from `oldValue`. | |
| * This method is used as the default for a property's `hasChanged` function. | |
| */ | |
| export const notEqual = (value, old) => !is(value, old); | |
| const defaultPropertyDeclaration = { | |
| attribute: true, | |
| type: String, | |
| converter: defaultConverter, | |
| reflect: false, | |
| useDefault: false, | |
| hasChanged: notEqual, | |
| }; | |
| // Ensure metadata is enabled. TypeScript does not polyfill | |
| // Symbol.metadata, so we must ensure that it exists. | |
| Symbol.metadata ??= Symbol('metadata'); | |
| // Map from a class's metadata object to property options | |
| // Note that we must use nullish-coalescing assignment so that we only use one | |
| // map even if we load multiple version of this module. | |
| global.litPropertyMetadata ??= new WeakMap(); | |
| /** | |
| * Base element class which manages element properties and attributes. When | |
| * properties change, the `update` method is asynchronously called. This method | |
| * should be supplied by subclasses to render updates as desired. | |
| * @noInheritDoc | |
| */ | |
| export class ReactiveElement | |
| // In the Node build, this `extends` clause will be substituted with | |
| // `(globalThis.HTMLElement ?? HTMLElement)`. | |
| // | |
| // This way, we will first prefer any global `HTMLElement` polyfill that the | |
| // user has assigned, and then fall back to the `HTMLElement` shim which has | |
| // been imported (see note at the top of this file about how this import is | |
| // generated by Rollup). Note that the `HTMLElement` variable has been | |
| // shadowed by this import, so it no longer refers to the global. | |
| extends HTMLElement { | |
| /** | |
| * Adds an initializer function to the class that is called during instance | |
| * construction. | |
| * | |
| * This is useful for code that runs against a `ReactiveElement` | |
| * subclass, such as a decorator, that needs to do work for each | |
| * instance, such as setting up a `ReactiveController`. | |
| * | |
| * ```ts | |
| * const myDecorator = (target: typeof ReactiveElement, key: string) => { | |
| * target.addInitializer((instance: ReactiveElement) => { | |
| * // This is run during construction of the element | |
| * new MyController(instance); | |
| * }); | |
| * } | |
| * ``` | |
| * | |
| * Decorating a field will then cause each instance to run an initializer | |
| * that adds a controller: | |
| * | |
| * ```ts | |
| * class MyElement extends LitElement { | |
| * @myDecorator foo; | |
| * } | |
| * ``` | |
| * | |
| * Initializers are stored per-constructor. Adding an initializer to a | |
| * subclass does not add it to a superclass. Since initializers are run in | |
| * constructors, initializers will run in order of the class hierarchy, | |
| * starting with superclasses and progressing to the instance's class. | |
| * | |
| * @nocollapse | |
| */ | |
| static addInitializer(initializer) { | |
| this.__prepare(); | |
| (this._initializers ??= []).push(initializer); | |
| } | |
| /** | |
| * Returns a list of attributes corresponding to the registered properties. | |
| * @nocollapse | |
| * @category attributes | |
| */ | |
| static get observedAttributes() { | |
| // Ensure we've created all properties | |
| this.finalize(); | |
| // this.__attributeToPropertyMap is only undefined after finalize() in | |
| // ReactiveElement itself. ReactiveElement.observedAttributes is only | |
| // accessed with ReactiveElement as the receiver when a subclass or mixin | |
| // calls super.observedAttributes | |
| return (this.__attributeToPropertyMap && [...this.__attributeToPropertyMap.keys()]); | |
| } | |
| /** | |
| * Creates a property accessor on the element prototype if one does not exist | |
| * and stores a {@linkcode PropertyDeclaration} for the property with the | |
| * given options. The property setter calls the property's `hasChanged` | |
| * property option or uses a strict identity check to determine whether or not | |
| * to request an update. | |
| * | |
| * This method may be overridden to customize properties; however, | |
| * when doing so, it's important to call `super.createProperty` to ensure | |
| * the property is setup correctly. This method calls | |
| * `getPropertyDescriptor` internally to get a descriptor to install. | |
| * To customize what properties do when they are get or set, override | |
| * `getPropertyDescriptor`. To customize the options for a property, | |
| * implement `createProperty` like this: | |
| * | |
| * ```ts | |
| * static createProperty(name, options) { | |
| * options = Object.assign(options, {myOption: true}); | |
| * super.createProperty(name, options); | |
| * } | |
| * ``` | |
| * | |
| * @nocollapse | |
| * @category properties | |
| */ | |
| static createProperty(name, options = defaultPropertyDeclaration) { | |
| // If this is a state property, force the attribute to false. | |
| if (options.state) { | |
| options.attribute = false; | |
| } | |
| this.__prepare(); | |
| // Whether this property is wrapping accessors. | |
| // Helps control the initial value change and reflection logic. | |
| if (this.prototype.hasOwnProperty(name)) { | |
| options = Object.create(options); | |
| options.wrapped = true; | |
| } | |
| this.elementProperties.set(name, options); | |
| if (!options.noAccessor) { | |
| const key = DEV_MODE | |
| ? // Use Symbol.for in dev mode to make it easier to maintain state | |
| // when doing HMR. | |
| Symbol.for(`${String(name)} (@property() cache)`) | |
| : Symbol(); | |
| const descriptor = this.getPropertyDescriptor(name, key, options); | |
| if (descriptor !== undefined) { | |
| defineProperty(this.prototype, name, descriptor); | |
| } | |
| } | |
| } | |
| /** | |
| * Returns a property descriptor to be defined on the given named property. | |
| * If no descriptor is returned, the property will not become an accessor. | |
| * For example, | |
| * | |
| * ```ts | |
| * class MyElement extends LitElement { | |
| * static getPropertyDescriptor(name, key, options) { | |
| * const defaultDescriptor = | |
| * super.getPropertyDescriptor(name, key, options); | |
| * const setter = defaultDescriptor.set; | |
| * return { | |
| * get: defaultDescriptor.get, | |
| * set(value) { | |
| * setter.call(this, value); | |
| * // custom action. | |
| * }, | |
| * configurable: true, | |
| * enumerable: true | |
| * } | |
| * } | |
| * } | |
| * ``` | |
| * | |
| * @nocollapse | |
| * @category properties | |
| */ | |
| static getPropertyDescriptor(name, key, options) { | |
| const { get, set } = getOwnPropertyDescriptor(this.prototype, name) ?? { | |
| get() { | |
| return this[key]; | |
| }, | |
| set(v) { | |
| this[key] = v; | |
| }, | |
| }; | |
| if (DEV_MODE && get == null) { | |
| if ('value' in (getOwnPropertyDescriptor(this.prototype, name) ?? {})) { | |
| throw new Error(`Field ${JSON.stringify(String(name))} on ` + | |
| `${this.name} was declared as a reactive property ` + | |
| `but it's actually declared as a value on the prototype. ` + | |
| `Usually this is due to using @property or @state on a method.`); | |
| } | |
| issueWarning('reactive-property-without-getter', `Field ${JSON.stringify(String(name))} on ` + | |
| `${this.name} was declared as a reactive property ` + | |
| `but it does not have a getter. This will be an error in a ` + | |
| `future version of Lit.`); | |
| } | |
| return { | |
| get, | |
| set(value) { | |
| const oldValue = get?.call(this); | |
| set?.call(this, value); | |
| this.requestUpdate(name, oldValue, options); | |
| }, | |
| configurable: true, | |
| enumerable: true, | |
| }; | |
| } | |
| /** | |
| * Returns the property options associated with the given property. | |
| * These options are defined with a `PropertyDeclaration` via the `properties` | |
| * object or the `@property` decorator and are registered in | |
| * `createProperty(...)`. | |
| * | |
| * Note, this method should be considered "final" and not overridden. To | |
| * customize the options for a given property, override | |
| * {@linkcode createProperty}. | |
| * | |
| * @nocollapse | |
| * @final | |
| * @category properties | |
| */ | |
| static getPropertyOptions(name) { | |
| return this.elementProperties.get(name) ?? defaultPropertyDeclaration; | |
| } | |
| /** | |
| * Initializes static own properties of the class used in bookkeeping | |
| * for element properties, initializers, etc. | |
| * | |
| * Can be called multiple times by code that needs to ensure these | |
| * properties exist before using them. | |
| * | |
| * This method ensures the superclass is finalized so that inherited | |
| * property metadata can be copied down. | |
| * @nocollapse | |
| */ | |
| static __prepare() { | |
| if (this.hasOwnProperty(JSCompiler_renameProperty('elementProperties', this))) { | |
| // Already prepared | |
| return; | |
| } | |
| // Finalize any superclasses | |
| const superCtor = getPrototypeOf(this); | |
| superCtor.finalize(); | |
| // Create own set of initializers for this class if any exist on the | |
| // superclass and copy them down. Note, for a small perf boost, avoid | |
| // creating initializers unless needed. | |
| if (superCtor._initializers !== undefined) { | |
| this._initializers = [...superCtor._initializers]; | |
| } | |
| // Initialize elementProperties from the superclass | |
| this.elementProperties = new Map(superCtor.elementProperties); | |
| } | |
| /** | |
| * Finishes setting up the class so that it's ready to be registered | |
| * as a custom element and instantiated. | |
| * | |
| * This method is called by the ReactiveElement.observedAttributes getter. | |
| * If you override the observedAttributes getter, you must either call | |
| * super.observedAttributes to trigger finalization, or call finalize() | |
| * yourself. | |
| * | |
| * @nocollapse | |
| */ | |
| static finalize() { | |
| if (this.hasOwnProperty(JSCompiler_renameProperty('finalized', this))) { | |
| return; | |
| } | |
| this.finalized = true; | |
| this.__prepare(); | |
| // Create properties from the static properties block: | |
| if (this.hasOwnProperty(JSCompiler_renameProperty('properties', this))) { | |
| const props = this.properties; | |
| const propKeys = [ | |
| ...getOwnPropertyNames(props), | |
| ...getOwnPropertySymbols(props), | |
| ]; | |
| for (const p of propKeys) { | |
| this.createProperty(p, props[p]); | |
| } | |
| } | |
| // Create properties from standard decorator metadata: | |
| const metadata = this[Symbol.metadata]; | |
| if (metadata !== null) { | |
| const properties = litPropertyMetadata.get(metadata); | |
| if (properties !== undefined) { | |
| for (const [p, options] of properties) { | |
| this.elementProperties.set(p, options); | |
| } | |
| } | |
| } | |
| // Create the attribute-to-property map | |
| this.__attributeToPropertyMap = new Map(); | |
| for (const [p, options] of this.elementProperties) { | |
| const attr = this.__attributeNameForProperty(p, options); | |
| if (attr !== undefined) { | |
| this.__attributeToPropertyMap.set(attr, p); | |
| } | |
| } | |
| this.elementStyles = this.finalizeStyles(this.styles); | |
| if (DEV_MODE) { | |
| if (this.hasOwnProperty('createProperty')) { | |
| issueWarning('no-override-create-property', 'Overriding ReactiveElement.createProperty() is deprecated. ' + | |
| 'The override will not be called with standard decorators'); | |
| } | |
| if (this.hasOwnProperty('getPropertyDescriptor')) { | |
| issueWarning('no-override-get-property-descriptor', 'Overriding ReactiveElement.getPropertyDescriptor() is deprecated. ' + | |
| 'The override will not be called with standard decorators'); | |
| } | |
| } | |
| } | |
| /** | |
| * Takes the styles the user supplied via the `static styles` property and | |
| * returns the array of styles to apply to the element. | |
| * Override this method to integrate into a style management system. | |
| * | |
| * Styles are deduplicated preserving the _last_ instance in the list. This | |
| * is a performance optimization to avoid duplicated styles that can occur | |
| * especially when composing via subclassing. The last item is kept to try | |
| * to preserve the cascade order with the assumption that it's most important | |
| * that last added styles override previous styles. | |
| * | |
| * @nocollapse | |
| * @category styles | |
| */ | |
| static finalizeStyles(styles) { | |
| const elementStyles = []; | |
| if (Array.isArray(styles)) { | |
| // Dedupe the flattened array in reverse order to preserve the last items. | |
| // Casting to Array<unknown> works around TS error that | |
| // appears to come from trying to flatten a type CSSResultArray. | |
| const set = new Set(styles.flat(Infinity).reverse()); | |
| // Then preserve original order by adding the set items in reverse order. | |
| for (const s of set) { | |
| elementStyles.unshift(getCompatibleStyle(s)); | |
| } | |
| } | |
| else if (styles !== undefined) { | |
| elementStyles.push(getCompatibleStyle(styles)); | |
| } | |
| return elementStyles; | |
| } | |
| /** | |
| * Returns the property name for the given attribute `name`. | |
| * @nocollapse | |
| */ | |
| static __attributeNameForProperty(name, options) { | |
| const attribute = options.attribute; | |
| return attribute === false | |
| ? undefined | |
| : typeof attribute === 'string' | |
| ? attribute | |
| : typeof name === 'string' | |
| ? name.toLowerCase() | |
| : undefined; | |
| } | |
| constructor() { | |
| super(); | |
| this.__instanceProperties = undefined; | |
| /** | |
| * True if there is a pending update as a result of calling `requestUpdate()`. | |
| * Should only be read. | |
| * @category updates | |
| */ | |
| this.isUpdatePending = false; | |
| /** | |
| * Is set to `true` after the first update. The element code cannot assume | |
| * that `renderRoot` exists before the element `hasUpdated`. | |
| * @category updates | |
| */ | |
| this.hasUpdated = false; | |
| /** | |
| * Name of currently reflecting property | |
| */ | |
| this.__reflectingProperty = null; | |
| this.__initialize(); | |
| } | |
| /** | |
| * Internal only override point for customizing work done when elements | |
| * are constructed. | |
| */ | |
| __initialize() { | |
| this.__updatePromise = new Promise((res) => (this.enableUpdating = res)); | |
| this._$changedProperties = new Map(); | |
| // This enqueues a microtask that must run before the first update, so it | |
| // must be called before requestUpdate() | |
| this.__saveInstanceProperties(); | |
| // ensures first update will be caught by an early access of | |
| // `updateComplete` | |
| this.requestUpdate(); | |
| this.constructor._initializers?.forEach((i) => i(this)); | |
| } | |
| /** | |
| * Registers a `ReactiveController` to participate in the element's reactive | |
| * update cycle. The element automatically calls into any registered | |
| * controllers during its lifecycle callbacks. | |
| * | |
| * If the element is connected when `addController()` is called, the | |
| * controller's `hostConnected()` callback will be immediately called. | |
| * @category controllers | |
| */ | |
| addController(controller) { | |
| (this.__controllers ??= new Set()).add(controller); | |
| // If a controller is added after the element has been connected, | |
| // call hostConnected. Note, re-using existence of `renderRoot` here | |
| // (which is set in connectedCallback) to avoid the need to track a | |
| // first connected state. | |
| if (this.renderRoot !== undefined && this.isConnected) { | |
| controller.hostConnected?.(); | |
| } | |
| } | |
| /** | |
| * Removes a `ReactiveController` from the element. | |
| * @category controllers | |
| */ | |
| removeController(controller) { | |
| this.__controllers?.delete(controller); | |
| } | |
| /** | |
| * Fixes any properties set on the instance before upgrade time. | |
| * Otherwise these would shadow the accessor and break these properties. | |
| * The properties are stored in a Map which is played back after the | |
| * constructor runs. | |
| */ | |
| __saveInstanceProperties() { | |
| const instanceProperties = new Map(); | |
| const elementProperties = this.constructor | |
| .elementProperties; | |
| for (const p of elementProperties.keys()) { | |
| if (this.hasOwnProperty(p)) { | |
| instanceProperties.set(p, this[p]); | |
| delete this[p]; | |
| } | |
| } | |
| if (instanceProperties.size > 0) { | |
| this.__instanceProperties = instanceProperties; | |
| } | |
| } | |
| /** | |
| * Returns the node into which the element should render and by default | |
| * creates and returns an open shadowRoot. Implement to customize where the | |
| * element's DOM is rendered. For example, to render into the element's | |
| * childNodes, return `this`. | |
| * | |
| * @return Returns a node into which to render. | |
| * @category rendering | |
| */ | |
| createRenderRoot() { | |
| const renderRoot = this.shadowRoot ?? | |
| this.attachShadow(this.constructor.shadowRootOptions); | |
| adoptStyles(renderRoot, this.constructor.elementStyles); | |
| return renderRoot; | |
| } | |
| /** | |
| * On first connection, creates the element's renderRoot, sets up | |
| * element styling, and enables updating. | |
| * @category lifecycle | |
| */ | |
| connectedCallback() { | |
| // Create renderRoot before controllers `hostConnected` | |
| this.renderRoot ??= | |
| this.createRenderRoot(); | |
| this.enableUpdating(true); | |
| this.__controllers?.forEach((c) => c.hostConnected?.()); | |
| } | |
| /** | |
| * Note, this method should be considered final and not overridden. It is | |
| * overridden on the element instance with a function that triggers the first | |
| * update. | |
| * @category updates | |
| */ | |
| enableUpdating(_requestedUpdate) { } | |
| /** | |
| * Allows for `super.disconnectedCallback()` in extensions while | |
| * reserving the possibility of making non-breaking feature additions | |
| * when disconnecting at some point in the future. | |
| * @category lifecycle | |
| */ | |
| disconnectedCallback() { | |
| this.__controllers?.forEach((c) => c.hostDisconnected?.()); | |
| } | |
| /** | |
| * Synchronizes property values when attributes change. | |
| * | |
| * Specifically, when an attribute is set, the corresponding property is set. | |
| * You should rarely need to implement this callback. If this method is | |
| * overridden, `super.attributeChangedCallback(name, _old, value)` must be | |
| * called. | |
| * | |
| * See [responding to attribute changes](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes) | |
| * on MDN for more information about the `attributeChangedCallback`. | |
| * @category attributes | |
| */ | |
| attributeChangedCallback(name, _old, value) { | |
| this._$attributeToProperty(name, value); | |
| } | |
| __propertyToAttribute(name, value) { | |
| const elemProperties = this.constructor.elementProperties; | |
| const options = elemProperties.get(name); | |
| const attr = this.constructor.__attributeNameForProperty(name, options); | |
| if (attr !== undefined && options.reflect === true) { | |
| const converter = options.converter?.toAttribute !== | |
| undefined | |
| ? options.converter | |
| : defaultConverter; | |
| const attrValue = converter.toAttribute(value, options.type); | |
| if (DEV_MODE && | |
| this.constructor.enabledWarnings.includes('migration') && | |
| attrValue === undefined) { | |
| issueWarning('undefined-attribute-value', `The attribute value for the ${name} property is ` + | |
| `undefined on element ${this.localName}. The attribute will be ` + | |
| `removed, but in the previous version of \`ReactiveElement\`, ` + | |
| `the attribute would not have changed.`); | |
| } | |
| // Track if the property is being reflected to avoid | |
| // setting the property again via `attributeChangedCallback`. Note: | |
| // 1. this takes advantage of the fact that the callback is synchronous. | |
| // 2. will behave incorrectly if multiple attributes are in the reaction | |
| // stack at time of calling. However, since we process attributes | |
| // in `update` this should not be possible (or an extreme corner case | |
| // that we'd like to discover). | |
| // mark state reflecting | |
| this.__reflectingProperty = name; | |
| if (attrValue == null) { | |
| this.removeAttribute(attr); | |
| } | |
| else { | |
| this.setAttribute(attr, attrValue); | |
| } | |
| // mark state not reflecting | |
| this.__reflectingProperty = null; | |
| } | |
| } | |
| /** @internal */ | |
| _$attributeToProperty(name, value) { | |
| const ctor = this.constructor; | |
| // Note, hint this as an `AttributeMap` so closure clearly understands | |
| // the type; it has issues with tracking types through statics | |
| const propName = ctor.__attributeToPropertyMap.get(name); | |
| // Use tracking info to avoid reflecting a property value to an attribute | |
| // if it was just set because the attribute changed. | |
| if (propName !== undefined && this.__reflectingProperty !== propName) { | |
| const options = ctor.getPropertyOptions(propName); | |
| const converter = typeof options.converter === 'function' | |
| ? { fromAttribute: options.converter } | |
| : options.converter?.fromAttribute !== undefined | |
| ? options.converter | |
| : defaultConverter; | |
| // mark state reflecting | |
| this.__reflectingProperty = propName; | |
| const convertedValue = converter.fromAttribute(value, options.type); | |
| this[propName] = | |
| convertedValue ?? | |
| this.__defaultValues?.get(propName) ?? | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| convertedValue; | |
| // mark state not reflecting | |
| this.__reflectingProperty = null; | |
| } | |
| } | |
| /** | |
| * Requests an update which is processed asynchronously. This should be called | |
| * when an element should update based on some state not triggered by setting | |
| * a reactive property. In this case, pass no arguments. It should also be | |
| * called when manually implementing a property setter. In this case, pass the | |
| * property `name` and `oldValue` to ensure that any configured property | |
| * options are honored. | |
| * | |
| * @param name name of requesting property | |
| * @param oldValue old value of requesting property | |
| * @param options property options to use instead of the previously | |
| * configured options | |
| * @param useNewValue if true, the newValue argument is used instead of | |
| * reading the property value. This is important to use if the reactive | |
| * property is a standard private accessor, as opposed to a plain | |
| * property, since private members can't be dynamically read by name. | |
| * @param newValue the new value of the property. This is only used if | |
| * `useNewValue` is true. | |
| * @category updates | |
| */ | |
| requestUpdate(name, oldValue, options, useNewValue = false, newValue) { | |
| // If we have a property key, perform property update steps. | |
| if (name !== undefined) { | |
| if (DEV_MODE && name instanceof Event) { | |
| issueWarning(``, `The requestUpdate() method was called with an Event as the property name. This is probably a mistake caused by binding this.requestUpdate as an event listener. Instead bind a function that will call it with no arguments: () => this.requestUpdate()`); | |
| } | |
| const ctor = this.constructor; | |
| if (useNewValue === false) { | |
| newValue = this[name]; | |
| } | |
| options ??= ctor.getPropertyOptions(name); | |
| const changed = (options.hasChanged ?? notEqual)(newValue, oldValue) || | |
| // When there is no change, check a corner case that can occur when | |
| // 1. there's a initial value which was not reflected | |
| // 2. the property is subsequently set to this value. | |
| // For example, `prop: {useDefault: true, reflect: true}` | |
| // and el.prop = 'foo'. This should be considered a change if the | |
| // attribute is not set because we will now reflect the property to the attribute. | |
| (options.useDefault && | |
| options.reflect && | |
| newValue === this.__defaultValues?.get(name) && | |
| !this.hasAttribute(ctor.__attributeNameForProperty(name, options))); | |
| if (changed) { | |
| this._$changeProperty(name, oldValue, options); | |
| } | |
| else { | |
| // Abort the request if the property should not be considered changed. | |
| return; | |
| } | |
| } | |
| if (this.isUpdatePending === false) { | |
| this.__updatePromise = this.__enqueueUpdate(); | |
| } | |
| } | |
| /** | |
| * @internal | |
| */ | |
| _$changeProperty(name, oldValue, { useDefault, reflect, wrapped }, initializeValue) { | |
| // Record default value when useDefault is used. This allows us to | |
| // restore this value when the attribute is removed. | |
| if (useDefault && !(this.__defaultValues ??= new Map()).has(name)) { | |
| this.__defaultValues.set(name, initializeValue ?? oldValue ?? this[name]); | |
| // if this is not wrapping an accessor, it must be an initial setting | |
| // and in this case we do not want to record the change or reflect. | |
| if (wrapped !== true || initializeValue !== undefined) { | |
| return; | |
| } | |
| } | |
| // TODO (justinfagnani): Create a benchmark of Map.has() + Map.set( | |
| // vs just Map.set() | |
| if (!this._$changedProperties.has(name)) { | |
| // On the initial change, the old value should be `undefined`, except | |
| // with `useDefault` | |
| if (!this.hasUpdated && !useDefault) { | |
| oldValue = undefined; | |
| } | |
| this._$changedProperties.set(name, oldValue); | |
| } | |
| // Add to reflecting properties set. | |
| // Note, it's important that every change has a chance to add the | |
| // property to `__reflectingProperties`. This ensures setting | |
| // attribute + property reflects correctly. | |
| if (reflect === true && this.__reflectingProperty !== name) { | |
| (this.__reflectingProperties ??= new Set()).add(name); | |
| } | |
| } | |
| /** | |
| * Sets up the element to asynchronously update. | |
| */ | |
| async __enqueueUpdate() { | |
| this.isUpdatePending = true; | |
| try { | |
| // Ensure any previous update has resolved before updating. | |
| // This `await` also ensures that property changes are batched. | |
| await this.__updatePromise; | |
| } | |
| catch (e) { | |
| // Refire any previous errors async so they do not disrupt the update | |
| // cycle. Errors are refired so developers have a chance to observe | |
| // them, and this can be done by implementing | |
| // `window.onunhandledrejection`. | |
| Promise.reject(e); | |
| } | |
| const result = this.scheduleUpdate(); | |
| // If `scheduleUpdate` returns a Promise, we await it. This is done to | |
| // enable coordinating updates with a scheduler. Note, the result is | |
| // checked to avoid delaying an additional microtask unless we need to. | |
| if (result != null) { | |
| await result; | |
| } | |
| return !this.isUpdatePending; | |
| } | |
| /** | |
| * Schedules an element update. You can override this method to change the | |
| * timing of updates by returning a Promise. The update will await the | |
| * returned Promise, and you should resolve the Promise to allow the update | |
| * to proceed. If this method is overridden, `super.scheduleUpdate()` | |
| * must be called. | |
| * | |
| * For instance, to schedule updates to occur just before the next frame: | |
| * | |
| * ```ts | |
| * override protected async scheduleUpdate(): Promise<unknown> { | |
| * await new Promise((resolve) => requestAnimationFrame(() => resolve())); | |
| * super.scheduleUpdate(); | |
| * } | |
| * ``` | |
| * @category updates | |
| */ | |
| scheduleUpdate() { | |
| const result = this.performUpdate(); | |
| if (DEV_MODE && | |
| this.constructor.enabledWarnings.includes('async-perform-update') && | |
| typeof result?.then === | |
| 'function') { | |
| issueWarning('async-perform-update', `Element ${this.localName} returned a Promise from performUpdate(). ` + | |
| `This behavior is deprecated and will be removed in a future ` + | |
| `version of ReactiveElement.`); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Performs an element update. Note, if an exception is thrown during the | |
| * update, `firstUpdated` and `updated` will not be called. | |
| * | |
| * Call `performUpdate()` to immediately process a pending update. This should | |
| * generally not be needed, but it can be done in rare cases when you need to | |
| * update synchronously. | |
| * | |
| * @category updates | |
| */ | |
| performUpdate() { | |
| // Abort any update if one is not pending when this is called. | |
| // This can happen if `performUpdate` is called early to "flush" | |
| // the update. | |
| if (!this.isUpdatePending) { | |
| return; | |
| } | |
| debugLogEvent?.({ kind: 'update' }); | |
| if (!this.hasUpdated) { | |
| // Create renderRoot before first update. This occurs in `connectedCallback` | |
| // but is done here to support out of tree calls to `enableUpdating`/`performUpdate`. | |
| this.renderRoot ??= | |
| this.createRenderRoot(); | |
| if (DEV_MODE) { | |
| // Produce warning if any reactive properties on the prototype are | |
| // shadowed by class fields. Instance fields set before upgrade are | |
| // deleted by this point, so any own property is caused by class field | |
| // initialization in the constructor. | |
| const ctor = this.constructor; | |
| const shadowedProperties = [...ctor.elementProperties.keys()].filter((p) => this.hasOwnProperty(p) && p in getPrototypeOf(this)); | |
| if (shadowedProperties.length) { | |
| throw new Error(`The following properties on element ${this.localName} will not ` + | |
| `trigger updates as expected because they are set using class ` + | |
| `fields: ${shadowedProperties.join(', ')}. ` + | |
| `Native class fields and some compiled output will overwrite ` + | |
| `accessors used for detecting changes. See ` + | |
| `https://lit.dev/msg/class-field-shadowing ` + | |
| `for more information.`); | |
| } | |
| } | |
| // Mixin instance properties once, if they exist. | |
| if (this.__instanceProperties) { | |
| // TODO (justinfagnani): should we use the stored value? Could a new value | |
| // have been set since we stored the own property value? | |
| for (const [p, value] of this.__instanceProperties) { | |
| this[p] = value; | |
| } | |
| this.__instanceProperties = undefined; | |
| } | |
| // Trigger initial value reflection and populate the initial | |
| // `changedProperties` map, but only for the case of properties created | |
| // via `createProperty` on accessors, which will not have already | |
| // populated the `changedProperties` map since they are not set. | |
| // We can't know if these accessors had initializers, so we just set | |
| // them anyway - a difference from experimental decorators on fields and | |
| // standard decorators on auto-accessors. | |
| // For context see: | |
| // https://github.com/lit/lit/pull/4183#issuecomment-1711959635 | |
| const elementProperties = this.constructor | |
| .elementProperties; | |
| if (elementProperties.size > 0) { | |
| for (const [p, options] of elementProperties) { | |
| const { wrapped } = options; | |
| const value = this[p]; | |
| if (wrapped === true && | |
| !this._$changedProperties.has(p) && | |
| value !== undefined) { | |
| this._$changeProperty(p, undefined, options, value); | |
| } | |
| } | |
| } | |
| } | |
| let shouldUpdate = false; | |
| const changedProperties = this._$changedProperties; | |
| try { | |
| shouldUpdate = this.shouldUpdate(changedProperties); | |
| if (shouldUpdate) { | |
| this.willUpdate(changedProperties); | |
| this.__controllers?.forEach((c) => c.hostUpdate?.()); | |
| this.update(changedProperties); | |
| } | |
| else { | |
| this.__markUpdated(); | |
| } | |
| } | |
| catch (e) { | |
| // Prevent `firstUpdated` and `updated` from running when there's an | |
| // update exception. | |
| shouldUpdate = false; | |
| // Ensure element can accept additional updates after an exception. | |
| this.__markUpdated(); | |
| throw e; | |
| } | |
| // The update is no longer considered pending and further updates are now allowed. | |
| if (shouldUpdate) { | |
| this._$didUpdate(changedProperties); | |
| } | |
| } | |
| /** | |
| * Invoked before `update()` to compute values needed during the update. | |
| * | |
| * Implement `willUpdate` to compute property values that depend on other | |
| * properties and are used in the rest of the update process. | |
| * | |
| * ```ts | |
| * willUpdate(changedProperties) { | |
| * // only need to check changed properties for an expensive computation. | |
| * if (changedProperties.has('firstName') || changedProperties.has('lastName')) { | |
| * this.sha = computeSHA(`${this.firstName} ${this.lastName}`); | |
| * } | |
| * } | |
| * | |
| * render() { | |
| * return html`SHA: ${this.sha}`; | |
| * } | |
| * ``` | |
| * | |
| * @category updates | |
| */ | |
| willUpdate(_changedProperties) { } | |
| // Note, this is an override point for polyfill-support. | |
| // @internal | |
| _$didUpdate(changedProperties) { | |
| this.__controllers?.forEach((c) => c.hostUpdated?.()); | |
| if (!this.hasUpdated) { | |
| this.hasUpdated = true; | |
| this.firstUpdated(changedProperties); | |
| } | |
| this.updated(changedProperties); | |
| if (DEV_MODE && | |
| this.isUpdatePending && | |
| this.constructor.enabledWarnings.includes('change-in-update')) { | |
| issueWarning('change-in-update', `Element ${this.localName} scheduled an update ` + | |
| `(generally because a property was set) ` + | |
| `after an update completed, causing a new update to be scheduled. ` + | |
| `This is inefficient and should be avoided unless the next update ` + | |
| `can only be scheduled as a side effect of the previous update.`); | |
| } | |
| } | |
| __markUpdated() { | |
| this._$changedProperties = new Map(); | |
| this.isUpdatePending = false; | |
| } | |
| /** | |
| * Returns a Promise that resolves when the element has completed updating. | |
| * The Promise value is a boolean that is `true` if the element completed the | |
| * update without triggering another update. The Promise result is `false` if | |
| * a property was set inside `updated()`. If the Promise is rejected, an | |
| * exception was thrown during the update. | |
| * | |
| * To await additional asynchronous work, override the `getUpdateComplete` | |
| * method. For example, it is sometimes useful to await a rendered element | |
| * before fulfilling this Promise. To do this, first await | |
| * `super.getUpdateComplete()`, then any subsequent state. | |
| * | |
| * @return A promise of a boolean that resolves to true if the update completed | |
| * without triggering another update. | |
| * @category updates | |
| */ | |
| get updateComplete() { | |
| return this.getUpdateComplete(); | |
| } | |
| /** | |
| * Override point for the `updateComplete` promise. | |
| * | |
| * It is not safe to override the `updateComplete` getter directly due to a | |
| * limitation in TypeScript which means it is not possible to call a | |
| * superclass getter (e.g. `super.updateComplete.then(...)`) when the target | |
| * language is ES5 (https://github.com/microsoft/TypeScript/issues/338). | |
| * This method should be overridden instead. For example: | |
| * | |
| * ```ts | |
| * class MyElement extends LitElement { | |
| * override async getUpdateComplete() { | |
| * const result = await super.getUpdateComplete(); | |
| * await this._myChild.updateComplete; | |
| * return result; | |
| * } | |
| * } | |
| * ``` | |
| * | |
| * @return A promise of a boolean that resolves to true if the update completed | |
| * without triggering another update. | |
| * @category updates | |
| */ | |
| getUpdateComplete() { | |
| return this.__updatePromise; | |
| } | |
| /** | |
| * Controls whether or not `update()` should be called when the element requests | |
| * an update. By default, this method always returns `true`, but this can be | |
| * customized to control when to update. | |
| * | |
| * @param _changedProperties Map of changed properties with old values | |
| * @category updates | |
| */ | |
| shouldUpdate(_changedProperties) { | |
| return true; | |
| } | |
| /** | |
| * Updates the element. This method reflects property values to attributes. | |
| * It can be overridden to render and keep updated element DOM. | |
| * Setting properties inside this method will *not* trigger | |
| * another update. | |
| * | |
| * @param _changedProperties Map of changed properties with old values | |
| * @category updates | |
| */ | |
| update(_changedProperties) { | |
| // The forEach() expression will only run when __reflectingProperties is | |
| // defined, and it returns undefined, setting __reflectingProperties to | |
| // undefined | |
| this.__reflectingProperties &&= this.__reflectingProperties.forEach((p) => this.__propertyToAttribute(p, this[p])); | |
| this.__markUpdated(); | |
| } | |
| /** | |
| * Invoked whenever the element is updated. Implement to perform | |
| * post-updating tasks via DOM APIs, for example, focusing an element. | |
| * | |
| * Setting properties inside this method will trigger the element to update | |
| * again after this update cycle completes. | |
| * | |
| * @param _changedProperties Map of changed properties with old values | |
| * @category updates | |
| */ | |
| updated(_changedProperties) { } | |
| /** | |
| * Invoked when the element is first updated. Implement to perform one time | |
| * work on the element after update. | |
| * | |
| * ```ts | |
| * firstUpdated() { | |
| * this.renderRoot.getElementById('my-text-area').focus(); | |
| * } | |
| * ``` | |
| * | |
| * Setting properties inside this method will trigger the element to update | |
| * again after this update cycle completes. | |
| * | |
| * @param _changedProperties Map of changed properties with old values | |
| * @category updates | |
| */ | |
| firstUpdated(_changedProperties) { } | |
| } | |
| /** | |
| * Memoized list of all element styles. | |
| * Created lazily on user subclasses when finalizing the class. | |
| * @nocollapse | |
| * @category styles | |
| */ | |
| ReactiveElement.elementStyles = []; | |
| /** | |
| * Options used when calling `attachShadow`. Set this property to customize | |
| * the options for the shadowRoot; for example, to create a closed | |
| * shadowRoot: `{mode: 'closed'}`. | |
| * | |
| * Note, these options are used in `createRenderRoot`. If this method | |
| * is customized, options should be respected if possible. | |
| * @nocollapse | |
| * @category rendering | |
| */ | |
| ReactiveElement.shadowRootOptions = { mode: 'open' }; | |
| // Assigned here to work around a jscompiler bug with static fields | |
| // when compiling to ES5. | |
| // https://github.com/google/closure-compiler/issues/3177 | |
| ReactiveElement[JSCompiler_renameProperty('elementProperties', ReactiveElement)] = new Map(); | |
| ReactiveElement[JSCompiler_renameProperty('finalized', ReactiveElement)] = new Map(); | |
| // Apply polyfills if available | |
| polyfillSupport?.({ ReactiveElement }); | |
| // Dev mode warnings... | |
| if (DEV_MODE) { | |
| // Default warning set. | |
| ReactiveElement.enabledWarnings = [ | |
| 'change-in-update', | |
| 'async-perform-update', | |
| ]; | |
| const ensureOwnWarnings = function (ctor) { | |
| if (!ctor.hasOwnProperty(JSCompiler_renameProperty('enabledWarnings', ctor))) { | |
| ctor.enabledWarnings = ctor.enabledWarnings.slice(); | |
| } | |
| }; | |
| ReactiveElement.enableWarning = function (warning) { | |
| ensureOwnWarnings(this); | |
| if (!this.enabledWarnings.includes(warning)) { | |
| this.enabledWarnings.push(warning); | |
| } | |
| }; | |
| ReactiveElement.disableWarning = function (warning) { | |
| ensureOwnWarnings(this); | |
| const i = this.enabledWarnings.indexOf(warning); | |
| if (i >= 0) { | |
| this.enabledWarnings.splice(i, 1); | |
| } | |
| }; | |
| } | |
| // IMPORTANT: do not change the property name or the assignment expression. | |
| // This line will be used in regexes to search for ReactiveElement usage. | |
| (global.reactiveElementVersions ??= []).push('2.1.2'); | |
| if (DEV_MODE && global.reactiveElementVersions.length > 1) { | |
| queueMicrotask(() => { | |
| issueWarning('multiple-versions', `Multiple versions of Lit loaded. Loading multiple versions ` + | |
| `is not recommended.`); | |
| }); | |
| } | |
| //# sourceMappingURL=reactive-element.js.map |
Xet Storage Details
- Size:
- 47.2 kB
- Xet hash:
- cc4e1aa6f436f79a1a7ca3c8a65a52a12b566dd2c7ce03bfbb5b9f97abd2bb61
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.