Skip to content

Instantly share code, notes, and snippets.

@ekashida
Last active February 16, 2024 21:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ekashida/4b95bf460435f8da3585397e86c49dd2 to your computer and use it in GitHub Desktop.
Save ekashida/4b95bf460435f8da3585397e86c49dd2 to your computer and use it in GitHub Desktop.
lit-all.min.js after disabling terser (b779807a82649b5128eb754b77b7b3cb4c7c1f98)
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const NODE_MODE$1 = false;
// Allows minifiers to rename references to globalThis
const global$2 = globalThis;
/**
* Whether the current browser supports `adoptedStyleSheets`.
*/
const supportsAdoptingStyleSheets = global$2.ShadowRoot &&
(global$2.ShadyCSS === undefined || global$2.ShadyCSS.nativeShadow) &&
'adoptedStyleSheets' in Document.prototype &&
'replace' in CSSStyleSheet.prototype;
const constructionToken = Symbol();
const cssTagCache = new WeakMap();
/**
* A container for a string of CSS text, that may be used to create a CSSStyleSheet.
*
* CSSResult is the return value of `css`-tagged template literals and
* `unsafeCSS()`. In order to ensure that CSSResults are only created via the
* `css` tag and `unsafeCSS()`, CSSResult cannot be constructed directly.
*/
class CSSResult {
constructor(cssText, strings, safeToken) {
// This property needs to remain unminified.
this['_$cssResult$'] = true;
if (safeToken !== constructionToken) {
throw new Error('CSSResult is not constructable. Use `unsafeCSS` or `css` instead.');
}
this.cssText = cssText;
this._strings = strings;
}
// This is a getter so that it's lazy. In practice, this means stylesheets
// are not created until the first element instance is made.
get styleSheet() {
// If `supportsAdoptingStyleSheets` is true then we assume CSSStyleSheet is
// constructable.
let styleSheet = this._styleSheet;
const strings = this._strings;
if (supportsAdoptingStyleSheets && styleSheet === undefined) {
const cacheable = strings !== undefined && strings.length === 1;
if (cacheable) {
styleSheet = cssTagCache.get(strings);
}
if (styleSheet === undefined) {
(this._styleSheet = styleSheet = new CSSStyleSheet()).replaceSync(this.cssText);
if (cacheable) {
cssTagCache.set(strings, styleSheet);
}
}
}
return styleSheet;
}
toString() {
return this.cssText;
}
}
const textFromCSSResult = (value) => {
// This property needs to remain unminified.
if (value['_$cssResult$'] === true) {
return value.cssText;
}
else if (typeof value === 'number') {
return value;
}
else {
throw new Error(`Value passed to 'css' function must be a 'css' function result: ` +
`${value}. Use 'unsafeCSS' to pass non-literal values, but take care ` +
`to ensure page security.`);
}
};
/**
* Wrap a value for interpolation in a {@linkcode css} tagged template literal.
*
* This is unsafe because untrusted CSS text can be used to phone home
* or exfiltrate data to an attacker controlled site. Take care to only use
* this with trusted input.
*/
const unsafeCSS = (value) => new CSSResult(typeof value === 'string' ? value : String(value), undefined, constructionToken);
/**
* A template literal tag which can be used with LitElement's
* {@linkcode LitElement.styles} property to set element styles.
*
* For security reasons, only literal string values and number may be used in
* embedded expressions. To incorporate non-literal values {@linkcode unsafeCSS}
* may be used inside an expression.
*/
const css = (strings, ...values) => {
const cssText = strings.length === 1
? strings[0]
: values.reduce((acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1], strings[0]);
return new CSSResult(cssText, strings, constructionToken);
};
/**
* Applies the given styles to a `shadowRoot`. When Shadow DOM is
* available but `adoptedStyleSheets` is not, styles are appended to the
* `shadowRoot` to [mimic spec behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets).
* Note, when shimming is used, any styles that are subsequently placed into
* the shadowRoot should be placed *before* any shimmed adopted styles. This
* will match spec behavior that gives adopted sheets precedence over styles in
* shadowRoot.
*/
const adoptStyles = (renderRoot, styles) => {
if (supportsAdoptingStyleSheets) {
renderRoot.adoptedStyleSheets = styles.map((s) => s instanceof CSSStyleSheet ? s : s.styleSheet);
}
else {
for (const s of styles) {
const style = document.createElement('style');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nonce = global$2['litNonce'];
if (nonce !== undefined) {
style.setAttribute('nonce', nonce);
}
style.textContent = s.cssText;
renderRoot.appendChild(style);
}
}
};
const cssResultFromStyleSheet = (sheet) => {
let cssText = '';
for (const rule of sheet.cssRules) {
cssText += rule.cssText;
}
return unsafeCSS(cssText);
};
const getCompatibleStyle = supportsAdoptingStyleSheets ||
(NODE_MODE$1 )
? (s) => s
: (s) => s instanceof CSSStyleSheet ? cssResultFromStyleSheet(s) : s;
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
// TODO (justinfagnani): Add `hasOwn` here when we ship ES2022
const { is, defineProperty, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, getPrototypeOf, } = Object;
// Lets a minifier replace globalThis references with a minified name
const global$1 = globalThis;
const trustedTypes$1 = global$1
.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$1
? trustedTypes$1.emptyScript
: '';
const polyfillSupport$2 = global$1.reactiveElementPolyfillSupport;
/**
* Useful for visualizing and logging insights into what the Lit template system is doing.
*
* Compiled out of prod mode builds.
*/
const debugLogEvent = 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$1 = (prop, _obj) => prop;
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.
*/
const notEqual = (value, old) => !is(value, old);
const defaultPropertyDeclaration = {
attribute: true,
type: String,
converter: defaultConverter,
reflect: 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$1.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
*/
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();
this.elementProperties.set(name, options);
if (!options.noAccessor) {
const key = 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;
},
};
return {
get() {
return get?.call(this);
},
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$1('elementProperties'))) {
// 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$1('finalized'))) {
return;
}
this.finalized = true;
this.__prepare();
// Create properties from the static properties block:
if (this.hasOwnProperty(JSCompiler_renameProperty$1('properties'))) {
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);
}
/**
* 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 ust 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. Note, on very old versions of Safari (<=9) or Chrome
* (<=41), properties created for native platform properties like (`id` or
* `name`) may not have default values set in the element constructor. On
* these browsers native properties appear on instances and therefore their
* default value will overwrite any element default (e.g. if the element sets
* this.id = 'id' in the constructor, the 'id' will become '' since this is
* the native platform default).
*/
__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 [using the lifecycle callbacks](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks)
* 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);
// 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;
this[propName] = converter.fromAttribute(value, options.type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
);
// 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
* @category updates
*/
requestUpdate(name, oldValue, options) {
// If we have a property key, perform property update steps.
if (name !== undefined) {
options ??= this.constructor.getPropertyOptions(name);
const hasChanged = options.hasChanged ?? notEqual;
const newValue = this[name];
if (hasChanged(newValue, oldValue)) {
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, options) {
// TODO (justinfagnani): Create a benchmark of Map.has() + Map.set(
// vs just Map.set()
if (!this._$changedProperties.has(name)) {
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 (options.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();
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();
// 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 experimental
// decorators on accessors, which will not have already populated the
// changedProperties map. 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 why experimentalDecorators with auto accessors are handled
// specifically also 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) {
if (options.wrapped === true &&
!this._$changedProperties.has(p) &&
this[p] !== undefined) {
this._$changeProperty(p, this[p], options);
}
}
}
}
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);
}
__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 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$1('elementProperties')] = new Map();
ReactiveElement[JSCompiler_renameProperty$1('finalized')] = new Map();
// Apply polyfills if available
polyfillSupport$2?.({ ReactiveElement });
// IMPORTANT: do not change the property name or the assignment expression.
// This line will be used in regexes to search for ReactiveElement usage.
(global$1.reactiveElementVersions ??= []).push('2.0.4');
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
// Allows minifiers to rename references to globalThis
const global = globalThis;
const wrap$1 = (node) => node;
const trustedTypes = global.trustedTypes;
/**
* Our TrustedTypePolicy for HTML which is declared using the html template
* tag function.
*
* That HTML is a developer-authored constant, and is parsed with innerHTML
* before any untrusted expressions have been mixed in. Therefor it is
* considered safe by construction.
*/
const policy = trustedTypes
? trustedTypes.createPolicy('lit-html', {
createHTML: (s) => s,
})
: undefined;
// Added to an attribute name to mark the attribute as bound so we can find
// it easily.
const boundAttributeSuffix = '$lit$';
// This marker is used in many syntactic positions in HTML, so it must be
// a valid element name and attribute name. We don't support dynamic names (yet)
// but this at least ensures that the parse tree is closer to the template
// intention.
const marker = `lit$${String(Math.random()).slice(9)}$`;
// String used to tell if a comment is a marker comment
const markerMatch = '?' + marker;
// Text used to insert a comment marker node. We use processing instruction
// syntax because it's slightly smaller, but parses as a comment node.
const nodeMarker = `<${markerMatch}>`;
const d = document;
// Creates a dynamic marker. We never have to search for these in the DOM.
const createMarker$1 = () => d.createComment('');
const isPrimitive$1 = (value) => value === null || (typeof value != 'object' && typeof value != 'function');
const isArray = Array.isArray;
const isIterable = (value) => isArray(value) ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
typeof value?.[Symbol.iterator] === 'function';
const SPACE_CHAR = `[ \t\n\f\r]`;
const ATTR_VALUE_CHAR = `[^ \t\n\f\r"'\`<>=]`;
const NAME_CHAR = `[^\\s"'>=/]`;
// These regexes represent the five parsing states that we care about in the
// Template's HTML scanner. They match the *end* of the state they're named
// after.
// Depending on the match, we transition to a new state. If there's no match,
// we stay in the same state.
// Note that the regexes are stateful. We utilize lastIndex and sync it
// across the multiple regexes used. In addition to the five regexes below
// we also dynamically create a regex to find the matching end tags for raw
// text elements.
/**
* End of text is: `<` followed by:
* (comment start) or (tag) or (dynamic tag binding)
*/
const textEndRegex = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g;
const COMMENT_START = 1;
const TAG_NAME = 2;
const DYNAMIC_TAG_NAME = 3;
const commentEndRegex = /-->/g;
/**
* Comments not started with <!--, like </{, can be ended by a single `>`
*/
const comment2EndRegex = />/g;
/**
* The tagEnd regex matches the end of the "inside an opening" tag syntax
* position. It either matches a `>`, an attribute-like sequence, or the end
* of the string after a space (attribute-name position ending).
*
* See attributes in the HTML spec:
* https://www.w3.org/TR/html5/syntax.html#elements-attributes
*
* " \t\n\f\r" are HTML space characters:
* https://infra.spec.whatwg.org/#ascii-whitespace
*
* So an attribute is:
* * The name: any character except a whitespace character, ("), ('), ">",
* "=", or "/". Note: this is different from the HTML spec which also excludes control characters.
* * Followed by zero or more space characters
* * Followed by "="
* * Followed by zero or more space characters
* * Followed by:
* * Any character except space, ('), ("), "<", ">", "=", (`), or
* * (") then any non-("), or
* * (') then any non-(')
*/
const tagEndRegex = new RegExp(`>|${SPACE_CHAR}(?:(${NAME_CHAR}+)(${SPACE_CHAR}*=${SPACE_CHAR}*(?:${ATTR_VALUE_CHAR}|("|')|))|$)`, 'g');
const ENTIRE_MATCH = 0;
const ATTRIBUTE_NAME = 1;
const SPACES_AND_EQUALS = 2;
const QUOTE_CHAR = 3;
const singleQuoteAttrEndRegex = /'/g;
const doubleQuoteAttrEndRegex = /"/g;
/**
* Matches the raw text elements.
*
* Comments are not parsed within raw text elements, so we need to search their
* text content for marker strings.
*/
const rawTextElement = /^(?:script|style|textarea|title)$/i;
/** TemplateResult types */
const HTML_RESULT$1 = 1;
const SVG_RESULT$1 = 2;
// TemplatePart types
// IMPORTANT: these must match the values in PartType
const ATTRIBUTE_PART = 1;
const CHILD_PART = 2;
const PROPERTY_PART = 3;
const BOOLEAN_ATTRIBUTE_PART = 4;
const EVENT_PART = 5;
const ELEMENT_PART = 6;
const COMMENT_PART = 7;
/**
* Generates a template literal tag function that returns a TemplateResult with
* the given result type.
*/
const tag = (type) => (strings, ...values) => {
return {
// This property needs to remain unminified.
['_$litType$']: type,
strings,
values,
};
};
/**
* Interprets a template literal as an HTML template that can efficiently
* render to and update a container.
*
* ```ts
* const header = (title: string) => html`<h1>${title}</h1>`;
* ```
*
* The `html` tag returns a description of the DOM to render as a value. It is
* lazy, meaning no work is done until the template is rendered. When rendering,
* if a template comes from the same expression as a previously rendered result,
* it's efficiently updated instead of replaced.
*/
const html$1 = tag(HTML_RESULT$1);
/**
* Interprets a template literal as an SVG fragment that can efficiently
* render to and update a container.
*
* ```ts
* const rect = svg`<rect width="10" height="10"></rect>`;
*
* const myImage = html`
* <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
* ${rect}
* </svg>`;
* ```
*
* The `svg` *tag function* should only be used for SVG fragments, or elements
* that would be contained **inside** an `<svg>` HTML element. A common error is
* placing an `<svg>` *element* in a template tagged with the `svg` tag
* function. The `<svg>` element is an HTML element and should be used within a
* template tagged with the {@linkcode html} tag function.
*
* In LitElement usage, it's invalid to return an SVG fragment from the
* `render()` method, as the SVG fragment will be contained within the element's
* shadow root and thus cannot be used within an `<svg>` HTML element.
*/
const svg$1 = tag(SVG_RESULT$1);
/**
* A sentinel value that signals that a value was handled by a directive and
* should not be written to the DOM.
*/
const noChange = Symbol.for('lit-noChange');
/**
* A sentinel value that signals a ChildPart to fully clear its content.
*
* ```ts
* const button = html`${
* user.isAdmin
* ? html`<button>DELETE</button>`
* : nothing
* }`;
* ```
*
* Prefer using `nothing` over other falsy values as it provides a consistent
* behavior between various expression binding contexts.
*
* In child expressions, `undefined`, `null`, `''`, and `nothing` all behave the
* same and render no nodes. In attribute expressions, `nothing` _removes_ the
* attribute, while `undefined` and `null` will render an empty string. In
* property expressions `nothing` becomes `undefined`.
*/
const nothing = Symbol.for('lit-nothing');
/**
* The cache of prepared templates, keyed by the tagged TemplateStringsArray
* and _not_ accounting for the specific template tag used. This means that
* template tags cannot be dynamic - the must statically be one of html, svg,
* or attr. This restriction simplifies the cache lookup, which is on the hot
* path for rendering.
*/
const templateCache = new WeakMap();
const walker = d.createTreeWalker(d, 129 /* NodeFilter.SHOW_{ELEMENT|COMMENT} */);
function trustFromTemplateString(tsa, stringFromTSA) {
// A security check to prevent spoofing of Lit template results.
// In the future, we may be able to replace this with Array.isTemplateObject,
// though we might need to make that check inside of the html and svg
// functions, because precompiled templates don't come in as
// TemplateStringArray objects.
if (!Array.isArray(tsa) || !tsa.hasOwnProperty('raw')) {
let message = 'invalid template strings array';
throw new Error(message);
}
return policy !== undefined
? policy.createHTML(stringFromTSA)
: stringFromTSA;
}
/**
* Returns an HTML string for the given TemplateStringsArray and result type
* (HTML or SVG), along with the case-sensitive bound attribute names in
* template order. The HTML contains comment markers denoting the `ChildPart`s
* and suffixes on bound attributes denoting the `AttributeParts`.
*
* @param strings template strings array
* @param type HTML or SVG
* @return Array containing `[html, attrNames]` (array returned for terseness,
* to avoid object fields since this code is shared with non-minified SSR
* code)
*/
const getTemplateHtml = (strings, type) => {
// Insert makers into the template HTML to represent the position of
// bindings. The following code scans the template strings to determine the
// syntactic position of the bindings. They can be in text position, where
// we insert an HTML comment, attribute value position, where we insert a
// sentinel string and re-write the attribute name, or inside a tag where
// we insert the sentinel string.
const l = strings.length - 1;
// Stores the case-sensitive bound attribute names in the order of their
// parts. ElementParts are also reflected in this array as undefined
// rather than a string, to disambiguate from attribute bindings.
const attrNames = [];
let html = type === SVG_RESULT$1 ? '<svg>' : '';
// When we're inside a raw text tag (not it's text content), the regex
// will still be tagRegex so we can find attributes, but will switch to
// this regex when the tag ends.
let rawTextEndRegex;
// The current parsing state, represented as a reference to one of the
// regexes
let regex = textEndRegex;
for (let i = 0; i < l; i++) {
const s = strings[i];
// The index of the end of the last attribute name. When this is
// positive at end of a string, it means we're in an attribute value
// position and need to rewrite the attribute name.
// We also use a special value of -2 to indicate that we encountered
// the end of a string in attribute name position.
let attrNameEndIndex = -1;
let attrName;
let lastIndex = 0;
let match;
// The conditions in this loop handle the current parse state, and the
// assignments to the `regex` variable are the state transitions.
while (lastIndex < s.length) {
// Make sure we start searching from where we previously left off
regex.lastIndex = lastIndex;
match = regex.exec(s);
if (match === null) {
break;
}
lastIndex = regex.lastIndex;
if (regex === textEndRegex) {
if (match[COMMENT_START] === '!--') {
regex = commentEndRegex;
}
else if (match[COMMENT_START] !== undefined) {
// We started a weird comment, like </{
regex = comment2EndRegex;
}
else if (match[TAG_NAME] !== undefined) {
if (rawTextElement.test(match[TAG_NAME])) {
// Record if we encounter a raw-text element. We'll switch to
// this regex at the end of the tag.
rawTextEndRegex = new RegExp(`</${match[TAG_NAME]}`, 'g');
}
regex = tagEndRegex;
}
else if (match[DYNAMIC_TAG_NAME] !== undefined) {
regex = tagEndRegex;
}
}
else if (regex === tagEndRegex) {
if (match[ENTIRE_MATCH] === '>') {
// End of a tag. If we had started a raw-text element, use that
// regex
regex = rawTextEndRegex ?? textEndRegex;
// We may be ending an unquoted attribute value, so make sure we
// clear any pending attrNameEndIndex
attrNameEndIndex = -1;
}
else if (match[ATTRIBUTE_NAME] === undefined) {
// Attribute name position
attrNameEndIndex = -2;
}
else {
attrNameEndIndex = regex.lastIndex - match[SPACES_AND_EQUALS].length;
attrName = match[ATTRIBUTE_NAME];
regex =
match[QUOTE_CHAR] === undefined
? tagEndRegex
: match[QUOTE_CHAR] === '"'
? doubleQuoteAttrEndRegex
: singleQuoteAttrEndRegex;
}
}
else if (regex === doubleQuoteAttrEndRegex ||
regex === singleQuoteAttrEndRegex) {
regex = tagEndRegex;
}
else if (regex === commentEndRegex || regex === comment2EndRegex) {
regex = textEndRegex;
}
else {
// Not one of the five state regexes, so it must be the dynamically
// created raw text regex and we're at the close of that element.
regex = tagEndRegex;
rawTextEndRegex = undefined;
}
}
// We have four cases:
// 1. We're in text position, and not in a raw text element
// (regex === textEndRegex): insert a comment marker.
// 2. We have a non-negative attrNameEndIndex which means we need to
// rewrite the attribute name to add a bound attribute suffix.
// 3. We're at the non-first binding in a multi-binding attribute, use a
// plain marker.
// 4. We're somewhere else inside the tag. If we're in attribute name
// position (attrNameEndIndex === -2), add a sequential suffix to
// generate a unique attribute name.
// Detect a binding next to self-closing tag end and insert a space to
// separate the marker from the tag end:
const end = regex === tagEndRegex && strings[i + 1].startsWith('/>') ? ' ' : '';
html +=
regex === textEndRegex
? s + nodeMarker
: attrNameEndIndex >= 0
? (attrNames.push(attrName),
s.slice(0, attrNameEndIndex) +
boundAttributeSuffix +
s.slice(attrNameEndIndex)) +
marker +
end
: s + marker + (attrNameEndIndex === -2 ? i : end);
}
const htmlResult = html + (strings[l] || '<?>') + (type === SVG_RESULT$1 ? '</svg>' : '');
// Returned as an array for terseness
return [trustFromTemplateString(strings, htmlResult), attrNames];
};
class Template {
constructor(
// This property needs to remain unminified.
{ strings, ['_$litType$']: type }, options) {
this.parts = [];
let node;
let nodeIndex = 0;
let attrNameIndex = 0;
const partCount = strings.length - 1;
const parts = this.parts;
// Create template element
const [html, attrNames] = getTemplateHtml(strings, type);
this.el = Template.createElement(html, options);
walker.currentNode = this.el.content;
// Re-parent SVG nodes into template root
if (type === SVG_RESULT$1) {
const svgElement = this.el.content.firstChild;
svgElement.replaceWith(...svgElement.childNodes);
}
// Walk the template to find binding markers and create TemplateParts
while ((node = walker.nextNode()) !== null && parts.length < partCount) {
if (node.nodeType === 1) {
// TODO (justinfagnani): for attempted dynamic tag names, we don't
// increment the bindingIndex, and it'll be off by 1 in the element
// and off by two after it.
if (node.hasAttributes()) {
for (const name of node.getAttributeNames()) {
if (name.endsWith(boundAttributeSuffix)) {
const realName = attrNames[attrNameIndex++];
const value = node.getAttribute(name);
const statics = value.split(marker);
const m = /([.?@])?(.*)/.exec(realName);
parts.push({
type: ATTRIBUTE_PART,
index: nodeIndex,
name: m[2],
strings: statics,
ctor: m[1] === '.'
? PropertyPart
: m[1] === '?'
? BooleanAttributePart
: m[1] === '@'
? EventPart
: AttributePart,
});
node.removeAttribute(name);
}
else if (name.startsWith(marker)) {
parts.push({
type: ELEMENT_PART,
index: nodeIndex,
});
node.removeAttribute(name);
}
}
}
// TODO (justinfagnani): benchmark the regex against testing for each
// of the 3 raw text element names.
if (rawTextElement.test(node.tagName)) {
// For raw text elements we need to split the text content on
// markers, create a Text node for each segment, and create
// a TemplatePart for each marker.
const strings = node.textContent.split(marker);
const lastIndex = strings.length - 1;
if (lastIndex > 0) {
node.textContent = trustedTypes
? trustedTypes.emptyScript
: '';
// Generate a new text node for each literal section
// These nodes are also used as the markers for node parts
// We can't use empty text nodes as markers because they're
// normalized when cloning in IE (could simplify when
// IE is no longer supported)
for (let i = 0; i < lastIndex; i++) {
node.append(strings[i], createMarker$1());
// Walk past the marker node we just added
walker.nextNode();
parts.push({ type: CHILD_PART, index: ++nodeIndex });
}
// Note because this marker is added after the walker's current
// node, it will be walked to in the outer loop (and ignored), so
// we don't need to adjust nodeIndex here
node.append(strings[lastIndex], createMarker$1());
}
}
}
else if (node.nodeType === 8) {
const data = node.data;
if (data === markerMatch) {
parts.push({ type: CHILD_PART, index: nodeIndex });
}
else {
let i = -1;
while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
// Comment node has a binding marker inside, make an inactive part
// The binding won't work, but subsequent bindings will
parts.push({ type: COMMENT_PART, index: nodeIndex });
// Move to the end of the match
i += marker.length - 1;
}
}
}
nodeIndex++;
}
}
// Overridden via `litHtmlPolyfillSupport` to provide platform support.
/** @nocollapse */
static createElement(html, _options) {
const el = d.createElement('template');
el.innerHTML = html;
return el;
}
}
function resolveDirective(part, value, parent = part, attributeIndex) {
// Bail early if the value is explicitly noChange. Note, this means any
// nested directive is still attached and is not run.
if (value === noChange) {
return value;
}
let currentDirective = attributeIndex !== undefined
? parent.__directives?.[attributeIndex]
: parent.__directive;
const nextDirectiveConstructor = isPrimitive$1(value)
? undefined
: // This property needs to remain unminified.
value['_$litDirective$'];
if (currentDirective?.constructor !== nextDirectiveConstructor) {
// This property needs to remain unminified.
currentDirective?.['_$notifyDirectiveConnectionChanged']?.(false);
if (nextDirectiveConstructor === undefined) {
currentDirective = undefined;
}
else {
currentDirective = new nextDirectiveConstructor(part);
currentDirective._$initialize(part, parent, attributeIndex);
}
if (attributeIndex !== undefined) {
(parent.__directives ??= [])[attributeIndex] =
currentDirective;
}
else {
parent.__directive = currentDirective;
}
}
if (currentDirective !== undefined) {
value = resolveDirective(part, currentDirective._$resolve(part, value.values), currentDirective, attributeIndex);
}
return value;
}
/**
* An updateable instance of a Template. Holds references to the Parts used to
* update the template instance.
*/
class TemplateInstance {
constructor(template, parent) {
this._$parts = [];
/** @internal */
this._$disconnectableChildren = undefined;
this._$template = template;
this._$parent = parent;
}
// Called by ChildPart parentNode getter
get parentNode() {
return this._$parent.parentNode;
}
// See comment in Disconnectable interface for why this is a getter
get _$isConnected() {
return this._$parent._$isConnected;
}
// This method is separate from the constructor because we need to return a
// DocumentFragment and we don't want to hold onto it with an instance field.
_clone(options) {
const { el: { content }, parts: parts, } = this._$template;
const fragment = (options?.creationScope ?? d).importNode(content, true);
walker.currentNode = fragment;
let node = walker.nextNode();
let nodeIndex = 0;
let partIndex = 0;
let templatePart = parts[0];
while (templatePart !== undefined) {
if (nodeIndex === templatePart.index) {
let part;
if (templatePart.type === CHILD_PART) {
part = new ChildPart$1(node, node.nextSibling, this, options);
}
else if (templatePart.type === ATTRIBUTE_PART) {
part = new templatePart.ctor(node, templatePart.name, templatePart.strings, this, options);
}
else if (templatePart.type === ELEMENT_PART) {
part = new ElementPart(node, this, options);
}
this._$parts.push(part);
templatePart = parts[++partIndex];
}
if (nodeIndex !== templatePart?.index) {
node = walker.nextNode();
nodeIndex++;
}
}
// We need to set the currentNode away from the cloned tree so that we
// don't hold onto the tree even if the tree is detached and should be
// freed.
walker.currentNode = d;
return fragment;
}
_update(values) {
let i = 0;
for (const part of this._$parts) {
if (part !== undefined) {
if (part.strings !== undefined) {
part._$setValue(values, part, i);
// The number of values the part consumes is part.strings.length - 1
// since values are in between template spans. We increment i by 1
// later in the loop, so increment it by part.strings.length - 2 here
i += part.strings.length - 2;
}
else {
part._$setValue(values[i]);
}
}
i++;
}
}
}
class ChildPart$1 {
// See comment in Disconnectable interface for why this is a getter
get _$isConnected() {
// ChildParts that are not at the root should always be created with a
// parent; only RootChildNode's won't, so they return the local isConnected
// state
return this._$parent?._$isConnected ?? this.__isConnected;
}
constructor(startNode, endNode, parent, options) {
this.type = CHILD_PART;
this._$committedValue = nothing;
// The following fields will be patched onto ChildParts when required by
// AsyncDirective
/** @internal */
this._$disconnectableChildren = undefined;
this._$startNode = startNode;
this._$endNode = endNode;
this._$parent = parent;
this.options = options;
// Note __isConnected is only ever accessed on RootParts (i.e. when there is
// no _$parent); the value on a non-root-part is "don't care", but checking
// for parent would be more code
this.__isConnected = options?.isConnected ?? true;
}
/**
* The parent node into which the part renders its content.
*
* A ChildPart's content consists of a range of adjacent child nodes of
* `.parentNode`, possibly bordered by 'marker nodes' (`.startNode` and
* `.endNode`).
*
* - If both `.startNode` and `.endNode` are non-null, then the part's content
* consists of all siblings between `.startNode` and `.endNode`, exclusively.
*
* - If `.startNode` is non-null but `.endNode` is null, then the part's
* content consists of all siblings following `.startNode`, up to and
* including the last child of `.parentNode`. If `.endNode` is non-null, then
* `.startNode` will always be non-null.
*
* - If both `.endNode` and `.startNode` are null, then the part's content
* consists of all child nodes of `.parentNode`.
*/
get parentNode() {
let parentNode = wrap$1(this._$startNode).parentNode;
const parent = this._$parent;
if (parent !== undefined &&
parentNode?.nodeType === 11 /* Node.DOCUMENT_FRAGMENT */) {
// If the parentNode is a DocumentFragment, it may be because the DOM is
// still in the cloned fragment during initial render; if so, get the real
// parentNode the part will be committed into by asking the parent.
parentNode = parent.parentNode;
}
return parentNode;
}
/**
* The part's leading marker node, if any. See `.parentNode` for more
* information.
*/
get startNode() {
return this._$startNode;
}
/**
* The part's trailing marker node, if any. See `.parentNode` for more
* information.
*/
get endNode() {
return this._$endNode;
}
_$setValue(value, directiveParent = this) {
value = resolveDirective(this, value, directiveParent);
if (isPrimitive$1(value)) {
// Non-rendering child values. It's important that these do not render
// empty text nodes to avoid issues with preventing default <slot>
// fallback content.
if (value === nothing || value == null || value === '') {
if (this._$committedValue !== nothing) {
this._$clear();
}
this._$committedValue = nothing;
}
else if (value !== this._$committedValue && value !== noChange) {
this._commitText(value);
}
// This property needs to remain unminified.
}
else if (value['_$litType$'] !== undefined) {
this._commitTemplateResult(value);
}
else if (value.nodeType !== undefined) {
this._commitNode(value);
}
else if (isIterable(value)) {
this._commitIterable(value);
}
else {
// Fallback, will render the string representation
this._commitText(value);
}
}
_insert(node) {
return wrap$1(wrap$1(this._$startNode).parentNode).insertBefore(node, this._$endNode);
}
_commitNode(value) {
if (this._$committedValue !== value) {
this._$clear();
this._$committedValue = this._insert(value);
}
}
_commitText(value) {
// If the committed value is a primitive it means we called _commitText on
// the previous render, and we know that this._$startNode.nextSibling is a
// Text node. We can now just replace the text content (.data) of the node.
if (this._$committedValue !== nothing &&
isPrimitive$1(this._$committedValue)) {
const node = wrap$1(this._$startNode).nextSibling;
node.data = value;
}
else {
{
this._commitNode(d.createTextNode(value));
}
}
this._$committedValue = value;
}
_commitTemplateResult(result) {
// This property needs to remain unminified.
const { values, ['_$litType$']: type } = result;
// If $litType$ is a number, result is a plain TemplateResult and we get
// the template from the template cache. If not, result is a
// CompiledTemplateResult and _$litType$ is a CompiledTemplate and we need
// to create the <template> element the first time we see it.
const template = typeof type === 'number'
? this._$getTemplate(result)
: (type.el === undefined &&
(type.el = Template.createElement(trustFromTemplateString(type.h, type.h[0]), this.options)),
type);
if (this._$committedValue?._$template === template) {
this._$committedValue._update(values);
}
else {
const instance = new TemplateInstance(template, this);
const fragment = instance._clone(this.options);
instance._update(values);
this._commitNode(fragment);
this._$committedValue = instance;
}
}
// Overridden via `litHtmlPolyfillSupport` to provide platform support.
/** @internal */
_$getTemplate(result) {
let template = templateCache.get(result.strings);
if (template === undefined) {
templateCache.set(result.strings, (template = new Template(result)));
}
return template;
}
_commitIterable(value) {
// For an Iterable, we create a new InstancePart per item, then set its
// value to the item. This is a little bit of overhead for every item in
// an Iterable, but it lets us recurse easily and efficiently update Arrays
// of TemplateResults that will be commonly returned from expressions like:
// array.map((i) => html`${i}`), by reusing existing TemplateInstances.
// If value is an array, then the previous render was of an
// iterable and value will contain the ChildParts from the previous
// render. If value is not an array, clear this part and make a new
// array for ChildParts.
if (!isArray(this._$committedValue)) {
this._$committedValue = [];
this._$clear();
}
// Lets us keep track of how many items we stamped so we can clear leftover
// items from a previous render
const itemParts = this._$committedValue;
let partIndex = 0;
let itemPart;
for (const item of value) {
if (partIndex === itemParts.length) {
// If no existing part, create a new one
// TODO (justinfagnani): test perf impact of always creating two parts
// instead of sharing parts between nodes
// https://github.com/lit/lit/issues/1266
itemParts.push((itemPart = new ChildPart$1(this._insert(createMarker$1()), this._insert(createMarker$1()), this, this.options)));
}
else {
// Reuse an existing part
itemPart = itemParts[partIndex];
}
itemPart._$setValue(item);
partIndex++;
}
if (partIndex < itemParts.length) {
// itemParts always have end nodes
this._$clear(itemPart && wrap$1(itemPart._$endNode).nextSibling, partIndex);
// Truncate the parts array so _value reflects the current state
itemParts.length = partIndex;
}
}
/**
* Removes the nodes contained within this Part from the DOM.
*
* @param start Start node to clear from, for clearing a subset of the part's
* DOM (used when truncating iterables)
* @param from When `start` is specified, the index within the iterable from
* which ChildParts are being removed, used for disconnecting directives in
* those Parts.
*
* @internal
*/
_$clear(start = wrap$1(this._$startNode).nextSibling, from) {
this._$notifyConnectionChanged?.(false, true, from);
while (start && start !== this._$endNode) {
const n = wrap$1(start).nextSibling;
wrap$1(start).remove();
start = n;
}
}
/**
* Implementation of RootPart's `isConnected`. Note that this metod
* should only be called on `RootPart`s (the `ChildPart` returned from a
* top-level `render()` call). It has no effect on non-root ChildParts.
* @param isConnected Whether to set
* @internal
*/
setConnected(isConnected) {
if (this._$parent === undefined) {
this.__isConnected = isConnected;
this._$notifyConnectionChanged?.(isConnected);
}
}
}
class AttributePart {
get tagName() {
return this.element.tagName;
}
// See comment in Disconnectable interface for why this is a getter
get _$isConnected() {
return this._$parent._$isConnected;
}
constructor(element, name, strings, parent, options) {
this.type = ATTRIBUTE_PART;
/** @internal */
this._$committedValue = nothing;
/** @internal */
this._$disconnectableChildren = undefined;
this.element = element;
this.name = name;
this._$parent = parent;
this.options = options;
if (strings.length > 2 || strings[0] !== '' || strings[1] !== '') {
this._$committedValue = new Array(strings.length - 1).fill(new String());
this.strings = strings;
}
else {
this._$committedValue = nothing;
}
}
/**
* Sets the value of this part by resolving the value from possibly multiple
* values and static strings and committing it to the DOM.
* If this part is single-valued, `this._strings` will be undefined, and the
* method will be called with a single value argument. If this part is
* multi-value, `this._strings` will be defined, and the method is called
* with the value array of the part's owning TemplateInstance, and an offset
* into the value array from which the values should be read.
* This method is overloaded this way to eliminate short-lived array slices
* of the template instance values, and allow a fast-path for single-valued
* parts.
*
* @param value The part value, or an array of values for multi-valued parts
* @param valueIndex the index to start reading values from. `undefined` for
* single-valued parts
* @param noCommit causes the part to not commit its value to the DOM. Used
* in hydration to prime attribute parts with their first-rendered value,
* but not set the attribute, and in SSR to no-op the DOM operation and
* capture the value for serialization.
*
* @internal
*/
_$setValue(value, directiveParent = this, valueIndex, noCommit) {
const strings = this.strings;
// Whether any of the values has changed, for dirty-checking
let change = false;
if (strings === undefined) {
// Single-value binding case
value = resolveDirective(this, value, directiveParent, 0);
change =
!isPrimitive$1(value) ||
(value !== this._$committedValue && value !== noChange);
if (change) {
this._$committedValue = value;
}
}
else {
// Interpolation case
const values = value;
value = strings[0];
let i, v;
for (i = 0; i < strings.length - 1; i++) {
v = resolveDirective(this, values[valueIndex + i], directiveParent, i);
if (v === noChange) {
// If the user-provided value is `noChange`, use the previous value
v = this._$committedValue[i];
}
change ||=
!isPrimitive$1(v) || v !== this._$committedValue[i];
if (v === nothing) {
value = nothing;
}
else if (value !== nothing) {
value += (v ?? '') + strings[i + 1];
}
// We always record each value, even if one is `nothing`, for future
// change detection.
this._$committedValue[i] = v;
}
}
if (change && !noCommit) {
this._commitValue(value);
}
}
/** @internal */
_commitValue(value) {
if (value === nothing) {
wrap$1(this.element).removeAttribute(this.name);
}
else {
wrap$1(this.element).setAttribute(this.name, (value ?? ''));
}
}
}
class PropertyPart extends AttributePart {
constructor() {
super(...arguments);
this.type = PROPERTY_PART;
}
/** @internal */
_commitValue(value) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.element[this.name] = value === nothing ? undefined : value;
}
}
class BooleanAttributePart extends AttributePart {
constructor() {
super(...arguments);
this.type = BOOLEAN_ATTRIBUTE_PART;
}
/** @internal */
_commitValue(value) {
wrap$1(this.element).toggleAttribute(this.name, !!value && value !== nothing);
}
}
class EventPart extends AttributePart {
constructor(element, name, strings, parent, options) {
super(element, name, strings, parent, options);
this.type = EVENT_PART;
}
// EventPart does not use the base _$setValue/_resolveValue implementation
// since the dirty checking is more complex
/** @internal */
_$setValue(newListener, directiveParent = this) {
newListener =
resolveDirective(this, newListener, directiveParent, 0) ?? nothing;
if (newListener === noChange) {
return;
}
const oldListener = this._$committedValue;
// If the new value is nothing or any options change we have to remove the
// part as a listener.
const shouldRemoveListener = (newListener === nothing && oldListener !== nothing) ||
newListener.capture !==
oldListener.capture ||
newListener.once !==
oldListener.once ||
newListener.passive !==
oldListener.passive;
// If the new value is not nothing and we removed the listener, we have
// to add the part as a listener.
const shouldAddListener = newListener !== nothing &&
(oldListener === nothing || shouldRemoveListener);
if (shouldRemoveListener) {
this.element.removeEventListener(this.name, this, oldListener);
}
if (shouldAddListener) {
// Beware: IE11 and Chrome 41 don't like using the listener as the
// options object. Figure out how to deal w/ this in IE11 - maybe
// patch addEventListener?
this.element.addEventListener(this.name, this, newListener);
}
this._$committedValue = newListener;
}
handleEvent(event) {
if (typeof this._$committedValue === 'function') {
this._$committedValue.call(this.options?.host ?? this.element, event);
}
else {
this._$committedValue.handleEvent(event);
}
}
}
class ElementPart {
constructor(element, parent, options) {
this.element = element;
this.type = ELEMENT_PART;
/** @internal */
this._$disconnectableChildren = undefined;
this._$parent = parent;
this.options = options;
}
// See comment in Disconnectable interface for why this is a getter
get _$isConnected() {
return this._$parent._$isConnected;
}
_$setValue(value) {
resolveDirective(this, value);
}
}
/**
* END USERS SHOULD NOT RELY ON THIS OBJECT.
*
* Private exports for use by other Lit packages, not intended for use by
* external users.
*
* We currently do not make a mangled rollup build of the lit-ssr code. In order
* to keep a number of (otherwise private) top-level exports mangled in the
* client side code, we export a _$LH object containing those members (or
* helper methods for accessing private fields of those members), and then
* re-export them for use in lit-ssr. This keeps lit-ssr agnostic to whether the
* client-side code is being used in `dev` mode or `prod` mode.
*
* This has a unique name, to disambiguate it from private exports in
* lit-element, which re-exports all of lit-html.
*
* @private
*/
const _$LH = {
// Used in lit-ssr
_boundAttributeSuffix: boundAttributeSuffix,
_marker: marker,
_markerMatch: markerMatch,
_HTML_RESULT: HTML_RESULT$1,
_getTemplateHtml: getTemplateHtml,
// Used in tests and private-ssr-support
_TemplateInstance: TemplateInstance,
_isIterable: isIterable,
_resolveDirective: resolveDirective,
_ChildPart: ChildPart$1,
_AttributePart: AttributePart,
_BooleanAttributePart: BooleanAttributePart,
_EventPart: EventPart,
_PropertyPart: PropertyPart,
_ElementPart: ElementPart,
};
// Apply polyfills if available
const polyfillSupport$1 = global.litHtmlPolyfillSupport;
polyfillSupport$1?.(Template, ChildPart$1);
// IMPORTANT: do not change the property name or the assignment expression.
// This line will be used in regexes to search for lit-html usage.
(global.litHtmlVersions ??= []).push('3.1.2');
/**
* Renders a value, usually a lit-html TemplateResult, to the container.
*
* This example renders the text "Hello, Zoe!" inside a paragraph tag, appending
* it to the container `document.body`.
*
* ```js
* import {html, render} from 'lit';
*
* const name = "Zoe";
* render(html`<p>Hello, ${name}!</p>`, document.body);
* ```
*
* @param value Any [renderable
* value](https://lit.dev/docs/templates/expressions/#child-expressions),
* typically a {@linkcode TemplateResult} created by evaluating a template tag
* like {@linkcode html} or {@linkcode svg}.
* @param container A DOM container to render to. The first render will append
* the rendered value to the container, and subsequent renders will
* efficiently update the rendered value if the same result type was
* previously rendered there.
* @param options See {@linkcode RenderOptions} for options documentation.
* @see
* {@link https://lit.dev/docs/libraries/standalone-templates/#rendering-lit-html-templates| Rendering Lit HTML Templates}
*/
const render = (value, container, options) => {
const partOwnerNode = options?.renderBefore ?? container;
// This property needs to remain unminified.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let part = partOwnerNode['_$litPart$'];
if (part === undefined) {
const endNode = options?.renderBefore ?? null;
// This property needs to remain unminified.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
partOwnerNode['_$litPart$'] = part = new ChildPart$1(container.insertBefore(createMarker$1(), endNode), endNode, undefined, options ?? {});
}
part._$setValue(value);
return part;
};
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* 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;
/**
* Base element class that manages element properties and attributes, and
* renders a lit-html template.
*
* To define a component, subclass `LitElement` and implement a
* `render` method to provide the component's template. Define properties
* using the {@linkcode LitElement.properties properties} property or the
* {@linkcode property} decorator.
*/
class LitElement extends ReactiveElement {
constructor() {
super(...arguments);
/**
* @category rendering
*/
this.renderOptions = { host: this };
this.__childPart = undefined;
}
/**
* @category rendering
*/
createRenderRoot() {
const renderRoot = super.createRenderRoot();
// When adoptedStyleSheets are shimmed, they are inserted into the
// shadowRoot by createRenderRoot. Adjust the renderBefore node so that
// any styles in Lit content render before adoptedStyleSheets. This is
// important so that adoptedStyleSheets have precedence over styles in
// the shadowRoot.
this.renderOptions.renderBefore ??= renderRoot.firstChild;
return renderRoot;
}
/**
* Updates the element. This method reflects property values to attributes
* and calls `render` to render DOM via lit-html. Setting properties inside
* this method will *not* trigger another update.
* @param changedProperties Map of changed properties with old values
* @category updates
*/
update(changedProperties) {
// Setting properties in `render` should not trigger an update. Since
// updates are allowed after super.update, it's important to call `render`
// before that.
const value = this.render();
if (!this.hasUpdated) {
this.renderOptions.isConnected = this.isConnected;
}
super.update(changedProperties);
this.__childPart = render(value, this.renderRoot, this.renderOptions);
}
/**
* Invoked when the component is added to the document's DOM.
*
* In `connectedCallback()` you should setup tasks that should only occur when
* the element is connected to the document. The most common of these is
* adding event listeners to nodes external to the element, like a keydown
* event handler added to the window.
*
* ```ts
* connectedCallback() {
* super.connectedCallback();
* addEventListener('keydown', this._handleKeydown);
* }
* ```
*
* Typically, anything done in `connectedCallback()` should be undone when the
* element is disconnected, in `disconnectedCallback()`.
*
* @category lifecycle
*/
connectedCallback() {
super.connectedCallback();
this.__childPart?.setConnected(true);
}
/**
* Invoked when the component is removed from the document's DOM.
*
* This callback is the main signal to the element that it may no longer be
* used. `disconnectedCallback()` should ensure that nothing is holding a
* reference to the element (such as event listeners added to nodes external
* to the element), so that it is free to be garbage collected.
*
* ```ts
* disconnectedCallback() {
* super.disconnectedCallback();
* window.removeEventListener('keydown', this._handleKeydown);
* }
* ```
*
* An element may be re-connected after being disconnected.
*
* @category lifecycle
*/
disconnectedCallback() {
super.disconnectedCallback();
this.__childPart?.setConnected(false);
}
/**
* Invoked on each update to perform rendering tasks. This method may return
* any value renderable by lit-html's `ChildPart` - typically a
* `TemplateResult`. Setting properties inside this method will *not* trigger
* the element to update.
* @category rendering
*/
render() {
return noChange;
}
}
// This property needs to remain unminified.
LitElement['_$litElement$'] = true;
/**
* Ensure this class is marked as `finalized` as an optimization ensuring
* it will not needlessly try to `finalize`.
*
* Note this property name is a string to prevent breaking Closure JS Compiler
* optimizations. See @lit/reactive-element for more information.
*/
LitElement[JSCompiler_renameProperty('finalized')] = true;
// Install hydration if available
globalThis.litElementHydrateSupport?.({ LitElement });
// Apply polyfills if available
const polyfillSupport = globalThis.litElementPolyfillSupport;
polyfillSupport?.({ LitElement });
/**
* END USERS SHOULD NOT RELY ON THIS OBJECT.
*
* Private exports for use by other Lit packages, not intended for use by
* external users.
*
* We currently do not make a mangled rollup build of the lit-ssr code. In order
* to keep a number of (otherwise private) top-level exports mangled in the
* client side code, we export a _$LE object containing those members (or
* helper methods for accessing private fields of those members), and then
* re-export them for use in lit-ssr. This keeps lit-ssr agnostic to whether the
* client-side code is being used in `dev` mode or `prod` mode.
*
* This has a unique name, to disambiguate it from private exports in
* lit-html, since this module re-exports all of lit-html.
*
* @private
*/
const _$LE = {
_$attributeToProperty: (el, name, value) => {
// eslint-disable-next-line
el._$attributeToProperty(name, value);
},
// eslint-disable-next-line
_$changedProperties: (el) => el._$changedProperties,
};
// IMPORTANT: do not change the property name or the assignment expression.
// This line will be used in regexes to search for LitElement usage.
(globalThis.litElementVersions ??= []).push('4.0.4');
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* @fileoverview
*
* This file exports a boolean const whose value will depend on what environment
* the module is being imported from.
*/
const NODE_MODE = false;
/**
* A boolean that will be `true` in server environments like Node, and `false`
* in browser environments. Note that your server environment or toolchain must
* support the `"node"` export condition for this to be `true`.
*
* This can be used when authoring components to change behavior based on
* whether or not the component is executing in an SSR context.
*/
const isServer = NODE_MODE;
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const { _ChildPart: ChildPart } = _$LH;
const wrap = (node) => node;
/**
* Tests if a value is a primitive value.
*
* See https://tc39.github.io/ecma262/#sec-typeof-operator
*/
const isPrimitive = (value) => value === null || (typeof value != 'object' && typeof value != 'function');
const TemplateResultType = {
HTML: 1,
SVG: 2,
};
/**
* Tests if a value is a TemplateResult or a CompiledTemplateResult.
*/
const isTemplateResult = (value, type) => type === undefined
? // This property needs to remain unminified.
value?.['_$litType$'] !== undefined
: value?.['_$litType$'] === type;
/**
* Tests if a value is a CompiledTemplateResult.
*/
const isCompiledTemplateResult = (value) => {
return value?.['_$litType$']?.h != null;
};
/**
* Tests if a value is a DirectiveResult.
*/
const isDirectiveResult = (value) =>
// This property needs to remain unminified.
value?.['_$litDirective$'] !== undefined;
/**
* Retrieves the Directive class for a DirectiveResult
*/
const getDirectiveClass = (value) =>
// This property needs to remain unminified.
value?.['_$litDirective$'];
/**
* Tests whether a part has only a single-expression with no strings to
* interpolate between.
*
* Only AttributePart and PropertyPart can have multiple expressions.
* Multi-expression parts have a `strings` property and single-expression
* parts do not.
*/
const isSingleExpression = (part) => part.strings === undefined;
const createMarker = () => document.createComment('');
/**
* Inserts a ChildPart into the given container ChildPart's DOM, either at the
* end of the container ChildPart, or before the optional `refPart`.
*
* This does not add the part to the containerPart's committed value. That must
* be done by callers.
*
* @param containerPart Part within which to add the new ChildPart
* @param refPart Part before which to add the new ChildPart; when omitted the
* part added to the end of the `containerPart`
* @param part Part to insert, or undefined to create a new part
*/
const insertPart = (containerPart, refPart, part) => {
const container = wrap(containerPart._$startNode).parentNode;
const refNode = refPart === undefined ? containerPart._$endNode : refPart._$startNode;
if (part === undefined) {
const startNode = wrap(container).insertBefore(createMarker(), refNode);
const endNode = wrap(container).insertBefore(createMarker(), refNode);
part = new ChildPart(startNode, endNode, containerPart, containerPart.options);
}
else {
const endNode = wrap(part._$endNode).nextSibling;
const oldParent = part._$parent;
const parentChanged = oldParent !== containerPart;
if (parentChanged) {
part._$reparentDisconnectables?.(containerPart);
// Note that although `_$reparentDisconnectables` updates the part's
// `_$parent` reference after unlinking from its current parent, that
// method only exists if Disconnectables are present, so we need to
// unconditionally set it here
part._$parent = containerPart;
// Since the _$isConnected getter is somewhat costly, only
// read it once we know the subtree has directives that need
// to be notified
let newConnectionState;
if (part._$notifyConnectionChanged !== undefined &&
(newConnectionState = containerPart._$isConnected) !==
oldParent._$isConnected) {
part._$notifyConnectionChanged(newConnectionState);
}
}
if (endNode !== refNode || parentChanged) {
let start = part._$startNode;
while (start !== endNode) {
const n = wrap(start).nextSibling;
wrap(container).insertBefore(start, refNode);
start = n;
}
}
}
return part;
};
/**
* Sets the value of a Part.
*
* Note that this should only be used to set/update the value of user-created
* parts (i.e. those created using `insertPart`); it should not be used
* by directives to set the value of the directive's container part. Directives
* should return a value from `update`/`render` to update their part state.
*
* For directives that require setting their part value asynchronously, they
* should extend `AsyncDirective` and call `this.setValue()`.
*
* @param part Part to set
* @param value Value to set
* @param index For `AttributePart`s, the index to set
* @param directiveParent Used internally; should not be set by user
*/
const setChildPartValue = (part, value, directiveParent = part) => {
part._$setValue(value, directiveParent);
return part;
};
// A sentinel value that can never appear as a part value except when set by
// live(). Used to force a dirty-check to fail and cause a re-render.
const RESET_VALUE = {};
/**
* Sets the committed value of a ChildPart directly without triggering the
* commit stage of the part.
*
* This is useful in cases where a directive needs to update the part such
* that the next update detects a value change or not. When value is omitted,
* the next update will be guaranteed to be detected as a change.
*
* @param part
* @param value
*/
const setCommittedValue = (part, value = RESET_VALUE) => (part._$committedValue = value);
/**
* Returns the committed value of a ChildPart.
*
* The committed value is used for change detection and efficient updates of
* the part. It can differ from the value set by the template or directive in
* cases where the template value is transformed before being committed.
*
* - `TemplateResult`s are committed as a `TemplateInstance`
* - Iterables are committed as `Array<ChildPart>`
* - All other types are committed as the template value or value returned or
* set by a directive.
*
* @param part
*/
const getCommittedValue = (part) => part._$committedValue;
/**
* Removes a ChildPart from the DOM, including any of its content.
*
* @param part The Part to remove
*/
const removePart = (part) => {
part._$notifyConnectionChanged?.(false, true);
let start = part._$startNode;
const end = wrap(part._$endNode).nextSibling;
while (start !== end) {
const n = wrap(start).nextSibling;
wrap(start).remove();
start = n;
}
};
const clearPart = (part) => {
part._$clear();
};
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const PartType = {
ATTRIBUTE: 1,
CHILD: 2,
PROPERTY: 3,
BOOLEAN_ATTRIBUTE: 4,
EVENT: 5,
ELEMENT: 6,
};
/**
* Creates a user-facing directive function from a Directive class. This
* function has the same parameters as the directive's render() method.
*/
const directive = (c) => (...values) => ({
// This property needs to remain unminified.
['_$litDirective$']: c,
values,
});
/**
* Base class for creating custom directives. Users should extend this class,
* implement `render` and/or `update`, and then pass their subclass to
* `directive`.
*/
class Directive {
constructor(_partInfo) { }
// See comment in Disconnectable interface for why this is a getter
get _$isConnected() {
return this._$parent._$isConnected;
}
/** @internal */
_$initialize(part, parent, attributeIndex) {
this.__part = part;
this._$parent = parent;
this.__attributeIndex = attributeIndex;
}
/** @internal */
_$resolve(part, props) {
return this.update(part, props);
}
update(_part, props) {
return this.render(...props);
}
}
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* Recursively walks down the tree of Parts/TemplateInstances/Directives to set
* the connected state of directives and run `disconnected`/ `reconnected`
* callbacks.
*
* @return True if there were children to disconnect; false otherwise
*/
const notifyChildrenConnectedChanged = (parent, isConnected) => {
const children = parent._$disconnectableChildren;
if (children === undefined) {
return false;
}
for (const obj of children) {
// The existence of `_$notifyDirectiveConnectionChanged` is used as a "brand" to
// disambiguate AsyncDirectives from other DisconnectableChildren
// (as opposed to using an instanceof check to know when to call it); the
// redundancy of "Directive" in the API name is to avoid conflicting with
// `_$notifyConnectionChanged`, which exists `ChildParts` which are also in
// this list
// Disconnect Directive (and any nested directives contained within)
// This property needs to remain unminified.
obj['_$notifyDirectiveConnectionChanged']?.(isConnected, false);
// Disconnect Part/TemplateInstance
notifyChildrenConnectedChanged(obj, isConnected);
}
return true;
};
/**
* Removes the given child from its parent list of disconnectable children, and
* if the parent list becomes empty as a result, removes the parent from its
* parent, and so forth up the tree when that causes subsequent parent lists to
* become empty.
*/
const removeDisconnectableFromParent = (obj) => {
let parent, children;
do {
if ((parent = obj._$parent) === undefined) {
break;
}
children = parent._$disconnectableChildren;
children.delete(obj);
obj = parent;
} while (children?.size === 0);
};
const addDisconnectableToParent = (obj) => {
// Climb the parent tree, creating a sparse tree of children needing
// disconnection
for (let parent; (parent = obj._$parent); obj = parent) {
let children = parent._$disconnectableChildren;
if (children === undefined) {
parent._$disconnectableChildren = children = new Set();
}
else if (children.has(obj)) {
// Once we've reached a parent that already contains this child, we
// can short-circuit
break;
}
children.add(obj);
installDisconnectAPI(parent);
}
};
/**
* Changes the parent reference of the ChildPart, and updates the sparse tree of
* Disconnectable children accordingly.
*
* Note, this method will be patched onto ChildPart instances and called from
* the core code when parts are moved between different parents.
*/
function reparentDisconnectables(newParent) {
if (this._$disconnectableChildren !== undefined) {
removeDisconnectableFromParent(this);
this._$parent = newParent;
addDisconnectableToParent(this);
}
else {
this._$parent = newParent;
}
}
/**
* Sets the connected state on any directives contained within the committed
* value of this part (i.e. within a TemplateInstance or iterable of
* ChildParts) and runs their `disconnected`/`reconnected`s, as well as within
* any directives stored on the ChildPart (when `valueOnly` is false).
*
* `isClearingValue` should be passed as `true` on a top-level part that is
* clearing itself, and not as a result of recursively disconnecting directives
* as part of a `clear` operation higher up the tree. This both ensures that any
* directive on this ChildPart that produced a value that caused the clear
* operation is not disconnected, and also serves as a performance optimization
* to avoid needless bookkeeping when a subtree is going away; when clearing a
* subtree, only the top-most part need to remove itself from the parent.
*
* `fromPartIndex` is passed only in the case of a partial `_clear` running as a
* result of truncating an iterable.
*
* Note, this method will be patched onto ChildPart instances and called from the
* core code when parts are cleared or the connection state is changed by the
* user.
*/
function notifyChildPartConnectedChanged(isConnected, isClearingValue = false, fromPartIndex = 0) {
const value = this._$committedValue;
const children = this._$disconnectableChildren;
if (children === undefined || children.size === 0) {
return;
}
if (isClearingValue) {
if (Array.isArray(value)) {
// Iterable case: Any ChildParts created by the iterable should be
// disconnected and removed from this ChildPart's disconnectable
// children (starting at `fromPartIndex` in the case of truncation)
for (let i = fromPartIndex; i < value.length; i++) {
notifyChildrenConnectedChanged(value[i], false);
removeDisconnectableFromParent(value[i]);
}
}
else if (value != null) {
// TemplateInstance case: If the value has disconnectable children (will
// only be in the case that it is a TemplateInstance), we disconnect it
// and remove it from this ChildPart's disconnectable children
notifyChildrenConnectedChanged(value, false);
removeDisconnectableFromParent(value);
}
}
else {
notifyChildrenConnectedChanged(this, isConnected);
}
}
/**
* Patches disconnection API onto ChildParts.
*/
const installDisconnectAPI = (obj) => {
if (obj.type == PartType.CHILD) {
obj._$notifyConnectionChanged ??=
notifyChildPartConnectedChanged;
obj._$reparentDisconnectables ??= reparentDisconnectables;
}
};
/**
* An abstract `Directive` base class whose `disconnected` method will be
* called when the part containing the directive is cleared as a result of
* re-rendering, or when the user calls `part.setConnected(false)` on
* a part that was previously rendered containing the directive (as happens
* when e.g. a LitElement disconnects from the DOM).
*
* If `part.setConnected(true)` is subsequently called on a
* containing part, the directive's `reconnected` method will be called prior
* to its next `update`/`render` callbacks. When implementing `disconnected`,
* `reconnected` should also be implemented to be compatible with reconnection.
*
* Note that updates may occur while the directive is disconnected. As such,
* directives should generally check the `this.isConnected` flag during
* render/update to determine whether it is safe to subscribe to resources
* that may prevent garbage collection.
*/
class AsyncDirective extends Directive {
constructor() {
super(...arguments);
// @internal
this._$disconnectableChildren = undefined;
}
/**
* Initialize the part with internal fields
* @param part
* @param parent
* @param attributeIndex
*/
_$initialize(part, parent, attributeIndex) {
super._$initialize(part, parent, attributeIndex);
addDisconnectableToParent(this);
this.isConnected = part._$isConnected;
}
// This property needs to remain unminified.
/**
* Called from the core code when a directive is going away from a part (in
* which case `shouldRemoveFromParent` should be true), and from the
* `setChildrenConnected` helper function when recursively changing the
* connection state of a tree (in which case `shouldRemoveFromParent` should
* be false).
*
* @param isConnected
* @param isClearingDirective - True when the directive itself is being
* removed; false when the tree is being disconnected
* @internal
*/
['_$notifyDirectiveConnectionChanged'](isConnected, isClearingDirective = true) {
if (isConnected !== this.isConnected) {
this.isConnected = isConnected;
if (isConnected) {
this.reconnected?.();
}
else {
this.disconnected?.();
}
}
if (isClearingDirective) {
notifyChildrenConnectedChanged(this, isConnected);
removeDisconnectableFromParent(this);
}
}
/**
* Sets the value of the directive's Part outside the normal `update`/`render`
* lifecycle of a directive.
*
* This method should not be called synchronously from a directive's `update`
* or `render`.
*
* @param directive The directive to update
* @param value The value to set
*/
setValue(value) {
if (isSingleExpression(this.__part)) {
this.__part._$setValue(value, this);
}
else {
const newValues = [...this.__part._$committedValue];
newValues[this.__attributeIndex] = value;
this.__part._$setValue(newValues, this, 0);
}
}
/**
* User callbacks for implementing logic to release any resources/subscriptions
* that may have been retained by this directive. Since directives may also be
* re-connected, `reconnected` should also be implemented to restore the
* working state of the directive prior to the next render.
*/
disconnected() { }
reconnected() { }
}
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
// Note, this module is not included in package exports so that it's private to
// our first-party directives. If it ends up being useful, we can open it up and
// export it.
/**
* Helper to iterate an AsyncIterable in its own closure.
* @param iterable The iterable to iterate
* @param callback The callback to call for each value. If the callback returns
* `false`, the loop will be broken.
*/
const forAwaitOf = async (iterable, callback) => {
for await (const v of iterable) {
if ((await callback(v)) === false) {
return;
}
}
};
/**
* Holds a reference to an instance that can be disconnected and reconnected,
* so that a closure over the ref (e.g. in a then function to a promise) does
* not strongly hold a ref to the instance. Approximates a WeakRef but must
* be manually connected & disconnected to the backing instance.
*/
class PseudoWeakRef {
constructor(ref) {
this._ref = ref;
}
/**
* Disassociates the ref with the backing instance.
*/
disconnect() {
this._ref = undefined;
}
/**
* Reassociates the ref with the backing instance.
*/
reconnect(ref) {
this._ref = ref;
}
/**
* Retrieves the backing instance (will be undefined when disconnected)
*/
deref() {
return this._ref;
}
}
/**
* A helper to pause and resume waiting on a condition in an async function
*/
class Pauser {
constructor() {
this._promise = undefined;
this._resolve = undefined;
}
/**
* When paused, returns a promise to be awaited; when unpaused, returns
* undefined. Note that in the microtask between the pauser being resumed
* an an await of this promise resolving, the pauser could be paused again,
* hence callers should check the promise in a loop when awaiting.
* @returns A promise to be awaited when paused or undefined
*/
get() {
return this._promise;
}
/**
* Creates a promise to be awaited
*/
pause() {
this._promise ??= new Promise((resolve) => (this._resolve = resolve));
}
/**
* Resolves the promise which may be awaited
*/
resume() {
this._resolve?.();
this._promise = this._resolve = undefined;
}
}
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
class AsyncReplaceDirective extends AsyncDirective {
constructor() {
super(...arguments);
this.__weakThis = new PseudoWeakRef(this);
this.__pauser = new Pauser();
}
// @ts-expect-error value not used, but we want a nice parameter for docs
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render(value, _mapper) {
return noChange;
}
update(_part, [value, mapper]) {
// If our initial render occurs while disconnected, ensure that the pauser
// and weakThis are in the disconnected state
if (!this.isConnected) {
this.disconnected();
}
// If we've already set up this particular iterable, we don't need
// to do anything.
if (value === this.__value) {
return noChange;
}
this.__value = value;
let i = 0;
const { __weakThis: weakThis, __pauser: pauser } = this;
// Note, the callback avoids closing over `this` so that the directive
// can be gc'ed before the promise resolves; instead `this` is retrieved
// from `weakThis`, which can break the hard reference in the closure when
// the directive disconnects
forAwaitOf(value, async (v) => {
// The while loop here handles the case that the connection state
// thrashes, causing the pauser to resume and then get re-paused
while (pauser.get()) {
await pauser.get();
}
// If the callback gets here and there is no `this`, it means that the
// directive has been disconnected and garbage collected and we don't
// need to do anything else
const _this = weakThis.deref();
if (_this !== undefined) {
// Check to make sure that value is the still the current value of
// the part, and if not bail because a new value owns this part
if (_this.__value !== value) {
return false;
}
// As a convenience, because functional-programming-style
// transforms of iterables and async iterables requires a library,
// we accept a mapper function. This is especially convenient for
// rendering a template for each item.
if (mapper !== undefined) {
v = mapper(v, i);
}
_this.commitValue(v, i);
i++;
}
return true;
});
return noChange;
}
// Override point for AsyncAppend to append rather than replace
commitValue(value, _index) {
this.setValue(value);
}
disconnected() {
this.__weakThis.disconnect();
this.__pauser.pause();
}
reconnected() {
this.__weakThis.reconnect(this);
this.__pauser.resume();
}
}
/**
* A directive that renders the items of an async iterable[1], replacing
* previous values with new values, so that only one value is ever rendered
* at a time. This directive may be used in any expression type.
*
* Async iterables are objects with a `[Symbol.asyncIterator]` method, which
* returns an iterator who's `next()` method returns a Promise. When a new
* value is available, the Promise resolves and the value is rendered to the
* Part controlled by the directive. If another value other than this
* directive has been set on the Part, the iterable will no longer be listened
* to and new values won't be written to the Part.
*
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
*
* @param value An async iterable
* @param mapper An optional function that maps from (value, index) to another
* value. Useful for generating templates for each item in the iterable.
*/
const asyncReplace = directive(AsyncReplaceDirective);
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
class AsyncAppendDirective extends AsyncReplaceDirective {
// Override AsyncReplace to narrow the allowed part type to ChildPart only
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.CHILD) {
throw new Error('asyncAppend can only be used in child expressions');
}
}
// Override AsyncReplace to save the part since we need to append into it
update(part, params) {
this.__childPart = part;
return super.update(part, params);
}
// Override AsyncReplace to append rather than replace
commitValue(value, index) {
// When we get the first value, clear the part. This lets the
// previous value display until we can replace it.
if (index === 0) {
clearPart(this.__childPart);
}
// Create and insert a new part and set its value to the next value
const newPart = insertPart(this.__childPart);
setChildPartValue(newPart, value);
}
}
/**
* A directive that renders the items of an async iterable[1], appending new
* values after previous values, similar to the built-in support for iterables.
* This directive is usable only in child expressions.
*
* Async iterables are objects with a [Symbol.asyncIterator] method, which
* returns an iterator who's `next()` method returns a Promise. When a new
* value is available, the Promise resolves and the value is appended to the
* Part controlled by the directive. If another value other than this
* directive has been set on the Part, the iterable will no longer be listened
* to and new values won't be written to the Part.
*
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
*
* @param value An async iterable
* @param mapper An optional function that maps from (value, index) to another
* value. Useful for generating templates for each item in the iterable.
*/
const asyncAppend = directive(AsyncAppendDirective);
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* The template strings array contents are not compatible between the two
* template result types as the compiled template contains a prepared string;
* only use the returned template strings array as a cache key.
*/
const getStringsFromTemplateResult = (result) => isCompiledTemplateResult(result) ? result['_$litType$'].h : result.strings;
class CacheDirective extends Directive {
constructor(partInfo) {
super(partInfo);
this._templateCache = new WeakMap();
}
render(v) {
// Return an array of the value to induce lit-html to create a ChildPart
// for the value that we can move into the cache.
return [v];
}
update(containerPart, [v]) {
const _valueKey = isTemplateResult(this._value)
? getStringsFromTemplateResult(this._value)
: null;
const vKey = isTemplateResult(v) ? getStringsFromTemplateResult(v) : null;
// If the previous value is a TemplateResult and the new value is not,
// or is a different Template as the previous value, move the child part
// into the cache.
if (_valueKey !== null && (vKey === null || _valueKey !== vKey)) {
// This is always an array because we return [v] in render()
const partValue = getCommittedValue(containerPart);
const childPart = partValue.pop();
let cachedContainerPart = this._templateCache.get(_valueKey);
if (cachedContainerPart === undefined) {
const fragment = document.createDocumentFragment();
cachedContainerPart = render(nothing, fragment);
cachedContainerPart.setConnected(false);
this._templateCache.set(_valueKey, cachedContainerPart);
}
// Move into cache
setCommittedValue(cachedContainerPart, [childPart]);
insertPart(cachedContainerPart, undefined, childPart);
}
// If the new value is a TemplateResult and the previous value is not,
// or is a different Template as the previous value, restore the child
// part from the cache.
if (vKey !== null) {
if (_valueKey === null || _valueKey !== vKey) {
const cachedContainerPart = this._templateCache.get(vKey);
if (cachedContainerPart !== undefined) {
// Move the cached part back into the container part value
const partValue = getCommittedValue(cachedContainerPart);
const cachedPart = partValue.pop();
// Move cached part back into DOM
clearPart(containerPart);
insertPart(containerPart, undefined, cachedPart);
setCommittedValue(containerPart, [cachedPart]);
}
}
// Because vKey is non null, v must be a TemplateResult.
this._value = v;
}
else {
this._value = undefined;
}
return this.render(v);
}
}
/**
* Enables fast switching between multiple templates by caching the DOM nodes
* and TemplateInstances produced by the templates.
*
* Example:
*
* ```js
* let checked = false;
*
* html`
* ${cache(checked ? html`input is checked` : html`input is not checked`)}
* `
* ```
*/
const cache = directive(CacheDirective);
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* Chooses and evaluates a template function from a list based on matching
* the given `value` to a case.
*
* Cases are structured as `[caseValue, func]`. `value` is matched to
* `caseValue` by strict equality. The first match is selected. Case values
* can be of any type including primitives, objects, and symbols.
*
* This is similar to a switch statement, but as an expression and without
* fallthrough.
*
* @example
*
* ```ts
* render() {
* return html`
* ${choose(this.section, [
* ['home', () => html`<h1>Home</h1>`],
* ['about', () => html`<h1>About</h1>`]
* ],
* () => html`<h1>Error</h1>`)}
* `;
* }
* ```
*/
const choose = (value, cases, defaultCase) => {
for (const c of cases) {
const caseValue = c[0];
if (caseValue === value) {
const fn = c[1];
return fn();
}
}
return defaultCase?.();
};
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
class ClassMapDirective extends Directive {
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.ATTRIBUTE ||
partInfo.name !== 'class' ||
partInfo.strings?.length > 2) {
throw new Error('`classMap()` can only be used in the `class` attribute ' +
'and must be the only part in the attribute.');
}
}
render(classInfo) {
// Add spaces to ensure separation from static classes
return (' ' +
Object.keys(classInfo)
.filter((key) => classInfo[key])
.join(' ') +
' ');
}
update(part, [classInfo]) {
// Remember dynamic classes on the first render
if (this._previousClasses === undefined) {
this._previousClasses = new Set();
if (part.strings !== undefined) {
this._staticClasses = new Set(part.strings
.join(' ')
.split(/\s/)
.filter((s) => s !== ''));
}
for (const name in classInfo) {
if (classInfo[name] && !this._staticClasses?.has(name)) {
this._previousClasses.add(name);
}
}
return this.render(classInfo);
}
const classList = part.element.classList;
// Remove old classes that no longer apply
for (const name of this._previousClasses) {
if (!(name in classInfo)) {
classList.remove(name);
this._previousClasses.delete(name);
}
}
// Add or remove classes based on their classMap value
for (const name in classInfo) {
// We explicitly want a loose truthy check of `value` because it seems
// more convenient that '' and 0 are skipped.
const value = !!classInfo[name];
if (value !== this._previousClasses.has(name) &&
!this._staticClasses?.has(name)) {
if (value) {
classList.add(name);
this._previousClasses.add(name);
}
else {
classList.remove(name);
this._previousClasses.delete(name);
}
}
}
return noChange;
}
}
/**
* A directive that applies dynamic CSS classes.
*
* This must be used in the `class` attribute and must be the only part used in
* the attribute. It takes each property in the `classInfo` argument and adds
* the property name to the element's `classList` if the property value is
* truthy; if the property value is falsey, the property name is removed from
* the element's `class`.
*
* For example `{foo: bar}` applies the class `foo` if the value of `bar` is
* truthy.
*
* @param classInfo
*/
const classMap = directive(ClassMapDirective);
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
// A sentinel that indicates guard() hasn't rendered anything yet
const initialValue = {};
class GuardDirective extends Directive {
constructor() {
super(...arguments);
this._previousValue = initialValue;
}
render(_value, f) {
return f();
}
update(_part, [value, f]) {
if (Array.isArray(value)) {
// Dirty-check arrays by item
if (Array.isArray(this._previousValue) &&
this._previousValue.length === value.length &&
value.every((v, i) => v === this._previousValue[i])) {
return noChange;
}
}
else if (this._previousValue === value) {
// Dirty-check non-arrays by identity
return noChange;
}
// Copy the value if it's an array so that if it's mutated we don't forget
// what the previous values were.
this._previousValue = Array.isArray(value) ? Array.from(value) : value;
const r = this.render(value, f);
return r;
}
}
/**
* Prevents re-render of a template function until a single value or an array of
* values changes.
*
* Values are checked against previous values with strict equality (`===`), and
* so the check won't detect nested property changes inside objects or arrays.
* Arrays values have each item checked against the previous value at the same
* index with strict equality. Nested arrays are also checked only by strict
* equality.
*
* Example:
*
* ```js
* html`
* <div>
* ${guard([user.id, company.id], () => html`...`)}
* </div>
* `
* ```
*
* In this case, the template only rerenders if either `user.id` or `company.id`
* changes.
*
* guard() is useful with immutable data patterns, by preventing expensive work
* until data updates.
*
* Example:
*
* ```js
* html`
* <div>
* ${guard([immutableItems], () => immutableItems.map(i => html`${i}`))}
* </div>
* `
* ```
*
* In this case, items are mapped over only when the array reference changes.
*
* @param value the value to check before re-rendering
* @param f the template function
*/
const guard = directive(GuardDirective);
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* For AttributeParts, sets the attribute if the value is defined and removes
* the attribute if the value is undefined.
*
* For other part types, this directive is a no-op.
*/
const ifDefined = (value) => value ?? nothing;
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
function* join(items, joiner) {
const isFunction = typeof joiner === 'function';
if (items !== undefined) {
let i = -1;
for (const value of items) {
if (i > -1) {
yield isFunction ? joiner(i) : joiner;
}
i++;
yield value;
}
}
}
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
class Keyed extends Directive {
constructor() {
super(...arguments);
this.key = nothing;
}
render(k, v) {
this.key = k;
return v;
}
update(part, [k, v]) {
if (k !== this.key) {
// Clear the part before returning a value. The one-arg form of
// setCommittedValue sets the value to a sentinel which forces a
// commit the next render.
setCommittedValue(part);
this.key = k;
}
return v;
}
}
/**
* Associates a renderable value with a unique key. When the key changes, the
* previous DOM is removed and disposed before rendering the next value, even
* if the value - such as a template - is the same.
*
* This is useful for forcing re-renders of stateful components, or working
* with code that expects new data to generate new HTML elements, such as some
* animation techniques.
*/
const keyed = directive(Keyed);
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
class LiveDirective extends Directive {
constructor(partInfo) {
super(partInfo);
if (!(partInfo.type === PartType.PROPERTY ||
partInfo.type === PartType.ATTRIBUTE ||
partInfo.type === PartType.BOOLEAN_ATTRIBUTE)) {
throw new Error('The `live` directive is not allowed on child or event bindings');
}
if (!isSingleExpression(partInfo)) {
throw new Error('`live` bindings can only contain a single expression');
}
}
render(value) {
return value;
}
update(part, [value]) {
if (value === noChange || value === nothing) {
return value;
}
const element = part.element;
const name = part.name;
if (part.type === PartType.PROPERTY) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (value === element[name]) {
return noChange;
}
}
else if (part.type === PartType.BOOLEAN_ATTRIBUTE) {
if (!!value === element.hasAttribute(name)) {
return noChange;
}
}
else if (part.type === PartType.ATTRIBUTE) {
if (element.getAttribute(name) === String(value)) {
return noChange;
}
}
// Resets the part's value, causing its dirty-check to fail so that it
// always sets the value.
setCommittedValue(part);
return value;
}
}
/**
* Checks binding values against live DOM values, instead of previously bound
* values, when determining whether to update the value.
*
* This is useful for cases where the DOM value may change from outside of
* lit-html, such as with a binding to an `<input>` element's `value` property,
* a content editable elements text, or to a custom element that changes it's
* own properties or attributes.
*
* In these cases if the DOM value changes, but the value set through lit-html
* bindings hasn't, lit-html won't know to update the DOM value and will leave
* it alone. If this is not what you want--if you want to overwrite the DOM
* value with the bound value no matter what--use the `live()` directive:
*
* ```js
* html`<input .value=${live(x)}>`
* ```
*
* `live()` performs a strict equality check against the live DOM value, and if
* the new value is equal to the live value, does nothing. This means that
* `live()` should not be used when the binding will cause a type conversion. If
* you use `live()` with an attribute binding, make sure that only strings are
* passed in, or the binding will update every render.
*/
const live = directive(LiveDirective);
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* Returns an iterable containing the result of calling `f(value)` on each
* value in `items`.
*
* @example
*
* ```ts
* render() {
* return html`
* <ul>
* ${map(items, (i) => html`<li>${i}</li>`)}
* </ul>
* `;
* }
* ```
*/
function* map(items, f) {
if (items !== undefined) {
let i = 0;
for (const value of items) {
yield f(value, i++);
}
}
}
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
function* range(startOrEnd, end, step = 1) {
const start = end === undefined ? 0 : startOrEnd;
end ??= startOrEnd;
for (let i = start; step > 0 ? i < end : end < i; i += step) {
yield i;
}
}
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* Creates a new Ref object, which is container for a reference to an element.
*/
const createRef = () => new Ref();
/**
* An object that holds a ref value.
*/
class Ref {
}
// When callbacks are used for refs, this map tracks the last value the callback
// was called with, for ensuring a directive doesn't clear the ref if the ref
// has already been rendered to a new spot. It is double-keyed on both the
// context (`options.host`) and the callback, since we auto-bind class methods
// to `options.host`.
const lastElementForContextAndCallback = new WeakMap();
class RefDirective extends AsyncDirective {
render(_ref) {
return nothing;
}
update(part, [ref]) {
const refChanged = ref !== this._ref;
if (refChanged && this._ref !== undefined) {
// The ref passed to the directive has changed;
// unset the previous ref's value
this._updateRefValue(undefined);
}
if (refChanged || this._lastElementForRef !== this._element) {
// We either got a new ref or this is the first render;
// store the ref/element & update the ref value
this._ref = ref;
this._context = part.options?.host;
this._updateRefValue((this._element = part.element));
}
return nothing;
}
_updateRefValue(element) {
if (typeof this._ref === 'function') {
// If the current ref was called with a previous value, call with
// `undefined`; We do this to ensure callbacks are called in a consistent
// way regardless of whether a ref might be moving up in the tree (in
// which case it would otherwise be called with the new value before the
// previous one unsets it) and down in the tree (where it would be unset
// before being set). Note that element lookup is keyed by
// both the context and the callback, since we allow passing unbound
// functions that are called on options.host, and we want to treat
// these as unique "instances" of a function.
const context = this._context ?? globalThis;
let lastElementForCallback = lastElementForContextAndCallback.get(context);
if (lastElementForCallback === undefined) {
lastElementForCallback = new WeakMap();
lastElementForContextAndCallback.set(context, lastElementForCallback);
}
if (lastElementForCallback.get(this._ref) !== undefined) {
this._ref.call(this._context, undefined);
}
lastElementForCallback.set(this._ref, element);
// Call the ref with the new element value
if (element !== undefined) {
this._ref.call(this._context, element);
}
}
else {
this._ref.value = element;
}
}
get _lastElementForRef() {
return typeof this._ref === 'function'
? lastElementForContextAndCallback
.get(this._context ?? globalThis)
?.get(this._ref)
: this._ref?.value;
}
disconnected() {
// Only clear the box if our element is still the one in it (i.e. another
// directive instance hasn't rendered its element to it before us); that
// only happens in the event of the directive being cleared (not via manual
// disconnection)
if (this._lastElementForRef === this._element) {
this._updateRefValue(undefined);
}
}
reconnected() {
// If we were manually disconnected, we can safely put our element back in
// the box, since no rendering could have occurred to change its state
this._updateRefValue(this._element);
}
}
/**
* Sets the value of a Ref object or calls a ref callback with the element it's
* bound to.
*
* A Ref object acts as a container for a reference to an element. A ref
* callback is a function that takes an element as its only argument.
*
* The ref directive sets the value of the Ref object or calls the ref callback
* during rendering, if the referenced element changed.
*
* Note: If a ref callback is rendered to a different element position or is
* removed in a subsequent render, it will first be called with `undefined`,
* followed by another call with the new element it was rendered to (if any).
*
* ```js
* // Using Ref object
* const inputRef = createRef();
* render(html`<input ${ref(inputRef)}>`, container);
* inputRef.value.focus();
*
* // Using callback
* const callback = (inputElement) => inputElement.focus();
* render(html`<input ${ref(callback)}>`, container);
* ```
*/
const ref = directive(RefDirective);
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
// Helper for generating a map of array item to its index over a subset
// of an array (used to lazily generate `newKeyToIndexMap` and
// `oldKeyToIndexMap`)
const generateMap = (list, start, end) => {
const map = new Map();
for (let i = start; i <= end; i++) {
map.set(list[i], i);
}
return map;
};
class RepeatDirective extends Directive {
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.CHILD) {
throw new Error('repeat() can only be used in text expressions');
}
}
_getValuesAndKeys(items, keyFnOrTemplate, template) {
let keyFn;
if (template === undefined) {
template = keyFnOrTemplate;
}
else if (keyFnOrTemplate !== undefined) {
keyFn = keyFnOrTemplate;
}
const keys = [];
const values = [];
let index = 0;
for (const item of items) {
keys[index] = keyFn ? keyFn(item, index) : index;
values[index] = template(item, index);
index++;
}
return {
values,
keys,
};
}
render(items, keyFnOrTemplate, template) {
return this._getValuesAndKeys(items, keyFnOrTemplate, template).values;
}
update(containerPart, [items, keyFnOrTemplate, template]) {
// Old part & key lists are retrieved from the last update (which may
// be primed by hydration)
const oldParts = getCommittedValue(containerPart);
const { values: newValues, keys: newKeys } = this._getValuesAndKeys(items, keyFnOrTemplate, template);
// We check that oldParts, the committed value, is an Array as an
// indicator that the previous value came from a repeat() call. If
// oldParts is not an Array then this is the first render and we return
// an array for lit-html's array handling to render, and remember the
// keys.
if (!Array.isArray(oldParts)) {
this._itemKeys = newKeys;
return newValues;
}
// In SSR hydration it's possible for oldParts to be an array but for us
// to not have item keys because the update() hasn't run yet. We set the
// keys to an empty array. This will cause all oldKey/newKey comparisons
// to fail and execution to fall to the last nested brach below which
// reuses the oldPart.
const oldKeys = (this._itemKeys ??= []);
// New part list will be built up as we go (either reused from
// old parts or created for new keys in this update). This is
// saved in the above cache at the end of the update.
const newParts = [];
// Maps from key to index for current and previous update; these
// are generated lazily only when needed as a performance
// optimization, since they are only required for multiple
// non-contiguous changes in the list, which are less common.
let newKeyToIndexMap;
let oldKeyToIndexMap;
// Head and tail pointers to old parts and new values
let oldHead = 0;
let oldTail = oldParts.length - 1;
let newHead = 0;
let newTail = newValues.length - 1;
// Overview of O(n) reconciliation algorithm (general approach
// based on ideas found in ivi, vue, snabbdom, etc.):
//
// * We start with the list of old parts and new values (and
// arrays of their respective keys), head/tail pointers into
// each, and we build up the new list of parts by updating
// (and when needed, moving) old parts or creating new ones.
// The initial scenario might look like this (for brevity of
// the diagrams, the numbers in the array reflect keys
// associated with the old parts or new values, although keys
// and parts/values are actually stored in parallel arrays
// indexed using the same head/tail pointers):
//
// oldHead v v oldTail
// oldKeys: [0, 1, 2, 3, 4, 5, 6]
// newParts: [ , , , , , , ]
// newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new
// item order
// newHead ^ ^ newTail
//
// * Iterate old & new lists from both sides, updating,
// swapping, or removing parts at the head/tail locations
// until neither head nor tail can move.
//
// * Example below: keys at head pointers match, so update old
// part 0 in-place (no need to move it) and record part 0 in
// the `newParts` list. The last thing we do is advance the
// `oldHead` and `newHead` pointers (will be reflected in the
// next diagram).
//
// oldHead v v oldTail
// oldKeys: [0, 1, 2, 3, 4, 5, 6]
// newParts: [0, , , , , , ] <- heads matched: update 0
// newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
// & newHead
// newHead ^ ^ newTail
//
// * Example below: head pointers don't match, but tail
// pointers do, so update part 6 in place (no need to move
// it), and record part 6 in the `newParts` list. Last,
// advance the `oldTail` and `oldHead` pointers.
//
// oldHead v v oldTail
// oldKeys: [0, 1, 2, 3, 4, 5, 6]
// newParts: [0, , , , , , 6] <- tails matched: update 6
// newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldTail
// & newTail
// newHead ^ ^ newTail
//
// * If neither head nor tail match; next check if one of the
// old head/tail items was removed. We first need to generate
// the reverse map of new keys to index (`newKeyToIndexMap`),
// which is done once lazily as a performance optimization,
// since we only hit this case if multiple non-contiguous
// changes were made. Note that for contiguous removal
// anywhere in the list, the head and tails would advance
// from either end and pass each other before we get to this
// case and removals would be handled in the final while loop
// without needing to generate the map.
//
// * Example below: The key at `oldTail` was removed (no longer
// in the `newKeyToIndexMap`), so remove that part from the
// DOM and advance just the `oldTail` pointer.
//
// oldHead v v oldTail
// oldKeys: [0, 1, 2, 3, 4, 5, 6]
// newParts: [0, , , , , , 6] <- 5 not in new map: remove
// newKeys: [0, 2, 1, 4, 3, 7, 6] 5 and advance oldTail
// newHead ^ ^ newTail
//
// * Once head and tail cannot move, any mismatches are due to
// either new or moved items; if a new key is in the previous
// "old key to old index" map, move the old part to the new
// location, otherwise create and insert a new part. Note
// that when moving an old part we null its position in the
// oldParts array if it lies between the head and tail so we
// know to skip it when the pointers get there.
//
// * Example below: neither head nor tail match, and neither
// were removed; so find the `newHead` key in the
// `oldKeyToIndexMap`, and move that old part's DOM into the
// next head position (before `oldParts[oldHead]`). Last,
// null the part in the `oldPart` array since it was
// somewhere in the remaining oldParts still to be scanned
// (between the head and tail pointers) so that we know to
// skip that old part on future iterations.
//
// oldHead v v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, , , , , 6] <- stuck: update & move 2
// newKeys: [0, 2, 1, 4, 3, 7, 6] into place and advance
// newHead
// newHead ^ ^ newTail
//
// * Note that for moves/insertions like the one above, a part
// inserted at the head pointer is inserted before the
// current `oldParts[oldHead]`, and a part inserted at the
// tail pointer is inserted before `newParts[newTail+1]`. The
// seeming asymmetry lies in the fact that new parts are
// moved into place outside in, so to the right of the head
// pointer are old parts, and to the right of the tail
// pointer are new parts.
//
// * We always restart back from the top of the algorithm,
// allowing matching and simple updates in place to
// continue...
//
// * Example below: the head pointers once again match, so
// simply update part 1 and record it in the `newParts`
// array. Last, advance both head pointers.
//
// oldHead v v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, 1, , , , 6] <- heads matched: update 1
// newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
// & newHead
// newHead ^ ^ newTail
//
// * As mentioned above, items that were moved as a result of
// being stuck (the final else clause in the code below) are
// marked with null, so we always advance old pointers over
// these so we're comparing the next actual old value on
// either end.
//
// * Example below: `oldHead` is null (already placed in
// newParts), so advance `oldHead`.
//
// oldHead v v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6] <- old head already used:
// newParts: [0, 2, 1, , , , 6] advance oldHead
// newKeys: [0, 2, 1, 4, 3, 7, 6]
// newHead ^ ^ newTail
//
// * Note it's not critical to mark old parts as null when they
// are moved from head to tail or tail to head, since they
// will be outside the pointer range and never visited again.
//
// * Example below: Here the old tail key matches the new head
// key, so the part at the `oldTail` position and move its
// DOM to the new head position (before `oldParts[oldHead]`).
// Last, advance `oldTail` and `newHead` pointers.
//
// oldHead v v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, 1, 4, , , 6] <- old tail matches new
// newKeys: [0, 2, 1, 4, 3, 7, 6] head: update & move 4,
// advance oldTail & newHead
// newHead ^ ^ newTail
//
// * Example below: Old and new head keys match, so update the
// old head part in place, and advance the `oldHead` and
// `newHead` pointers.
//
// oldHead v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3
// newKeys: [0, 2, 1, 4, 3, 7, 6] and advance oldHead &
// newHead
// newHead ^ ^ newTail
//
// * Once the new or old pointers move past each other then all
// we have left is additions (if old list exhausted) or
// removals (if new list exhausted). Those are handled in the
// final while loops at the end.
//
// * Example below: `oldHead` exceeded `oldTail`, so we're done
// with the main loop. Create the remaining part and insert
// it at the new head position, and the update is complete.
//
// (oldHead > oldTail)
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, 1, 4, 3, 7 ,6] <- create and insert 7
// newKeys: [0, 2, 1, 4, 3, 7, 6]
// newHead ^ newTail
//
// * Note that the order of the if/else clauses is not
// important to the algorithm, as long as the null checks
// come first (to ensure we're always working on valid old
// parts) and that the final else clause comes last (since
// that's where the expensive moves occur). The order of
// remaining clauses is is just a simple guess at which cases
// will be most common.
//
// * Note, we could calculate the longest
// increasing subsequence (LIS) of old items in new position,
// and only move those not in the LIS set. However that costs
// O(nlogn) time and adds a bit more code, and only helps
// make rare types of mutations require fewer moves. The
// above handles removes, adds, reversal, swaps, and single
// moves of contiguous items in linear time, in the minimum
// number of moves. As the number of multiple moves where LIS
// might help approaches a random shuffle, the LIS
// optimization becomes less helpful, so it seems not worth
// the code at this point. Could reconsider if a compelling
// case arises.
while (oldHead <= oldTail && newHead <= newTail) {
if (oldParts[oldHead] === null) {
// `null` means old part at head has already been used
// below; skip
oldHead++;
}
else if (oldParts[oldTail] === null) {
// `null` means old part at tail has already been used
// below; skip
oldTail--;
}
else if (oldKeys[oldHead] === newKeys[newHead]) {
// Old head matches new head; update in place
newParts[newHead] = setChildPartValue(oldParts[oldHead], newValues[newHead]);
oldHead++;
newHead++;
}
else if (oldKeys[oldTail] === newKeys[newTail]) {
// Old tail matches new tail; update in place
newParts[newTail] = setChildPartValue(oldParts[oldTail], newValues[newTail]);
oldTail--;
newTail--;
}
else if (oldKeys[oldHead] === newKeys[newTail]) {
// Old head matches new tail; update and move to new tail
newParts[newTail] = setChildPartValue(oldParts[oldHead], newValues[newTail]);
insertPart(containerPart, newParts[newTail + 1], oldParts[oldHead]);
oldHead++;
newTail--;
}
else if (oldKeys[oldTail] === newKeys[newHead]) {
// Old tail matches new head; update and move to new head
newParts[newHead] = setChildPartValue(oldParts[oldTail], newValues[newHead]);
insertPart(containerPart, oldParts[oldHead], oldParts[oldTail]);
oldTail--;
newHead++;
}
else {
if (newKeyToIndexMap === undefined) {
// Lazily generate key-to-index maps, used for removals &
// moves below
newKeyToIndexMap = generateMap(newKeys, newHead, newTail);
oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail);
}
if (!newKeyToIndexMap.has(oldKeys[oldHead])) {
// Old head is no longer in new list; remove
removePart(oldParts[oldHead]);
oldHead++;
}
else if (!newKeyToIndexMap.has(oldKeys[oldTail])) {
// Old tail is no longer in new list; remove
removePart(oldParts[oldTail]);
oldTail--;
}
else {
// Any mismatches at this point are due to additions or
// moves; see if we have an old part we can reuse and move
// into place
const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]);
const oldPart = oldIndex !== undefined ? oldParts[oldIndex] : null;
if (oldPart === null) {
// No old part for this value; create a new one and
// insert it
const newPart = insertPart(containerPart, oldParts[oldHead]);
setChildPartValue(newPart, newValues[newHead]);
newParts[newHead] = newPart;
}
else {
// Reuse old part
newParts[newHead] = setChildPartValue(oldPart, newValues[newHead]);
insertPart(containerPart, oldParts[oldHead], oldPart);
// This marks the old part as having been used, so that
// it will be skipped in the first two checks above
oldParts[oldIndex] = null;
}
newHead++;
}
}
}
// Add parts for any remaining new values
while (newHead <= newTail) {
// For all remaining additions, we insert before last new
// tail, since old pointers are no longer valid
const newPart = insertPart(containerPart, newParts[newTail + 1]);
setChildPartValue(newPart, newValues[newHead]);
newParts[newHead++] = newPart;
}
// Remove any remaining unused old parts
while (oldHead <= oldTail) {
const oldPart = oldParts[oldHead++];
if (oldPart !== null) {
removePart(oldPart);
}
}
// Save order of new parts for next round
this._itemKeys = newKeys;
// Directly set part value, bypassing it's dirty-checking
setCommittedValue(containerPart, newParts);
return noChange;
}
}
/**
* A directive that repeats a series of values (usually `TemplateResults`)
* generated from an iterable, and updates those items efficiently when the
* iterable changes based on user-provided `keys` associated with each item.
*
* Note that if a `keyFn` is provided, strict key-to-DOM mapping is maintained,
* meaning previous DOM for a given key is moved into the new position if
* needed, and DOM will never be reused with values for different keys (new DOM
* will always be created for new keys). This is generally the most efficient
* way to use `repeat` since it performs minimum unnecessary work for insertions
* and removals.
*
* The `keyFn` takes two parameters, the item and its index, and returns a unique key value.
*
* ```js
* html`
* <ol>
* ${repeat(this.items, (item) => item.id, (item, index) => {
* return html`<li>${index}: ${item.name}</li>`;
* })}
* </ol>
* `
* ```
*
* **Important**: If providing a `keyFn`, keys *must* be unique for all items in a
* given call to `repeat`. The behavior when two or more items have the same key
* is undefined.
*
* If no `keyFn` is provided, this directive will perform similar to mapping
* items to values, and DOM will be reused against potentially different items.
*/
const repeat = directive(RepeatDirective);
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const important = 'important';
// The leading space is important
const importantFlag = ' !' + important;
// How many characters to remove from a value, as a negative number
const flagTrim = 0 - importantFlag.length;
class StyleMapDirective extends Directive {
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.ATTRIBUTE ||
partInfo.name !== 'style' ||
partInfo.strings?.length > 2) {
throw new Error('The `styleMap` directive must be used in the `style` attribute ' +
'and must be the only part in the attribute.');
}
}
render(styleInfo) {
return Object.keys(styleInfo).reduce((style, prop) => {
const value = styleInfo[prop];
if (value == null) {
return style;
}
// Convert property names from camel-case to dash-case, i.e.:
// `backgroundColor` -> `background-color`
// Vendor-prefixed names need an extra `-` appended to front:
// `webkitAppearance` -> `-webkit-appearance`
// Exception is any property name containing a dash, including
// custom properties; we assume these are already dash-cased i.e.:
// `--my-button-color` --> `--my-button-color`
prop = prop.includes('-')
? prop
: prop
.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g, '-$&')
.toLowerCase();
return style + `${prop}:${value};`;
}, '');
}
update(part, [styleInfo]) {
const { style } = part.element;
if (this._previousStyleProperties === undefined) {
this._previousStyleProperties = new Set(Object.keys(styleInfo));
return this.render(styleInfo);
}
// Remove old properties that no longer exist in styleInfo
for (const name of this._previousStyleProperties) {
// If the name isn't in styleInfo or it's null/undefined
if (styleInfo[name] == null) {
this._previousStyleProperties.delete(name);
if (name.includes('-')) {
style.removeProperty(name);
}
else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
style[name] = null;
}
}
}
// Add or update properties
for (const name in styleInfo) {
const value = styleInfo[name];
if (value != null) {
this._previousStyleProperties.add(name);
const isImportant = typeof value === 'string' && value.endsWith(importantFlag);
if (name.includes('-') || isImportant) {
style.setProperty(name, isImportant
? value.slice(0, flagTrim)
: value, isImportant ? important : '');
}
else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
style[name] = value;
}
}
}
return noChange;
}
}
/**
* A directive that applies CSS properties to an element.
*
* `styleMap` can only be used in the `style` attribute and must be the only
* expression in the attribute. It takes the property names in the
* {@link StyleInfo styleInfo} object and adds the properties to the inline
* style of the element.
*
* Property names with dashes (`-`) are assumed to be valid CSS
* property names and set on the element's style object using `setProperty()`.
* Names without dashes are assumed to be camelCased JavaScript property names
* and set on the element's style object using property assignment, allowing the
* style object to translate JavaScript-style names to CSS property names.
*
* For example `styleMap({backgroundColor: 'red', 'border-top': '5px', '--size':
* '0'})` sets the `background-color`, `border-top` and `--size` properties.
*
* @param styleInfo
* @see {@link https://lit.dev/docs/templates/directives/#stylemap styleMap code samples on Lit.dev}
*/
const styleMap = directive(StyleMapDirective);
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
class TemplateContentDirective extends Directive {
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.CHILD) {
throw new Error('templateContent can only be used in child bindings');
}
}
render(template) {
if (this._previousTemplate === template) {
return noChange;
}
this._previousTemplate = template;
return document.importNode(template.content, true);
}
}
/**
* Renders the content of a template element as HTML.
*
* Note, the template should be developer controlled and not user controlled.
* Rendering a user-controlled template with this directive
* could lead to cross-site-scripting vulnerabilities.
*/
const templateContent = directive(TemplateContentDirective);
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const HTML_RESULT = 1;
class UnsafeHTMLDirective extends Directive {
constructor(partInfo) {
super(partInfo);
this._value = nothing;
if (partInfo.type !== PartType.CHILD) {
throw new Error(`${this.constructor.directiveName}() can only be used in child bindings`);
}
}
render(value) {
if (value === nothing || value == null) {
this._templateResult = undefined;
return (this._value = value);
}
if (value === noChange) {
return value;
}
if (typeof value != 'string') {
throw new Error(`${this.constructor.directiveName}() called with a non-string value`);
}
if (value === this._value) {
return this._templateResult;
}
this._value = value;
const strings = [value];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
strings.raw = strings;
// WARNING: impersonating a TemplateResult like this is extremely
// dangerous. Third-party directives should not do this.
return (this._templateResult = {
// Cast to a known set of integers that satisfy ResultType so that we
// don't have to export ResultType and possibly encourage this pattern.
// This property needs to remain unminified.
['_$litType$']: this.constructor
.resultType,
strings,
values: [],
});
}
}
UnsafeHTMLDirective.directiveName = 'unsafeHTML';
UnsafeHTMLDirective.resultType = HTML_RESULT;
/**
* Renders the result as HTML, rather than text.
*
* The values `undefined`, `null`, and `nothing`, will all result in no content
* (empty string) being rendered.
*
* Note, this is unsafe to use with any user-provided input that hasn't been
* sanitized or escaped, as it may lead to cross-site-scripting
* vulnerabilities.
*/
const unsafeHTML = directive(UnsafeHTMLDirective);
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const SVG_RESULT = 2;
class UnsafeSVGDirective extends UnsafeHTMLDirective {
}
UnsafeSVGDirective.directiveName = 'unsafeSVG';
UnsafeSVGDirective.resultType = SVG_RESULT;
/**
* Renders the result as SVG, rather than text.
*
* The values `undefined`, `null`, and `nothing`, will all result in no content
* (empty string) being rendered.
*
* Note, this is unsafe to use with any user-provided input that hasn't been
* sanitized or escaped, as it may lead to cross-site-scripting
* vulnerabilities.
*/
const unsafeSVG = directive(UnsafeSVGDirective);
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const isPromise = (x) => {
return !isPrimitive(x) && typeof x.then === 'function';
};
// Effectively infinity, but a SMI.
const _infinity = 0x3fffffff;
class UntilDirective extends AsyncDirective {
constructor() {
super(...arguments);
this.__lastRenderedIndex = _infinity;
this.__values = [];
this.__weakThis = new PseudoWeakRef(this);
this.__pauser = new Pauser();
}
render(...args) {
return args.find((x) => !isPromise(x)) ?? noChange;
}
update(_part, args) {
const previousValues = this.__values;
let previousLength = previousValues.length;
this.__values = args;
const weakThis = this.__weakThis;
const pauser = this.__pauser;
// If our initial render occurs while disconnected, ensure that the pauser
// and weakThis are in the disconnected state
if (!this.isConnected) {
this.disconnected();
}
for (let i = 0; i < args.length; i++) {
// If we've rendered a higher-priority value already, stop.
if (i > this.__lastRenderedIndex) {
break;
}
const value = args[i];
// Render non-Promise values immediately
if (!isPromise(value)) {
this.__lastRenderedIndex = i;
// Since a lower-priority value will never overwrite a higher-priority
// synchronous value, we can stop processing now.
return value;
}
// If this is a Promise we've already handled, skip it.
if (i < previousLength && value === previousValues[i]) {
continue;
}
// We have a Promise that we haven't seen before, so priorities may have
// changed. Forget what we rendered before.
this.__lastRenderedIndex = _infinity;
previousLength = 0;
// Note, the callback avoids closing over `this` so that the directive
// can be gc'ed before the promise resolves; instead `this` is retrieved
// from `weakThis`, which can break the hard reference in the closure when
// the directive disconnects
Promise.resolve(value).then(async (result) => {
// If we're disconnected, wait until we're (maybe) reconnected
// The while loop here handles the case that the connection state
// thrashes, causing the pauser to resume and then get re-paused
while (pauser.get()) {
await pauser.get();
}
// If the callback gets here and there is no `this`, it means that the
// directive has been disconnected and garbage collected and we don't
// need to do anything else
const _this = weakThis.deref();
if (_this !== undefined) {
const index = _this.__values.indexOf(value);
// If state.values doesn't contain the value, we've re-rendered without
// the value, so don't render it. Then, only render if the value is
// higher-priority than what's already been rendered.
if (index > -1 && index < _this.__lastRenderedIndex) {
_this.__lastRenderedIndex = index;
_this.setValue(result);
}
}
});
}
return noChange;
}
disconnected() {
this.__weakThis.disconnect();
this.__pauser.pause();
}
reconnected() {
this.__weakThis.reconnect(this);
this.__pauser.resume();
}
}
/**
* Renders one of a series of values, including Promises, to a Part.
*
* Values are rendered in priority order, with the first argument having the
* highest priority and the last argument having the lowest priority. If a
* value is a Promise, low-priority values will be rendered until it resolves.
*
* The priority of values can be used to create placeholder content for async
* data. For example, a Promise with pending content can be the first,
* highest-priority, argument, and a non_promise loading indicator template can
* be used as the second, lower-priority, argument. The loading indicator will
* render immediately, and the primary content will render when the Promise
* resolves.
*
* Example:
*
* ```js
* const content = fetch('./content.txt').then(r => r.text());
* html`${until(content, html`<span>Loading...</span>`)}`
* ```
*/
const until = directive(UntilDirective);
/**
* The type of the class that powers this directive. Necessary for naming the
* directive's return type.
*/
// export type {UntilDirective};
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
function when(condition, trueCase, falseCase) {
return condition ? trueCase(condition) : falseCase?.(condition);
}
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* Prevents JSON injection attacks.
*
* The goals of this brand:
* 1) fast to check
* 2) code is small on the wire
* 3) multiple versions of Lit in a single page will all produce mutually
* interoperable StaticValues
* 4) normal JSON.parse (without an unusual reviver) can not produce a
* StaticValue
*
* Symbols satisfy (1), (2), and (4). We use Symbol.for to satisfy (3), but
* we don't care about the key, so we break ties via (2) and use the empty
* string.
*/
const brand = Symbol.for('');
/** Safely extracts the string part of a StaticValue. */
const unwrapStaticValue = (value) => {
if (value?.r !== brand) {
return undefined;
}
return value?.['_$litStatic$'];
};
/**
* Wraps a string so that it behaves like part of the static template
* strings instead of a dynamic value.
*
* Users must take care to ensure that adding the static string to the template
* results in well-formed HTML, or else templates may break unexpectedly.
*
* Note that this function is unsafe to use on untrusted content, as it will be
* directly parsed into HTML. Do not pass user input to this function
* without sanitizing it.
*
* Static values can be changed, but they will cause a complete re-render
* since they effectively create a new template.
*/
const unsafeStatic = (value) => ({
['_$litStatic$']: value,
r: brand,
});
const textFromStatic = (value) => {
if (value['_$litStatic$'] !== undefined) {
return value['_$litStatic$'];
}
else {
throw new Error(`Value passed to 'literal' function must be a 'literal' result: ${value}. Use 'unsafeStatic' to pass non-literal values, but
take care to ensure page security.`);
}
};
/**
* Tags a string literal so that it behaves like part of the static template
* strings instead of a dynamic value.
*
* The only values that may be used in template expressions are other tagged
* `literal` results or `unsafeStatic` values (note that untrusted content
* should never be passed to `unsafeStatic`).
*
* Users must take care to ensure that adding the static string to the template
* results in well-formed HTML, or else templates may break unexpectedly.
*
* Static values can be changed, but they will cause a complete re-render since
* they effectively create a new template.
*/
const literal = (strings, ...values) => ({
['_$litStatic$']: values.reduce((acc, v, idx) => acc + textFromStatic(v) + strings[idx + 1], strings[0]),
r: brand,
});
const stringsCache = new Map();
/**
* Wraps a lit-html template tag (`html` or `svg`) to add static value support.
*/
const withStatic = (coreTag) => (strings, ...values) => {
const l = values.length;
let staticValue;
let dynamicValue;
const staticStrings = [];
const dynamicValues = [];
let i = 0;
let hasStatics = false;
let s;
while (i < l) {
s = strings[i];
// Collect any unsafeStatic values, and their following template strings
// so that we treat a run of template strings and unsafe static values as
// a single template string.
while (i < l &&
((dynamicValue = values[i]),
(staticValue = unwrapStaticValue(dynamicValue))) !== undefined) {
s += staticValue + strings[++i];
hasStatics = true;
}
// If the last value is static, we don't need to push it.
if (i !== l) {
dynamicValues.push(dynamicValue);
}
staticStrings.push(s);
i++;
}
// If the last value isn't static (which would have consumed the last
// string), then we need to add the last string.
if (i === l) {
staticStrings.push(strings[l]);
}
if (hasStatics) {
const key = staticStrings.join('$$lit$$');
strings = stringsCache.get(key);
if (strings === undefined) {
// Beware: in general this pattern is unsafe, and doing so may bypass
// lit's security checks and allow an attacker to execute arbitrary
// code and inject arbitrary content.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
staticStrings.raw = staticStrings;
stringsCache.set(key, (strings = staticStrings));
}
values = dynamicValues;
}
return coreTag(strings, ...values);
};
/**
* Interprets a template literal as an HTML template that can efficiently
* render to and update a container.
*
* Includes static value support from `lit-html/static.js`.
*/
const html = withStatic(html$1);
/**
* Interprets a template literal as an SVG template that can efficiently
* render to and update a container.
*
* Includes static value support from `lit-html/static.js`.
*/
const svg = withStatic(svg$1);
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
if (!window.litDisableBundleWarning) {
console.warn('Lit has been loaded from a bundle that combines all core features into ' +
'a single file. To reduce transfer size and parsing cost, consider ' +
'using the `lit` npm package directly in your project.');
}
export { AsyncDirective, AsyncReplaceDirective, CSSResult, Directive, LitElement, PartType, ReactiveElement, TemplateResultType, UnsafeHTMLDirective, UntilDirective, _$LE, _$LH, adoptStyles, asyncAppend, asyncReplace, cache, choose, classMap, clearPart, createRef, css, defaultConverter, directive, getCommittedValue, getCompatibleStyle, getDirectiveClass, guard, html$1 as html, ifDefined, insertPart, isCompiledTemplateResult, isDirectiveResult, isPrimitive, isServer, isSingleExpression, isTemplateResult, join, keyed, literal, live, map, noChange, notEqual, nothing, range, ref, removePart, render, repeat, setChildPartValue, setCommittedValue, html as staticHtml, svg as staticSvg, styleMap, supportsAdoptingStyleSheets, svg$1 as svg, templateContent, unsafeCSS, unsafeHTML, unsafeSVG, unsafeStatic, until, when, withStatic };
//# sourceMappingURL=lit-all.min.js.map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment