UNPKG

@lit/reactive-element

Version:

A simple low level base class for creating fast, lightweight web components

150 lines (147 loc) 6.02 kB
import { notEqual, defaultConverter } from '../reactive-element.js'; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ /* * IMPORTANT: For compatibility with tsickle and the Closure JS compiler, all * property decorators (but not class decorators) in this file that have * an @ExportDecoratedItems annotation must be defined as a regular function, * not an arrow function. */ let issueWarning; { // Ensure warnings are issued only 1x, even if multiple versions of Lit // are loaded. globalThis.litIssuedWarnings ??= new Set(); /** * Issue a warning if we haven't already, based either on `code` or `warning`. * Warnings are disabled automatically only by `warning`; disabling via `code` * can be done by users. */ issueWarning = (code, warning) => { warning += ` See https://lit.dev/msg/${code} for more information.`; if (!globalThis.litIssuedWarnings.has(warning) && !globalThis.litIssuedWarnings.has(code)) { console.warn(warning); globalThis.litIssuedWarnings.add(warning); } }; } const legacyProperty = (options, proto, name) => { const hasOwnProperty = proto.hasOwnProperty(name); proto.constructor.createProperty(name, options); // For accessors (which have a descriptor on the prototype) we need to // return a descriptor, otherwise TypeScript overwrites the descriptor we // define in createProperty() with the original descriptor. We don't do this // for fields, which don't have a descriptor, because this could overwrite // descriptor defined by other decorators. return hasOwnProperty ? Object.getOwnPropertyDescriptor(proto, name) : undefined; }; // This is duplicated from a similar variable in reactive-element.ts, but // actually makes sense to have this default defined with the decorator, so // that different decorators could have different defaults. const defaultPropertyDeclaration = { attribute: true, type: String, converter: defaultConverter, reflect: false, hasChanged: notEqual, }; /** * Wraps a class accessor or setter so that `requestUpdate()` is called with the * property name and old value when the accessor is set. */ const standardProperty = (options = defaultPropertyDeclaration, target, context) => { const { kind, metadata } = context; if (metadata == null) { issueWarning('missing-class-metadata', `The class ${target} is missing decorator metadata. This ` + `could mean that you're using a compiler that supports decorators ` + `but doesn't support decorator metadata, such as TypeScript 5.1. ` + `Please update your compiler.`); } // Store the property options let properties = globalThis.litPropertyMetadata.get(metadata); if (properties === undefined) { globalThis.litPropertyMetadata.set(metadata, (properties = new Map())); } if (kind === 'setter') { options = Object.create(options); options.wrapped = true; } properties.set(context.name, options); if (kind === 'accessor') { // Standard decorators cannot dynamically modify the class, so we can't // replace a field with accessors. The user must use the new `accessor` // keyword instead. const { name } = context; return { set(v) { const oldValue = target.get.call(this); target.set.call(this, v); this.requestUpdate(name, oldValue, options, true, v); }, init(v) { if (v !== undefined) { this._$changeProperty(name, undefined, options, v); } return v; }, }; } else if (kind === 'setter') { const { name } = context; return function (value) { const oldValue = this[name]; target.call(this, value); this.requestUpdate(name, oldValue, options, true, value); }; } throw new Error(`Unsupported decorator location: ${kind}`); }; /** * A class field or accessor decorator which creates a reactive property that * reflects a corresponding attribute value. When a decorated property is set * the element will update and render. A {@linkcode PropertyDeclaration} may * optionally be supplied to configure property features. * * This decorator should only be used for public fields. As public fields, * properties should be considered as primarily settable by element users, * either via attribute or the property itself. * * Generally, properties that are changed by the element should be private or * protected fields and should use the {@linkcode state} decorator. * * However, sometimes element code does need to set a public property. This * should typically only be done in response to user interaction, and an event * should be fired informing the user; for example, a checkbox sets its * `checked` property when clicked and fires a `changed` event. Mutating public * properties should typically not be done for non-primitive (object or array) * properties. In other cases when an element needs to manage state, a private * property decorated via the {@linkcode state} decorator should be used. When * needed, state properties can be initialized via public properties to * facilitate complex interactions. * * ```ts * class MyElement { * @property({ type: Boolean }) * clicked = false; * } * ``` * @category Decorator * @ExportDecoratedItems */ function property(options) { return (protoOrTarget, nameOrContext // eslint-disable-next-line @typescript-eslint/no-explicit-any ) => { return (typeof nameOrContext === 'object' ? standardProperty(options, protoOrTarget, nameOrContext) : legacyProperty(options, protoOrTarget, nameOrContext)); }; } export { property, standardProperty }; //# sourceMappingURL=property.js.map