/** * Enregistre des callbacks sur des propriétés Lit (via `willUpdate`) pour * réagir aux changements de segments dynamiques `${…}` dans les chemins. * Chaque décorateur utilise des clés de stockage distinctes pour éviter les collisions. */ type InstanceStores = Record; export function registerDynamicPropertyWatcher( watcherStoreKey: PropertyKey, hookedStoreKey: PropertyKey, instance: object, propertyName: string, onChange: () => void, ): () => void { const inst = instance as InstanceStores; const key = String(propertyName); ensureDynamicPropertiesWillUpdate( watcherStoreKey, hookedStoreKey, instance, ); if (!inst[watcherStoreKey]) { Object.defineProperty(inst, watcherStoreKey, { value: new Map void>>(), enumerable: false, configurable: false, writable: false, }); } const watcherMap = inst[watcherStoreKey] as Map void>>; if (!watcherMap.has(key)) { watcherMap.set(key, new Set()); } const watchers = watcherMap.get(key)!; watchers.add(onChange); return () => { watchers.delete(onChange); if (watchers.size === 0) { watcherMap.delete(key); } }; } export function ensureDynamicPropertiesWillUpdate( watcherStoreKey: PropertyKey, hookedStoreKey: PropertyKey, instance: object, ): void { const proto = Object.getPrototypeOf(instance); if (!proto || (proto as InstanceStores)[hookedStoreKey]) return; const originalWillUpdate = Object.prototype.hasOwnProperty.call( proto, "willUpdate", ) ? (proto as InstanceStores).willUpdate : (Object.getPrototypeOf(proto) as InstanceStores)?.willUpdate; (proto as InstanceStores).willUpdate = function ( changedProperties?: Map, ) { const handlers = (this as InstanceStores)[watcherStoreKey] as | Map void>> | undefined; if (handlers && handlers.size > 0) { if (changedProperties && changedProperties.size > 0) { changedProperties.forEach((_value, dependency) => { const callbacks = handlers.get(String(dependency)); if (callbacks) { callbacks.forEach((cb) => cb()); } }); } else { handlers.forEach((callbacks) => callbacks.forEach((cb) => cb())); } } if (typeof originalWillUpdate === "function") { originalWillUpdate.call(this, changedProperties); } }; (proto as InstanceStores)[hookedStoreKey] = true; } /** Clés utilisées par `@bind`. */ export const bindDynamicWatchKeys = { watcherStore: Symbol("__bindDynamicWatcherStore__"), hooked: Symbol("__bindDynamicWillUpdateHooked__"), } as const; /** Clés utilisées par `@publish`. */ export const publishDynamicWatchKeys = { watcherStore: "__publishDynamicWatcherStore__", hooked: "__publishDynamicWillUpdateHooked__", } as const; /** Clés utilisées par `@get`. */ export const getDynamicWatchKeys = { watcherStore: "__getDynamicWatcherStore__", hooked: "__getDynamicWillUpdateHooked__", } as const; /** Clés utilisées par `@onAssign`. */ export const onAssignDynamicWatchKeys = { watcherStore: Symbol("__onAssignDynamicWatcherStore__"), hooked: Symbol("__onAssignDynamicWillUpdateHooked__"), } as const;