UNPKG

element-vir

Version:

Heroic. Reactive. Declarative. Type safe. Web components without compromise.

106 lines (105 loc) 4.32 kB
import { isObservableBase } from 'observavir'; import { property } from '../../lit-exports/base-lit-exports.js'; /** * Binds the given property key as a reactive property on the given element. * * @category Internal */ export function bindReactiveProperty(element, propertyKey) { if (!(propertyKey in element)) { property()(element, propertyKey); } } function assertValidPropertyName(propKey, element, elementTagName) { if (typeof propKey !== 'string' && typeof propKey !== 'number' && typeof propKey !== 'symbol') { throw new TypeError(`Property name must be a string, got type '${typeof propKey}' from: '${String(propKey)}' for '${elementTagName.toLowerCase()}'`); } else if (!(propKey in element)) { throw new Error(`Property '${String(propKey)}' does not exist on '${elementTagName.toLowerCase()}'.`); } } /** * Creates an internal proxy for setting inputs and state properties and then updating them on the * element itself so they will trigger lit's change detection. * * @category Internal */ export function createElementPropertyProxy(element, shouldAlreadyExist) { /** * Lit element updates state and inputs by setting them directly on the element, so we must do * that here. DeclarativeElement's types, however, do not expose this behavior, so we add that * back in here. */ const elementAsProps = element; function verifyProperty(propertyKey) { if (shouldAlreadyExist) { assertValidPropertyName(propertyKey, element, element.tagName); } else { bindReactiveProperty(element, propertyKey); } } function valueGetter(target, propertyKey) { verifyProperty(propertyKey); return elementAsProps[propertyKey]; } const propsProxy = new Proxy({}, { get: valueGetter, set(target, propertyKey, newValue) { verifyProperty(propertyKey); const oldValue = elementAsProps[propertyKey]; function setValueOnElement(value) { /** * We need to at least set the property on target so we can detect it in "ownKeys" * and "getOwnPropertyDescriptor". We don't need duplicates of the values stored in * target but doing so makes console logging more effective it actually works). */ target[propertyKey] = value; elementAsProps[propertyKey] = value; } const existingPropertyListener = element.observablePropertyListenerMap[propertyKey]; if (oldValue !== newValue && isObservableBase(oldValue) && existingPropertyListener) { /** Stop listening to the old value now that we have a new value */ oldValue.removeListener(existingPropertyListener); } if (isObservableBase(newValue)) { /** If we're using an existing observable property */ if (existingPropertyListener) { newValue.listen(false, existingPropertyListener); } else { function newListener() { element.requestUpdate(); } element.observablePropertyListenerMap[propertyKey] = newListener; newValue.listen(false, newListener); } } else if (isObservableBase(oldValue)) { /** Clear out old listener that is no longer used. */ delete element.observablePropertyListenerMap[propertyKey]; } setValueOnElement(newValue); return true; }, ownKeys(target) { return Reflect.ownKeys(target); }, getOwnPropertyDescriptor(target, propertyName) { if (propertyName in target) { return { get value() { return valueGetter(target, propertyName); }, configurable: true, enumerable: true, }; } return undefined; }, has(target, propertyName) { return Reflect.has(target, propertyName); }, }); return propsProxy; }