import type {LitElement} from "lit"; type UpdateHandler = (prev?: unknown, next?: unknown) => void; type NonUndefined = T extends undefined ? never : T; type UpdateHandlerFunctionKeys = { [K in keyof T]-?: NonUndefined extends UpdateHandler ? K : never; }[keyof T]; interface WatchOptions { /** * If true, will only start watching after the initial update/render */ waitUntilFirstUpdate?: boolean; } /** * Runs when observed properties change, e.g. @property or @state, but before the component updates. To wait for an * update to complete after a change occurs, use `await this.updateComplete` in the handler. To start watching after the * initial update, set `{ waitUntilFirstUpdate: true }` or `this.hasUpdated` in the handler. * * Usage: * * ```ts * @watch('propName') * handlePropChanges(oldValue, newValue) {...} * ``` * * @param propertyName * @param options */ export function watch(propertyName: string | string[], options?: WatchOptions) { const resolvedOptions: Required = { waitUntilFirstUpdate: false, ...options, }; return (proto: ElemClass, decoratedFnName: UpdateHandlerFunctionKeys) => { // @ts-expect-error update is a protected property const {update} = proto; const watchedProperties = Array.isArray(propertyName) ? propertyName : [propertyName]; // @ts-expect-error update is a protected property proto.update = function (this: ElemClass, changedProps: Map) { watchedProperties.forEach(property => { const key = property as keyof ElemClass; if (changedProps.has(key)) { const oldValue = changedProps.get(key); const newValue = this[key]; if (oldValue !== newValue) { if (!resolvedOptions.waitUntilFirstUpdate || this.hasUpdated) { (this[decoratedFnName] as unknown as UpdateHandler)(oldValue, newValue) } } } }); update.call(this, changedProps); } } }