@lit/reactive-element
Version:
122 lines (119 loc) • 4.47 kB
JavaScript
import { desc } from './base.js';
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
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 += code
? ` 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);
}
};
}
/**
* A property decorator that converts a class property into a getter that
* executes a querySelector on the element's renderRoot.
*
* @param selector A DOMString containing one or more selectors to match.
* @param cache An optional boolean which when true performs the DOM query only
* once and caches the result.
*
* See: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
*
* ```ts
* class MyElement {
* @query('#first')
* first: HTMLDivElement;
*
* render() {
* return html`
* <div id="first"></div>
* <div id="second"></div>
* `;
* }
* }
* ```
* @category Decorator
*/
function query(selector, cache) {
return ((protoOrTarget, nameOrContext, descriptor) => {
const doQuery = (el) => {
const result = (el.renderRoot?.querySelector(selector) ?? null);
if (result === null && cache && !el.hasUpdated) {
const name = typeof nameOrContext === 'object'
? nameOrContext.name
: nameOrContext;
issueWarning('', `@query'd field ${JSON.stringify(String(name))} with the 'cache' ` +
`flag set for selector '${selector}' has been accessed before ` +
`the first update and returned null. This is expected if the ` +
`renderRoot tree has not been provided beforehand (e.g. via ` +
`Declarative Shadow DOM). Therefore the value hasn't been cached.`);
}
// TODO: if we want to allow users to assert that the query will never
// return null, we need a new option and to throw here if the result
// is null.
return result;
};
if (cache) {
// Accessors to wrap from either:
// 1. The decorator target, in the case of standard decorators
// 2. The property descriptor, in the case of experimental decorators
// on auto-accessors.
// 3. Functions that access our own cache-key property on the instance,
// in the case of experimental decorators on fields.
const { get, set } = typeof nameOrContext === 'object'
? protoOrTarget
: (descriptor ??
(() => {
const key = Symbol(`${String(nameOrContext)} (@query() cache)`)
;
return {
get() {
return this[key];
},
set(v) {
this[key] = v;
},
};
})());
return desc(protoOrTarget, nameOrContext, {
get() {
let result = get.call(this);
if (result === undefined) {
result = doQuery(this);
if (result !== null || this.hasUpdated) {
set.call(this, result);
}
}
return result;
},
});
}
else {
// This object works as the return type for both standard and
// experimental decorators.
return desc(protoOrTarget, nameOrContext, {
get() {
return doQuery(this);
},
});
}
});
}
export { query };
//# sourceMappingURL=query.js.map