diff --git a/attach-effect/Counter.mjs b/attach-effect/Counter.mjs index 0c44864..a4f2c0f 100644 --- a/attach-effect/Counter.mjs +++ b/attach-effect/Counter.mjs @@ -1,14 +1,22 @@ // @ts-check import WebComponent from "../src/WebComponent.js"; -// import { attachEffect } from "../src/attach-effect.js"; +import { attachEffect } from "../src/attach-effect.js"; export class Counter extends WebComponent { static properties = ["count"]; onInit() { this.props.count = 0; this.onclick = () => ++this.props.count; - // attachEffect(this.props, 'count', console.log); + attachEffect( + this.props.count, + (count) => console.log(count) + ); } + + afterViewInit(){ + attachEffect(this.props.count, (count) => console.log(count + 100)); + } + get template() { return ``; } diff --git a/src/WebComponent.js b/src/WebComponent.js index 32dd0ef..f9a661b 100644 --- a/src/WebComponent.js +++ b/src/WebComponent.js @@ -24,7 +24,6 @@ export class WebComponent extends HTMLElement { * Read-only property containing camelCase counterparts of observed attributes. * @see https://www.npmjs.com/package/web-component-base#prop-access * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset - * @typedef {{[name: string]: any}} PropStringMap * @type {PropStringMap} */ get props() { @@ -53,11 +52,7 @@ export class WebComponent extends HTMLElement { /** * Triggered when an attribute value changes - * @param {{ - * property: string, - * previousValue: any, - * currentValue: any - * }} changes + * @param {Changes} changes */ onChanges(changes) {} @@ -125,7 +120,11 @@ export class WebComponent extends HTMLElement { } }; - #handler(setter, typeMap) { + #effectsMap = {}; + + #handler(setter, meta) { + const effectsMap = meta.#effectsMap; + const typeMap = meta.#typeMap; const getKebab = (str) => { return str.replace( /[A-Z]+(?![a-z])|[A-Z]/g, @@ -141,14 +140,25 @@ export class WebComponent extends HTMLElement { typeMap[prop] = typeof value; } - if (oldValue !== value) { + if (value.attach === "effect") { + if (!effectsMap[prop]) { + effectsMap[prop] = []; + } + effectsMap[prop].push(value.callback); + } else if (oldValue !== value) { obj[prop] = value; + effectsMap[prop]?.forEach((f) => f(value)); const kebab = getKebab(prop); setter(kebab, value); } return true; }, + get(obj, prop) { + Object.getPrototypeOf(obj[prop]).proxy = meta.#props; + Object.getPrototypeOf(obj[prop]).prop = prop; + return obj[prop]; + }, }; } @@ -156,13 +166,19 @@ export class WebComponent extends HTMLElement { if (!this.#props) { this.#props = new Proxy( {}, - this.#handler( - (key, value) => this.setAttribute(key, value), - this.#typeMap - ) + this.#handler((key, value) => this.setAttribute(key, value), this) ); } } } export default WebComponent; + +/** + * @typedef {{ + * property: string, + * previousValue: any, + * currentValue: any + * }} Changes + * @typedef {{[name: string]: any}} PropStringMap + */ diff --git a/src/attach-effect.js b/src/attach-effect.js index 330212a..94cab43 100644 --- a/src/attach-effect.js +++ b/src/attach-effect.js @@ -1,6 +1,15 @@ -export function attachEffect(proxy, prop, callback) { - proxy[prop] = { - attach: 'effect', - callback - } -} \ No newline at end of file +/** + * + * @typedef { import('./WebComponent.js').Changes} Changes + * @typedef { import('./WebComponent.js').PropStringMap} PropStringMap + * @param {Object} obj + * @param {(newValue: any) => void} callback + */ +export function attachEffect(obj, callback) { + const { proxy, prop } = Object.getPrototypeOf(obj); + + proxy[prop] = { + attach: "effect", + callback, + }; +}