@lit/reactive-element
Version:
150 lines (147 loc) • 6.02 kB
JavaScript
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