@lytjs/common-event-normalizer 6.5.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["EVENT_MODIFIER_RE"],"mappings":"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","file":"index.cjs","sourcesContent":["// @lytjs/common-event-normalizer\r\n// 事件归一化:解析事件名、提供 invoker 缓存模式、通过 RendererHost 操作事件\r\n\r\nimport type { RendererHost, HostEvent, HostEventHandler, HostEventOptions } from '@lytjs/host-contract';\r\nimport { EVENT_MODIFIER_RE } from '@lytjs/common-events';\r\n\r\n// ============================================================\r\n// 类型定义\r\n// ============================================================\r\n\r\n/**\r\n * 事件修饰符解析结果。\r\n */\r\nexport interface ParsedModifiers {\r\n /** 是否调用 stopPropagation */\r\n stop: boolean;\r\n /** 是否调用 preventDefault */\r\n prevent: boolean;\r\n /** 是否使用 capture 模式 */\r\n capture: boolean;\r\n /** 是否使用 once 模式 */\r\n once: boolean;\r\n /** 是否仅在 target === currentTarget 时触发 */\r\n self: boolean;\r\n /** 是否使用 passive 模式 */\r\n passive: boolean;\r\n}\r\n\r\n/**\r\n * 解析后的事件信息。\r\n */\r\nexport interface ParsedEventInfo {\r\n /** 规范化后的事件名(如 'click') */\r\n name: string;\r\n /** 事件修饰符 */\r\n modifiers: ParsedModifiers;\r\n}\r\n\r\n/**\r\n * 事件 invoker 接口。\r\n *\r\n * 持有 value 属性用于更新,避免重复 addEventListener。\r\n */\r\nexport interface EventInvoker<HE> {\r\n /** 当前绑定的事件处理函数 */\r\n value: ((event: HostEvent) => void) | null;\r\n /** 解析后的事件信息 */\r\n parsed: ParsedEventInfo;\r\n /** 宿主元素引用 */\r\n el: HE;\r\n /** 实际的事件处理函数(传给 addEventListener 的函数) */\r\n handler: (event: HostEvent) => void;\r\n /** 取消监听函数 */\r\n dispose: () => void;\r\n}\r\n\r\n/**\r\n * 事件监听器注册条目。\r\n */\r\nexport interface EventListenerEntry<HE> {\r\n /** 宿主元素 */\r\n el: HE;\r\n /** 事件名 */\r\n event: string;\r\n /** 处理函数 */\r\n handler: (event: HostEvent) => void;\r\n /** 事件选项 */\r\n options?: { capture?: boolean; once?: boolean; passive?: boolean };\r\n}\r\n\r\n// ============================================================\r\n// 常量\r\n// ============================================================\r\n\r\n/** invoker 缓存类型 */\r\ntype InvokerCache<HE> = Record<string, EventInvoker<HE> | undefined>;\r\n\r\n// ============================================================\r\n// EventNormalizer\r\n// ============================================================\r\n\r\n/**\r\n * 事件归一化器。\r\n *\r\n * 负责解析事件名中的修饰符、管理事件 invoker 缓存,\r\n * 通过 RendererHost 接口执行平台无关的事件操作。\r\n *\r\n * @template HN - 宿主节点类型\r\n * @template HE - 宿主元素类型\r\n */\r\n// FIX: DTS build error - HN 必须约束为 object\r\nexport class EventNormalizer<HN extends object = object, HE extends HN = HN> {\r\n /** RendererHost 实例 */\r\n private host: RendererHost<HN, HE>;\r\n\r\n /**\r\n * FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储,避免类型断言和属性污染\r\n * 键:宿主元素,值:该元素的事件 invoker 缓存\r\n */\r\n private invokerCache = new WeakMap<HE, InvokerCache<HE>>();\r\n\r\n /**\r\n * 创建事件归一化器实例。\r\n * @param host - RendererHost 实例\r\n */\r\n constructor(host: RendererHost<HN, HE>) {\r\n this.host = host;\r\n }\r\n\r\n // ==========================================================\r\n // 事件名解析\r\n // ==========================================================\r\n\r\n /**\r\n * 解析事件名。\r\n *\r\n * 将各种格式的事件名规范化并提取修饰符:\r\n * - onClick.stop.prevent → { name: 'click', modifiers: { stop: true, prevent: true } }\r\n * - @click.capture → { name: 'click', modifiers: { capture: true } }\r\n * - click → { name: 'click', modifiers: { stop: false, ... } }\r\n *\r\n * @param rawName - 原始事件名\r\n * @returns 解析后的事件信息\r\n */\r\n parseEventName(rawName: string): ParsedEventInfo {\r\n const name = this.normalizeEventName(rawName);\r\n const modifiers = this.parseModifiers(rawName);\r\n\r\n return { name, modifiers };\r\n }\r\n\r\n /**\r\n * 将原始事件名规范化为标准事件名。\r\n *\r\n * 支持格式:@click / onClick / click → click\r\n *\r\n * @param rawName - 原始事件名\r\n * @returns 规范化后的事件名\r\n */\r\n normalizeEventName(rawName: string): string {\r\n // 移除 @ 前缀\r\n let name = rawName.startsWith('@') ? rawName.slice(1) : rawName;\r\n\r\n // 移除 on 前缀(仅当以大写字母开头的 on 前缀时)\r\n // FIX: P2-45 使用 charAt 替代非空断言,更安全地访问字符串字符\r\n if (name.startsWith('on') && name.length > 2 && /^[A-Za-z]$/.test(name.charAt(2))) {\r\n name = name.slice(2);\r\n }\r\n\r\n // 移除修饰符后缀(如 .stop.prevent)\r\n name = name.replace(EVENT_MODIFIER_RE, '');\r\n\r\n // 转为小写\r\n return name.toLowerCase();\r\n }\r\n\r\n /**\r\n * 解析事件名中的修饰符。\r\n *\r\n * @param rawName - 原始事件名\r\n * @returns 修饰符集合\r\n */\r\n parseModifiers(rawName: string): ParsedModifiers {\r\n const modifiers: ParsedModifiers = {\r\n stop: false,\r\n prevent: false,\r\n capture: false,\r\n once: false,\r\n self: false,\r\n passive: false,\r\n };\r\n\r\n // FIX: P2-23 正则表达式缓存 - 模块级预编译正则\r\n const RE_EVENT_MODIFIERS = /\\.(stop|prevent|capture|once|self|passive)/g;\r\n // FIX: P2-41 事件捕获选项支持:支持 .capture 修饰符\r\n const modifierMatch = rawName.match(RE_EVENT_MODIFIERS);\r\n if (modifierMatch) {\r\n for (const mod of modifierMatch) {\r\n switch (mod) {\r\n case '.stop':\r\n modifiers.stop = true;\r\n break;\r\n case '.prevent':\r\n modifiers.prevent = true;\r\n break;\r\n case '.capture':\r\n modifiers.capture = true;\r\n break;\r\n case '.once':\r\n modifiers.once = true;\r\n break;\r\n case '.self':\r\n modifiers.self = true;\r\n break;\r\n case '.passive':\r\n modifiers.passive = true;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n return modifiers;\r\n }\r\n\r\n // ==========================================================\r\n // 事件名 → 缓存 key 转换\r\n // ==========================================================\r\n\r\n /**\r\n * 将事件名转换为 invoker 缓存的 key。\r\n *\r\n * click → onClick,mouseenter → onMouseenter\r\n *\r\n * @param rawName - 原始事件名\r\n * @returns 缓存 key\r\n */\r\n getEventKey(rawName: string): string {\r\n const name = this.normalizeEventName(rawName);\r\n // FIX: P2-v11-20 添加空字符串检查,避免 name 为空时 name[0] 返回 undefined\r\n if (!name) return 'on';\r\n return 'on' + name[0]!.toUpperCase() + name.slice(1);\r\n }\r\n\r\n // ==========================================================\r\n // Invoker 缓存模式\r\n // ==========================================================\r\n\r\n /**\r\n * 更新元素上的事件监听(invoker 缓存模式)。\r\n *\r\n * 四种情况:\r\n * 1. nextValue && existingInvoker → 直接替换 invoker.value(O(1) 赋值)\r\n * 2. nextValue && !existingInvoker → 创建 invoker,addEventListener\r\n * 3. !nextValue && existingInvoker → removeEventListener,清除缓存\r\n * 4. !nextValue && !existingInvoker → 无操作\r\n *\r\n * @param el - 宿主元素\r\n * @param rawName - 原始事件名\r\n * @param nextValue - 新的事件处理函数\r\n */\r\n patchEvent(\r\n el: HE,\r\n rawName: string,\r\n nextValue: HostEventHandler | null,\r\n ): void {\r\n const eventKey = this.getEventKey(rawName);\r\n const parsed = this.parseEventName(rawName);\r\n\r\n // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储\r\n let invokers = this.invokerCache.get(el);\r\n if (!invokers) {\r\n invokers = {};\r\n this.invokerCache.set(el, invokers);\r\n }\r\n\r\n const existingInvoker = invokers[eventKey];\r\n\r\n if (nextValue && existingInvoker) {\r\n // 情况 1:有新值 + 有旧 invoker → 直接替换 value\r\n existingInvoker.value = nextValue;\r\n } else if (nextValue && !existingInvoker) {\r\n // 情况 2:有新值 + 无旧 invoker → 创建并绑定\r\n const invoker = this.createInvoker(el, parsed, nextValue);\r\n invokers[eventKey] = invoker;\r\n\r\n const options = this.buildHostEventOptions(parsed.modifiers);\r\n // FIX: P2 保存 addEventListener 返回的 dispose 函数到 invoker,\r\n // 确保后续可以通过 invoker.dispose() 正确移除事件监听\r\n const hostDispose = this.host.addEventListener(el, parsed.name, invoker.handler, options);\r\n invoker.dispose = () => {\r\n hostDispose();\r\n // 同时清理内部状态\r\n (invoker as { value?: HostEventHandler | null }).value = null;\r\n };\r\n } else if (!nextValue && existingInvoker) {\r\n // 情况 3:无新值 + 有旧 invoker → 移除\r\n const options = this.buildHostEventOptions(parsed.modifiers);\r\n this.host.removeEventListener(el, parsed.name, existingInvoker.handler, options);\r\n invokers[eventKey] = undefined;\r\n }\r\n // 情况 4:无新值 + 无旧 invoker → 无操作\r\n }\r\n\r\n /**\r\n * 移除元素上所有通过 invoker 缓存的事件监听。\r\n *\r\n * @param el - 宿主元素\r\n */\r\n removeAllEventListeners(el: HE): void {\r\n // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储\r\n const invokers = this.invokerCache.get(el);\r\n if (!invokers) return;\r\n\r\n // FIX: P2-47 使用 Object.keys 替代 for...in,避免遍历原型链上的属性\r\n for (const eventKey of Object.keys(invokers)) {\r\n const invoker = invokers[eventKey];\r\n if (invoker) {\r\n // FIX: P1-48 使用 invoker.handler 而非原始 handler 引用移除事件监听器,\r\n // 确保能正确匹配 addEventListener 时注册的包装 handler\r\n const options = this.buildHostEventOptions(invoker.parsed.modifiers);\r\n this.host.removeEventListener(el, invoker.parsed.name, invoker.handler, options);\r\n }\r\n }\r\n\r\n // 从 WeakMap 中删除该元素的缓存\r\n this.invokerCache.delete(el);\r\n }\r\n\r\n // ==========================================================\r\n // 内部方法\r\n // ==========================================================\r\n\r\n /**\r\n * 创建事件 invoker。\r\n *\r\n * invoker 持有 value 属性,调用时执行 invoker.value(event)。\r\n * 修饰符在 invoker 内部处理,更新时仅需替换 value。\r\n */\r\n private createInvoker(\r\n el: HE,\r\n parsed: ParsedEventInfo,\r\n initialValue: HostEventHandler,\r\n ): EventInvoker<HE> {\r\n let currentValue: HostEventHandler | null = initialValue;\r\n let disposed = false;\r\n\r\n const handler: HostEventHandler = (event: HostEvent) => {\r\n if (disposed) return;\r\n\r\n // 处理修饰符\r\n if (parsed.modifiers.stop) {\r\n event.stopPropagation();\r\n }\r\n if (parsed.modifiers.prevent) {\r\n event.preventDefault();\r\n }\r\n if (parsed.modifiers.self && event.target !== event.currentTarget) {\r\n return;\r\n }\r\n\r\n if (currentValue) {\r\n currentValue(event);\r\n }\r\n };\r\n\r\n const invoker: EventInvoker<HE> = {\r\n get value() {\r\n return currentValue;\r\n },\r\n set value(fn: HostEventHandler | null) {\r\n currentValue = fn;\r\n },\r\n parsed,\r\n el,\r\n handler,\r\n dispose: () => {\r\n disposed = true;\r\n currentValue = null;\r\n },\r\n };\r\n\r\n return invoker;\r\n }\r\n\r\n /**\r\n * 根据 ParsedModifiers 构建 HostEventOptions。\r\n */\r\n private buildHostEventOptions(modifiers: ParsedModifiers): HostEventOptions | undefined {\r\n if (modifiers.capture || modifiers.once || modifiers.passive) {\r\n const options: HostEventOptions = {};\r\n if (modifiers.capture) options.capture = true;\r\n if (modifiers.once) options.once = true;\r\n if (modifiers.passive) options.passive = true;\r\n return options;\r\n }\r\n return undefined;\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["EVENT_MODIFIER_RE"],"mappings":"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","file":"index.cjs","sourcesContent":["// @lytjs/common-event-normalizer\n// 事件归一化:解析事件名、提供 invoker 缓存模式、通过 RendererHost 操作事件\n\nimport type {\n RendererHost,\n HostEvent,\n HostEventHandler,\n HostEventOptions,\n} from '@lytjs/host-contract';\nimport { EVENT_MODIFIER_RE } from '@lytjs/common-events';\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 事件修饰符解析结果。\n */\nexport interface ParsedModifiers {\n /** 是否调用 stopPropagation */\n stop: boolean;\n /** 是否调用 preventDefault */\n prevent: boolean;\n /** 是否使用 capture 模式 */\n capture: boolean;\n /** 是否使用 once 模式 */\n once: boolean;\n /** 是否仅在 target === currentTarget 时触发 */\n self: boolean;\n /** 是否使用 passive 模式 */\n passive: boolean;\n}\n\n/**\n * 解析后的事件信息。\n */\nexport interface ParsedEventInfo {\n /** 规范化后的事件名(如 'click') */\n name: string;\n /** 事件修饰符 */\n modifiers: ParsedModifiers;\n}\n\n/**\n * 事件 invoker 接口。\n *\n * 持有 value 属性用于更新,避免重复 addEventListener。\n */\nexport interface EventInvoker<HE> {\n /** 当前绑定的事件处理函数 */\n value: ((event: HostEvent) => void) | null;\n /** 解析后的事件信息 */\n parsed: ParsedEventInfo;\n /** 宿主元素引用 */\n el: HE;\n /** 实际的事件处理函数(传给 addEventListener 的函数) */\n handler: (event: HostEvent) => void;\n /** 取消监听函数 */\n dispose: () => void;\n}\n\n/**\n * 事件监听器注册条目。\n */\nexport interface EventListenerEntry<HE> {\n /** 宿主元素 */\n el: HE;\n /** 事件名 */\n event: string;\n /** 处理函数 */\n handler: (event: HostEvent) => void;\n /** 事件选项 */\n options?: { capture?: boolean; once?: boolean; passive?: boolean };\n}\n\n// ============================================================\n// 常量\n// ============================================================\n\n/** invoker 缓存类型 */\ntype InvokerCache<HE> = Record<string, EventInvoker<HE> | undefined>;\n\n// ============================================================\n// EventNormalizer\n// ============================================================\n\n/**\n * 事件归一化器。\n *\n * 负责解析事件名中的修饰符、管理事件 invoker 缓存,\n * 通过 RendererHost 接口执行平台无关的事件操作。\n *\n * @template HN - 宿主节点类型\n * @template HE - 宿主元素类型\n */\n// FIX: DTS build error - HN 必须约束为 object\nexport class EventNormalizer<HN extends object = object, HE extends HN = HN> {\n /** RendererHost 实例 */\n private host: RendererHost<HN, HE>;\n\n /**\n * FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储,避免类型断言和属性污染\n * 键:宿主元素,值:该元素的事件 invoker 缓存\n */\n private invokerCache = new WeakMap<HE, InvokerCache<HE>>();\n\n /**\n * 创建事件归一化器实例。\n * @param host - RendererHost 实例\n */\n constructor(host: RendererHost<HN, HE>) {\n this.host = host;\n }\n\n // ==========================================================\n // 事件名解析\n // ==========================================================\n\n /**\n * 解析事件名。\n *\n * 将各种格式的事件名规范化并提取修饰符:\n * - onClick.stop.prevent → { name: 'click', modifiers: { stop: true, prevent: true } }\n * - @click.capture → { name: 'click', modifiers: { capture: true } }\n * - click → { name: 'click', modifiers: { stop: false, ... } }\n *\n * @param rawName - 原始事件名\n * @returns 解析后的事件信息\n */\n parseEventName(rawName: string): ParsedEventInfo {\n const name = this.normalizeEventName(rawName);\n const modifiers = this.parseModifiers(rawName);\n\n return { name, modifiers };\n }\n\n /**\n * 将原始事件名规范化为标准事件名。\n *\n * 支持格式:@click / onClick / click → click\n *\n * @param rawName - 原始事件名\n * @returns 规范化后的事件名\n */\n normalizeEventName(rawName: string): string {\n // 移除 @ 前缀\n let name = rawName.startsWith('@') ? rawName.slice(1) : rawName;\n\n // 移除 on 前缀(仅当以大写字母开头的 on 前缀时)\n // FIX: P2-45 使用 charAt 替代非空断言,更安全地访问字符串字符\n if (name.startsWith('on') && name.length > 2 && /^[A-Za-z]$/.test(name.charAt(2))) {\n name = name.slice(2);\n }\n\n // 移除修饰符后缀(如 .stop.prevent)\n name = name.replace(EVENT_MODIFIER_RE, '');\n\n // 转为小写\n return name.toLowerCase();\n }\n\n /**\n * 解析事件名中的修饰符。\n *\n * @param rawName - 原始事件名\n * @returns 修饰符集合\n */\n parseModifiers(rawName: string): ParsedModifiers {\n const modifiers: ParsedModifiers = {\n stop: false,\n prevent: false,\n capture: false,\n once: false,\n self: false,\n passive: false,\n };\n\n // FIX: P2-23 正则表达式缓存 - 模块级预编译正则\n const RE_EVENT_MODIFIERS = /\\.(stop|prevent|capture|once|self|passive)/g;\n // FIX: P2-41 事件捕获选项支持:支持 .capture 修饰符\n const modifierMatch = rawName.match(RE_EVENT_MODIFIERS);\n if (modifierMatch) {\n for (const mod of modifierMatch) {\n switch (mod) {\n case '.stop':\n modifiers.stop = true;\n break;\n case '.prevent':\n modifiers.prevent = true;\n break;\n case '.capture':\n modifiers.capture = true;\n break;\n case '.once':\n modifiers.once = true;\n break;\n case '.self':\n modifiers.self = true;\n break;\n case '.passive':\n modifiers.passive = true;\n break;\n }\n }\n }\n\n return modifiers;\n }\n\n // ==========================================================\n // 事件名 → 缓存 key 转换\n // ==========================================================\n\n /**\n * 将事件名转换为 invoker 缓存的 key。\n *\n * click → onClick,mouseenter → onMouseenter\n *\n * @param rawName - 原始事件名\n * @returns 缓存 key\n */\n getEventKey(rawName: string): string {\n const name = this.normalizeEventName(rawName);\n // FIX: P2-v11-20 添加空字符串检查,避免 name 为空时 name[0] 返回 undefined\n if (!name) return 'on';\n return 'on' + name[0]!.toUpperCase() + name.slice(1);\n }\n\n // ==========================================================\n // Invoker 缓存模式\n // ==========================================================\n\n /**\n * 更新元素上的事件监听(invoker 缓存模式)。\n *\n * 四种情况:\n * 1. nextValue && existingInvoker → 直接替换 invoker.value(O(1) 赋值)\n * 2. nextValue && !existingInvoker → 创建 invoker,addEventListener\n * 3. !nextValue && existingInvoker → removeEventListener,清除缓存\n * 4. !nextValue && !existingInvoker → 无操作\n *\n * @param el - 宿主元素\n * @param rawName - 原始事件名\n * @param nextValue - 新的事件处理函数\n */\n patchEvent(el: HE, rawName: string, nextValue: HostEventHandler | null): void {\n const eventKey = this.getEventKey(rawName);\n const parsed = this.parseEventName(rawName);\n\n // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储\n let invokers = this.invokerCache.get(el);\n if (!invokers) {\n invokers = {};\n this.invokerCache.set(el, invokers);\n }\n\n const existingInvoker = invokers[eventKey];\n\n if (nextValue && existingInvoker) {\n // 情况 1:有新值 + 有旧 invoker → 直接替换 value\n existingInvoker.value = nextValue;\n } else if (nextValue && !existingInvoker) {\n // 情况 2:有新值 + 无旧 invoker → 创建并绑定\n const invoker = this.createInvoker(el, parsed, nextValue);\n invokers[eventKey] = invoker;\n\n const options = this.buildHostEventOptions(parsed.modifiers);\n // FIX: P2 保存 addEventListener 返回的 dispose 函数到 invoker,\n // 确保后续可以通过 invoker.dispose() 正确移除事件监听\n const hostDispose = this.host.addEventListener(el, parsed.name, invoker.handler, options);\n invoker.dispose = () => {\n hostDispose();\n // 同时清理内部状态\n (invoker as { value?: HostEventHandler | null }).value = null;\n };\n } else if (!nextValue && existingInvoker) {\n // 情况 3:无新值 + 有旧 invoker → 移除\n const options = this.buildHostEventOptions(parsed.modifiers);\n this.host.removeEventListener(el, parsed.name, existingInvoker.handler, options);\n invokers[eventKey] = undefined;\n }\n // 情况 4:无新值 + 无旧 invoker → 无操作\n }\n\n /**\n * 移除元素上所有通过 invoker 缓存的事件监听。\n *\n * @param el - 宿主元素\n */\n removeAllEventListeners(el: HE): void {\n // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储\n const invokers = this.invokerCache.get(el);\n if (!invokers) return;\n\n // FIX: P2-47 使用 Object.keys 替代 for...in,避免遍历原型链上的属性\n for (const eventKey of Object.keys(invokers)) {\n const invoker = invokers[eventKey];\n if (invoker) {\n // FIX: P1-48 使用 invoker.handler 而非原始 handler 引用移除事件监听器,\n // 确保能正确匹配 addEventListener 时注册的包装 handler\n const options = this.buildHostEventOptions(invoker.parsed.modifiers);\n this.host.removeEventListener(el, invoker.parsed.name, invoker.handler, options);\n }\n }\n\n // 从 WeakMap 中删除该元素的缓存\n this.invokerCache.delete(el);\n }\n\n // ==========================================================\n // 内部方法\n // ==========================================================\n\n /**\n * 创建事件 invoker。\n *\n * invoker 持有 value 属性,调用时执行 invoker.value(event)。\n * 修饰符在 invoker 内部处理,更新时仅需替换 value。\n */\n private createInvoker(\n el: HE,\n parsed: ParsedEventInfo,\n initialValue: HostEventHandler,\n ): EventInvoker<HE> {\n let currentValue: HostEventHandler | null = initialValue;\n let disposed = false;\n\n const handler: HostEventHandler = (event: HostEvent) => {\n if (disposed) return;\n\n // 处理修饰符\n if (parsed.modifiers.stop) {\n event.stopPropagation();\n }\n if (parsed.modifiers.prevent) {\n event.preventDefault();\n }\n if (parsed.modifiers.self && event.target !== event.currentTarget) {\n return;\n }\n\n if (currentValue) {\n currentValue(event);\n }\n };\n\n const invoker: EventInvoker<HE> = {\n get value() {\n return currentValue;\n },\n set value(fn: HostEventHandler | null) {\n currentValue = fn;\n },\n parsed,\n el,\n handler,\n dispose: () => {\n disposed = true;\n currentValue = null;\n },\n };\n\n return invoker;\n }\n\n /**\n * 根据 ParsedModifiers 构建 HostEventOptions。\n */\n private buildHostEventOptions(modifiers: ParsedModifiers): HostEventOptions | undefined {\n if (modifiers.capture || modifiers.once || modifiers.passive) {\n const options: HostEventOptions = {};\n if (modifiers.capture) options.capture = true;\n if (modifiers.once) options.once = true;\n if (modifiers.passive) options.passive = true;\n return options;\n }\n return undefined;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @lytjs/common-event-normalizer\r\n// 事件归一化:解析事件名、提供 invoker 缓存模式、通过 RendererHost 操作事件\r\n\r\nimport type { RendererHost, HostEvent, HostEventHandler, HostEventOptions } from '@lytjs/host-contract';\r\nimport { EVENT_MODIFIER_RE } from '@lytjs/common-events';\r\n\r\n// ============================================================\r\n// 类型定义\r\n// ============================================================\r\n\r\n/**\r\n * 事件修饰符解析结果。\r\n */\r\nexport interface ParsedModifiers {\r\n /** 是否调用 stopPropagation */\r\n stop: boolean;\r\n /** 是否调用 preventDefault */\r\n prevent: boolean;\r\n /** 是否使用 capture 模式 */\r\n capture: boolean;\r\n /** 是否使用 once 模式 */\r\n once: boolean;\r\n /** 是否仅在 target === currentTarget 时触发 */\r\n self: boolean;\r\n /** 是否使用 passive 模式 */\r\n passive: boolean;\r\n}\r\n\r\n/**\r\n * 解析后的事件信息。\r\n */\r\nexport interface ParsedEventInfo {\r\n /** 规范化后的事件名(如 'click') */\r\n name: string;\r\n /** 事件修饰符 */\r\n modifiers: ParsedModifiers;\r\n}\r\n\r\n/**\r\n * 事件 invoker 接口。\r\n *\r\n * 持有 value 属性用于更新,避免重复 addEventListener。\r\n */\r\nexport interface EventInvoker<HE> {\r\n /** 当前绑定的事件处理函数 */\r\n value: ((event: HostEvent) => void) | null;\r\n /** 解析后的事件信息 */\r\n parsed: ParsedEventInfo;\r\n /** 宿主元素引用 */\r\n el: HE;\r\n /** 实际的事件处理函数(传给 addEventListener 的函数) */\r\n handler: (event: HostEvent) => void;\r\n /** 取消监听函数 */\r\n dispose: () => void;\r\n}\r\n\r\n/**\r\n * 事件监听器注册条目。\r\n */\r\nexport interface EventListenerEntry<HE> {\r\n /** 宿主元素 */\r\n el: HE;\r\n /** 事件名 */\r\n event: string;\r\n /** 处理函数 */\r\n handler: (event: HostEvent) => void;\r\n /** 事件选项 */\r\n options?: { capture?: boolean; once?: boolean; passive?: boolean };\r\n}\r\n\r\n// ============================================================\r\n// 常量\r\n// ============================================================\r\n\r\n/** invoker 缓存类型 */\r\ntype InvokerCache<HE> = Record<string, EventInvoker<HE> | undefined>;\r\n\r\n// ============================================================\r\n// EventNormalizer\r\n// ============================================================\r\n\r\n/**\r\n * 事件归一化器。\r\n *\r\n * 负责解析事件名中的修饰符、管理事件 invoker 缓存,\r\n * 通过 RendererHost 接口执行平台无关的事件操作。\r\n *\r\n * @template HN - 宿主节点类型\r\n * @template HE - 宿主元素类型\r\n */\r\n// FIX: DTS build error - HN 必须约束为 object\r\nexport class EventNormalizer<HN extends object = object, HE extends HN = HN> {\r\n /** RendererHost 实例 */\r\n private host: RendererHost<HN, HE>;\r\n\r\n /**\r\n * FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储,避免类型断言和属性污染\r\n * 键:宿主元素,值:该元素的事件 invoker 缓存\r\n */\r\n private invokerCache = new WeakMap<HE, InvokerCache<HE>>();\r\n\r\n /**\r\n * 创建事件归一化器实例。\r\n * @param host - RendererHost 实例\r\n */\r\n constructor(host: RendererHost<HN, HE>) {\r\n this.host = host;\r\n }\r\n\r\n // ==========================================================\r\n // 事件名解析\r\n // ==========================================================\r\n\r\n /**\r\n * 解析事件名。\r\n *\r\n * 将各种格式的事件名规范化并提取修饰符:\r\n * - onClick.stop.prevent → { name: 'click', modifiers: { stop: true, prevent: true } }\r\n * - @click.capture → { name: 'click', modifiers: { capture: true } }\r\n * - click → { name: 'click', modifiers: { stop: false, ... } }\r\n *\r\n * @param rawName - 原始事件名\r\n * @returns 解析后的事件信息\r\n */\r\n parseEventName(rawName: string): ParsedEventInfo {\r\n const name = this.normalizeEventName(rawName);\r\n const modifiers = this.parseModifiers(rawName);\r\n\r\n return { name, modifiers };\r\n }\r\n\r\n /**\r\n * 将原始事件名规范化为标准事件名。\r\n *\r\n * 支持格式:@click / onClick / click → click\r\n *\r\n * @param rawName - 原始事件名\r\n * @returns 规范化后的事件名\r\n */\r\n normalizeEventName(rawName: string): string {\r\n // 移除 @ 前缀\r\n let name = rawName.startsWith('@') ? rawName.slice(1) : rawName;\r\n\r\n // 移除 on 前缀(仅当以大写字母开头的 on 前缀时)\r\n // FIX: P2-45 使用 charAt 替代非空断言,更安全地访问字符串字符\r\n if (name.startsWith('on') && name.length > 2 && /^[A-Za-z]$/.test(name.charAt(2))) {\r\n name = name.slice(2);\r\n }\r\n\r\n // 移除修饰符后缀(如 .stop.prevent)\r\n name = name.replace(EVENT_MODIFIER_RE, '');\r\n\r\n // 转为小写\r\n return name.toLowerCase();\r\n }\r\n\r\n /**\r\n * 解析事件名中的修饰符。\r\n *\r\n * @param rawName - 原始事件名\r\n * @returns 修饰符集合\r\n */\r\n parseModifiers(rawName: string): ParsedModifiers {\r\n const modifiers: ParsedModifiers = {\r\n stop: false,\r\n prevent: false,\r\n capture: false,\r\n once: false,\r\n self: false,\r\n passive: false,\r\n };\r\n\r\n // FIX: P2-23 正则表达式缓存 - 模块级预编译正则\r\n const RE_EVENT_MODIFIERS = /\\.(stop|prevent|capture|once|self|passive)/g;\r\n // FIX: P2-41 事件捕获选项支持:支持 .capture 修饰符\r\n const modifierMatch = rawName.match(RE_EVENT_MODIFIERS);\r\n if (modifierMatch) {\r\n for (const mod of modifierMatch) {\r\n switch (mod) {\r\n case '.stop':\r\n modifiers.stop = true;\r\n break;\r\n case '.prevent':\r\n modifiers.prevent = true;\r\n break;\r\n case '.capture':\r\n modifiers.capture = true;\r\n break;\r\n case '.once':\r\n modifiers.once = true;\r\n break;\r\n case '.self':\r\n modifiers.self = true;\r\n break;\r\n case '.passive':\r\n modifiers.passive = true;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n return modifiers;\r\n }\r\n\r\n // ==========================================================\r\n // 事件名 → 缓存 key 转换\r\n // ==========================================================\r\n\r\n /**\r\n * 将事件名转换为 invoker 缓存的 key。\r\n *\r\n * click → onClick,mouseenter → onMouseenter\r\n *\r\n * @param rawName - 原始事件名\r\n * @returns 缓存 key\r\n */\r\n getEventKey(rawName: string): string {\r\n const name = this.normalizeEventName(rawName);\r\n // FIX: P2-v11-20 添加空字符串检查,避免 name 为空时 name[0] 返回 undefined\r\n if (!name) return 'on';\r\n return 'on' + name[0]!.toUpperCase() + name.slice(1);\r\n }\r\n\r\n // ==========================================================\r\n // Invoker 缓存模式\r\n // ==========================================================\r\n\r\n /**\r\n * 更新元素上的事件监听(invoker 缓存模式)。\r\n *\r\n * 四种情况:\r\n * 1. nextValue && existingInvoker → 直接替换 invoker.value(O(1) 赋值)\r\n * 2. nextValue && !existingInvoker → 创建 invoker,addEventListener\r\n * 3. !nextValue && existingInvoker → removeEventListener,清除缓存\r\n * 4. !nextValue && !existingInvoker → 无操作\r\n *\r\n * @param el - 宿主元素\r\n * @param rawName - 原始事件名\r\n * @param nextValue - 新的事件处理函数\r\n */\r\n patchEvent(\r\n el: HE,\r\n rawName: string,\r\n nextValue: HostEventHandler | null,\r\n ): void {\r\n const eventKey = this.getEventKey(rawName);\r\n const parsed = this.parseEventName(rawName);\r\n\r\n // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储\r\n let invokers = this.invokerCache.get(el);\r\n if (!invokers) {\r\n invokers = {};\r\n this.invokerCache.set(el, invokers);\r\n }\r\n\r\n const existingInvoker = invokers[eventKey];\r\n\r\n if (nextValue && existingInvoker) {\r\n // 情况 1:有新值 + 有旧 invoker → 直接替换 value\r\n existingInvoker.value = nextValue;\r\n } else if (nextValue && !existingInvoker) {\r\n // 情况 2:有新值 + 无旧 invoker → 创建并绑定\r\n const invoker = this.createInvoker(el, parsed, nextValue);\r\n invokers[eventKey] = invoker;\r\n\r\n const options = this.buildHostEventOptions(parsed.modifiers);\r\n // FIX: P2 保存 addEventListener 返回的 dispose 函数到 invoker,\r\n // 确保后续可以通过 invoker.dispose() 正确移除事件监听\r\n const hostDispose = this.host.addEventListener(el, parsed.name, invoker.handler, options);\r\n invoker.dispose = () => {\r\n hostDispose();\r\n // 同时清理内部状态\r\n (invoker as { value?: HostEventHandler | null }).value = null;\r\n };\r\n } else if (!nextValue && existingInvoker) {\r\n // 情况 3:无新值 + 有旧 invoker → 移除\r\n const options = this.buildHostEventOptions(parsed.modifiers);\r\n this.host.removeEventListener(el, parsed.name, existingInvoker.handler, options);\r\n invokers[eventKey] = undefined;\r\n }\r\n // 情况 4:无新值 + 无旧 invoker → 无操作\r\n }\r\n\r\n /**\r\n * 移除元素上所有通过 invoker 缓存的事件监听。\r\n *\r\n * @param el - 宿主元素\r\n */\r\n removeAllEventListeners(el: HE): void {\r\n // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储\r\n const invokers = this.invokerCache.get(el);\r\n if (!invokers) return;\r\n\r\n // FIX: P2-47 使用 Object.keys 替代 for...in,避免遍历原型链上的属性\r\n for (const eventKey of Object.keys(invokers)) {\r\n const invoker = invokers[eventKey];\r\n if (invoker) {\r\n // FIX: P1-48 使用 invoker.handler 而非原始 handler 引用移除事件监听器,\r\n // 确保能正确匹配 addEventListener 时注册的包装 handler\r\n const options = this.buildHostEventOptions(invoker.parsed.modifiers);\r\n this.host.removeEventListener(el, invoker.parsed.name, invoker.handler, options);\r\n }\r\n }\r\n\r\n // 从 WeakMap 中删除该元素的缓存\r\n this.invokerCache.delete(el);\r\n }\r\n\r\n // ==========================================================\r\n // 内部方法\r\n // ==========================================================\r\n\r\n /**\r\n * 创建事件 invoker。\r\n *\r\n * invoker 持有 value 属性,调用时执行 invoker.value(event)。\r\n * 修饰符在 invoker 内部处理,更新时仅需替换 value。\r\n */\r\n private createInvoker(\r\n el: HE,\r\n parsed: ParsedEventInfo,\r\n initialValue: HostEventHandler,\r\n ): EventInvoker<HE> {\r\n let currentValue: HostEventHandler | null = initialValue;\r\n let disposed = false;\r\n\r\n const handler: HostEventHandler = (event: HostEvent) => {\r\n if (disposed) return;\r\n\r\n // 处理修饰符\r\n if (parsed.modifiers.stop) {\r\n event.stopPropagation();\r\n }\r\n if (parsed.modifiers.prevent) {\r\n event.preventDefault();\r\n }\r\n if (parsed.modifiers.self && event.target !== event.currentTarget) {\r\n return;\r\n }\r\n\r\n if (currentValue) {\r\n currentValue(event);\r\n }\r\n };\r\n\r\n const invoker: EventInvoker<HE> = {\r\n get value() {\r\n return currentValue;\r\n },\r\n set value(fn: HostEventHandler | null) {\r\n currentValue = fn;\r\n },\r\n parsed,\r\n el,\r\n handler,\r\n dispose: () => {\r\n disposed = true;\r\n currentValue = null;\r\n },\r\n };\r\n\r\n return invoker;\r\n }\r\n\r\n /**\r\n * 根据 ParsedModifiers 构建 HostEventOptions。\r\n */\r\n private buildHostEventOptions(modifiers: ParsedModifiers): HostEventOptions | undefined {\r\n if (modifiers.capture || modifiers.once || modifiers.passive) {\r\n const options: HostEventOptions = {};\r\n if (modifiers.capture) options.capture = true;\r\n if (modifiers.once) options.once = true;\r\n if (modifiers.passive) options.passive = true;\r\n return options;\r\n }\r\n return undefined;\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAgGO,IAAM,kBAAN,MAAsE;AAAA;AAAA;AAAA;AAAA;AAAA,EAc3E,YAAY,IAAA,EAA4B;AANxC;AAAA;AAAA;AAAA;AAAA,IAAA,IAAA,CAAQ,YAAA,uBAAmB,OAAA,EAA8B;AAOvD,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,eAAe,OAAA,EAAkC;AAC/C,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,kBAAA,CAAmB,OAAO,CAAA;AAC5C,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,cAAA,CAAe,OAAO,CAAA;AAE7C,IAAA,OAAO,EAAE,MAAM,SAAA,EAAU;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,mBAAmB,OAAA,EAAyB;AAE1C,IAAA,IAAI,IAAA,GAAO,QAAQ,UAAA,CAAW,GAAG,IAAI,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,GAAI,OAAA;AAIxD,IAAA,IAAI,IAAA,CAAK,UAAA,CAAW,IAAI,CAAA,IAAK,IAAA,CAAK,MAAA,GAAS,CAAA,IAAK,YAAA,CAAa,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC,CAAA,EAAG;AACjF,MAAA,IAAA,GAAO,IAAA,CAAK,MAAM,CAAC,CAAA;AAAA,IACrB;AAGA,IAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAA;AAGzC,IAAA,OAAO,KAAK,WAAA,EAAY;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,OAAA,EAAkC;AAC/C,IAAA,MAAM,SAAA,GAA6B;AAAA,MACjC,IAAA,EAAM,KAAA;AAAA,MACN,OAAA,EAAS,KAAA;AAAA,MACT,OAAA,EAAS,KAAA;AAAA,MACT,IAAA,EAAM,KAAA;AAAA,MACN,IAAA,EAAM,KAAA;AAAA,MACN,OAAA,EAAS;AAAA,KACX;AAGA,IAAA,MAAM,kBAAA,GAAqB,6CAAA;AAE3B,IAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,KAAA,CAAM,kBAAkB,CAAA;AACtD,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,KAAA,MAAW,OAAO,aAAA,EAAe;AAC/B,QAAA,QAAQ,GAAA;AAAK,UACX,KAAK,OAAA;AACH,YAAA,SAAA,CAAU,IAAA,GAAO,IAAA;AACjB,YAAA;AAAA,UACF,KAAK,UAAA;AACH,YAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AACpB,YAAA;AAAA,UACF,KAAK,UAAA;AACH,YAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AACpB,YAAA;AAAA,UACF,KAAK,OAAA;AACH,YAAA,SAAA,CAAU,IAAA,GAAO,IAAA;AACjB,YAAA;AAAA,UACF,KAAK,OAAA;AACH,YAAA,SAAA,CAAU,IAAA,GAAO,IAAA;AACjB,YAAA;AAAA,UACF,KAAK,UAAA;AACH,YAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AACpB,YAAA;AAAA;AACJ,MACF;AAAA,IACF;AAEA,IAAA,OAAO,SAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,YAAY,OAAA,EAAyB;AACnC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,kBAAA,CAAmB,OAAO,CAAA;AAE5C,IAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,IAAA,OAAO,IAAA,GAAO,KAAK,CAAC,CAAA,CAAG,aAAY,GAAI,IAAA,CAAK,MAAM,CAAC,CAAA;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,UAAA,CAAW,EAAA,EAAQ,OAAA,EAAiB,SAAA,EAA0C;AAC5E,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,WAAA,CAAY,OAAO,CAAA;AACzC,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,cAAA,CAAe,OAAO,CAAA;AAG1C,IAAA,IAAI,QAAA,GAAW,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,EAAE,CAAA;AACvC,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,QAAA,GAAW,EAAC;AACZ,MAAA,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,EAAA,EAAI,QAAQ,CAAA;AAAA,IACpC;AAEA,IAAA,MAAM,eAAA,GAAkB,SAAS,QAAQ,CAAA;AAEzC,IAAA,IAAI,aAAa,eAAA,EAAiB;AAEhC,MAAA,eAAA,CAAgB,KAAA,GAAQ,SAAA;AAAA,IAC1B,CAAA,MAAA,IAAW,SAAA,IAAa,CAAC,eAAA,EAAiB;AAExC,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,aAAA,CAAc,EAAA,EAAI,QAAQ,SAAS,CAAA;AACxD,MAAA,QAAA,CAAS,QAAQ,CAAA,GAAI,OAAA;AAErB,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,qBAAA,CAAsB,MAAA,CAAO,SAAS,CAAA;AAG3D,MAAA,MAAM,WAAA,GAAc,KAAK,IAAA,CAAK,gBAAA,CAAiB,IAAI,MAAA,CAAO,IAAA,EAAM,OAAA,CAAQ,OAAA,EAAS,OAAO,CAAA;AACxF,MAAA,OAAA,CAAQ,UAAU,MAAM;AACtB,QAAA,WAAA,EAAY;AAEZ,QAAC,QAAgD,KAAA,GAAQ,IAAA;AAAA,MAC3D,CAAA;AAAA,IACF,CAAA,MAAA,IAAW,CAAC,SAAA,IAAa,eAAA,EAAiB;AAExC,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,qBAAA,CAAsB,MAAA,CAAO,SAAS,CAAA;AAC3D,MAAA,IAAA,CAAK,KAAK,mBAAA,CAAoB,EAAA,EAAI,OAAO,IAAA,EAAM,eAAA,CAAgB,SAAS,OAAO,CAAA;AAC/E,MAAA,QAAA,CAAS,QAAQ,CAAA,GAAI,MAAA;AAAA,IACvB;AAAA,EAEF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,wBAAwB,EAAA,EAAc;AAEpC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,EAAE,CAAA;AACzC,IAAA,IAAI,CAAC,QAAA,EAAU;AAGf,IAAA,KAAA,MAAW,QAAA,IAAY,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,EAAG;AAC5C,MAAA,MAAM,OAAA,GAAU,SAAS,QAAQ,CAAA;AACjC,MAAA,IAAI,OAAA,EAAS;AAGX,QAAA,MAAM,OAAA,GAAU,IAAA,CAAK,qBAAA,CAAsB,OAAA,CAAQ,OAAO,SAAS,CAAA;AACnE,QAAA,IAAA,CAAK,IAAA,CAAK,oBAAoB,EAAA,EAAI,OAAA,CAAQ,OAAO,IAAA,EAAM,OAAA,CAAQ,SAAS,OAAO,CAAA;AAAA,MACjF;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,YAAA,CAAa,OAAO,EAAE,CAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,aAAA,CACN,EAAA,EACA,MAAA,EACA,YAAA,EACkB;AAClB,IAAA,IAAI,YAAA,GAAwC,YAAA;AAC5C,IAAA,IAAI,QAAA,GAAW,KAAA;AAEf,IAAA,MAAM,OAAA,GAA4B,CAAC,KAAA,KAAqB;AACtD,MAAA,IAAI,QAAA,EAAU;AAGd,MAAA,IAAI,MAAA,CAAO,UAAU,IAAA,EAAM;AACzB,QAAA,KAAA,CAAM,eAAA,EAAgB;AAAA,MACxB;AACA,MAAA,IAAI,MAAA,CAAO,UAAU,OAAA,EAAS;AAC5B,QAAA,KAAA,CAAM,cAAA,EAAe;AAAA,MACvB;AACA,MAAA,IAAI,OAAO,SAAA,CAAU,IAAA,IAAQ,KAAA,CAAM,MAAA,KAAW,MAAM,aAAA,EAAe;AACjE,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,OAAA,GAA4B;AAAA,MAChC,IAAI,KAAA,GAAQ;AACV,QAAA,OAAO,YAAA;AAAA,MACT,CAAA;AAAA,MACA,IAAI,MAAM,EAAA,EAA6B;AACrC,QAAA,YAAA,GAAe,EAAA;AAAA,MACjB,CAAA;AAAA,MACA,MAAA;AAAA,MACA,EAAA;AAAA,MACA,OAAA;AAAA,MACA,SAAS,MAAM;AACb,QAAA,QAAA,GAAW,IAAA;AACX,QAAA,YAAA,GAAe,IAAA;AAAA,MACjB;AAAA,KACF;AAEA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,SAAA,EAA0D;AACtF,IAAA,IAAI,SAAA,CAAU,OAAA,IAAW,SAAA,CAAU,IAAA,IAAQ,UAAU,OAAA,EAAS;AAC5D,MAAA,MAAM,UAA4B,EAAC;AACnC,MAAA,IAAI,SAAA,CAAU,OAAA,EAAS,OAAA,CAAQ,OAAA,GAAU,IAAA;AACzC,MAAA,IAAI,SAAA,CAAU,IAAA,EAAM,OAAA,CAAQ,IAAA,GAAO,IAAA;AACnC,MAAA,IAAI,SAAA,CAAU,OAAA,EAAS,OAAA,CAAQ,OAAA,GAAU,IAAA;AACzC,MAAA,OAAO,OAAA;AAAA,IACT;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF","file":"index.mjs","sourcesContent":["// @lytjs/common-event-normalizer\n// 事件归一化:解析事件名、提供 invoker 缓存模式、通过 RendererHost 操作事件\n\nimport type {\n RendererHost,\n HostEvent,\n HostEventHandler,\n HostEventOptions,\n} from '@lytjs/host-contract';\nimport { EVENT_MODIFIER_RE } from '@lytjs/common-events';\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 事件修饰符解析结果。\n */\nexport interface ParsedModifiers {\n /** 是否调用 stopPropagation */\n stop: boolean;\n /** 是否调用 preventDefault */\n prevent: boolean;\n /** 是否使用 capture 模式 */\n capture: boolean;\n /** 是否使用 once 模式 */\n once: boolean;\n /** 是否仅在 target === currentTarget 时触发 */\n self: boolean;\n /** 是否使用 passive 模式 */\n passive: boolean;\n}\n\n/**\n * 解析后的事件信息。\n */\nexport interface ParsedEventInfo {\n /** 规范化后的事件名(如 'click') */\n name: string;\n /** 事件修饰符 */\n modifiers: ParsedModifiers;\n}\n\n/**\n * 事件 invoker 接口。\n *\n * 持有 value 属性用于更新,避免重复 addEventListener。\n */\nexport interface EventInvoker<HE> {\n /** 当前绑定的事件处理函数 */\n value: ((event: HostEvent) => void) | null;\n /** 解析后的事件信息 */\n parsed: ParsedEventInfo;\n /** 宿主元素引用 */\n el: HE;\n /** 实际的事件处理函数(传给 addEventListener 的函数) */\n handler: (event: HostEvent) => void;\n /** 取消监听函数 */\n dispose: () => void;\n}\n\n/**\n * 事件监听器注册条目。\n */\nexport interface EventListenerEntry<HE> {\n /** 宿主元素 */\n el: HE;\n /** 事件名 */\n event: string;\n /** 处理函数 */\n handler: (event: HostEvent) => void;\n /** 事件选项 */\n options?: { capture?: boolean; once?: boolean; passive?: boolean };\n}\n\n// ============================================================\n// 常量\n// ============================================================\n\n/** invoker 缓存类型 */\ntype InvokerCache<HE> = Record<string, EventInvoker<HE> | undefined>;\n\n// ============================================================\n// EventNormalizer\n// ============================================================\n\n/**\n * 事件归一化器。\n *\n * 负责解析事件名中的修饰符、管理事件 invoker 缓存,\n * 通过 RendererHost 接口执行平台无关的事件操作。\n *\n * @template HN - 宿主节点类型\n * @template HE - 宿主元素类型\n */\n// FIX: DTS build error - HN 必须约束为 object\nexport class EventNormalizer<HN extends object = object, HE extends HN = HN> {\n /** RendererHost 实例 */\n private host: RendererHost<HN, HE>;\n\n /**\n * FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储,避免类型断言和属性污染\n * 键:宿主元素,值:该元素的事件 invoker 缓存\n */\n private invokerCache = new WeakMap<HE, InvokerCache<HE>>();\n\n /**\n * 创建事件归一化器实例。\n * @param host - RendererHost 实例\n */\n constructor(host: RendererHost<HN, HE>) {\n this.host = host;\n }\n\n // ==========================================================\n // 事件名解析\n // ==========================================================\n\n /**\n * 解析事件名。\n *\n * 将各种格式的事件名规范化并提取修饰符:\n * - onClick.stop.prevent → { name: 'click', modifiers: { stop: true, prevent: true } }\n * - @click.capture → { name: 'click', modifiers: { capture: true } }\n * - click → { name: 'click', modifiers: { stop: false, ... } }\n *\n * @param rawName - 原始事件名\n * @returns 解析后的事件信息\n */\n parseEventName(rawName: string): ParsedEventInfo {\n const name = this.normalizeEventName(rawName);\n const modifiers = this.parseModifiers(rawName);\n\n return { name, modifiers };\n }\n\n /**\n * 将原始事件名规范化为标准事件名。\n *\n * 支持格式:@click / onClick / click → click\n *\n * @param rawName - 原始事件名\n * @returns 规范化后的事件名\n */\n normalizeEventName(rawName: string): string {\n // 移除 @ 前缀\n let name = rawName.startsWith('@') ? rawName.slice(1) : rawName;\n\n // 移除 on 前缀(仅当以大写字母开头的 on 前缀时)\n // FIX: P2-45 使用 charAt 替代非空断言,更安全地访问字符串字符\n if (name.startsWith('on') && name.length > 2 && /^[A-Za-z]$/.test(name.charAt(2))) {\n name = name.slice(2);\n }\n\n // 移除修饰符后缀(如 .stop.prevent)\n name = name.replace(EVENT_MODIFIER_RE, '');\n\n // 转为小写\n return name.toLowerCase();\n }\n\n /**\n * 解析事件名中的修饰符。\n *\n * @param rawName - 原始事件名\n * @returns 修饰符集合\n */\n parseModifiers(rawName: string): ParsedModifiers {\n const modifiers: ParsedModifiers = {\n stop: false,\n prevent: false,\n capture: false,\n once: false,\n self: false,\n passive: false,\n };\n\n // FIX: P2-23 正则表达式缓存 - 模块级预编译正则\n const RE_EVENT_MODIFIERS = /\\.(stop|prevent|capture|once|self|passive)/g;\n // FIX: P2-41 事件捕获选项支持:支持 .capture 修饰符\n const modifierMatch = rawName.match(RE_EVENT_MODIFIERS);\n if (modifierMatch) {\n for (const mod of modifierMatch) {\n switch (mod) {\n case '.stop':\n modifiers.stop = true;\n break;\n case '.prevent':\n modifiers.prevent = true;\n break;\n case '.capture':\n modifiers.capture = true;\n break;\n case '.once':\n modifiers.once = true;\n break;\n case '.self':\n modifiers.self = true;\n break;\n case '.passive':\n modifiers.passive = true;\n break;\n }\n }\n }\n\n return modifiers;\n }\n\n // ==========================================================\n // 事件名 → 缓存 key 转换\n // ==========================================================\n\n /**\n * 将事件名转换为 invoker 缓存的 key。\n *\n * click → onClick,mouseenter → onMouseenter\n *\n * @param rawName - 原始事件名\n * @returns 缓存 key\n */\n getEventKey(rawName: string): string {\n const name = this.normalizeEventName(rawName);\n // FIX: P2-v11-20 添加空字符串检查,避免 name 为空时 name[0] 返回 undefined\n if (!name) return 'on';\n return 'on' + name[0]!.toUpperCase() + name.slice(1);\n }\n\n // ==========================================================\n // Invoker 缓存模式\n // ==========================================================\n\n /**\n * 更新元素上的事件监听(invoker 缓存模式)。\n *\n * 四种情况:\n * 1. nextValue && existingInvoker → 直接替换 invoker.value(O(1) 赋值)\n * 2. nextValue && !existingInvoker → 创建 invoker,addEventListener\n * 3. !nextValue && existingInvoker → removeEventListener,清除缓存\n * 4. !nextValue && !existingInvoker → 无操作\n *\n * @param el - 宿主元素\n * @param rawName - 原始事件名\n * @param nextValue - 新的事件处理函数\n */\n patchEvent(el: HE, rawName: string, nextValue: HostEventHandler | null): void {\n const eventKey = this.getEventKey(rawName);\n const parsed = this.parseEventName(rawName);\n\n // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储\n let invokers = this.invokerCache.get(el);\n if (!invokers) {\n invokers = {};\n this.invokerCache.set(el, invokers);\n }\n\n const existingInvoker = invokers[eventKey];\n\n if (nextValue && existingInvoker) {\n // 情况 1:有新值 + 有旧 invoker → 直接替换 value\n existingInvoker.value = nextValue;\n } else if (nextValue && !existingInvoker) {\n // 情况 2:有新值 + 无旧 invoker → 创建并绑定\n const invoker = this.createInvoker(el, parsed, nextValue);\n invokers[eventKey] = invoker;\n\n const options = this.buildHostEventOptions(parsed.modifiers);\n // FIX: P2 保存 addEventListener 返回的 dispose 函数到 invoker,\n // 确保后续可以通过 invoker.dispose() 正确移除事件监听\n const hostDispose = this.host.addEventListener(el, parsed.name, invoker.handler, options);\n invoker.dispose = () => {\n hostDispose();\n // 同时清理内部状态\n (invoker as { value?: HostEventHandler | null }).value = null;\n };\n } else if (!nextValue && existingInvoker) {\n // 情况 3:无新值 + 有旧 invoker → 移除\n const options = this.buildHostEventOptions(parsed.modifiers);\n this.host.removeEventListener(el, parsed.name, existingInvoker.handler, options);\n invokers[eventKey] = undefined;\n }\n // 情况 4:无新值 + 无旧 invoker → 无操作\n }\n\n /**\n * 移除元素上所有通过 invoker 缓存的事件监听。\n *\n * @param el - 宿主元素\n */\n removeAllEventListeners(el: HE): void {\n // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储\n const invokers = this.invokerCache.get(el);\n if (!invokers) return;\n\n // FIX: P2-47 使用 Object.keys 替代 for...in,避免遍历原型链上的属性\n for (const eventKey of Object.keys(invokers)) {\n const invoker = invokers[eventKey];\n if (invoker) {\n // FIX: P1-48 使用 invoker.handler 而非原始 handler 引用移除事件监听器,\n // 确保能正确匹配 addEventListener 时注册的包装 handler\n const options = this.buildHostEventOptions(invoker.parsed.modifiers);\n this.host.removeEventListener(el, invoker.parsed.name, invoker.handler, options);\n }\n }\n\n // 从 WeakMap 中删除该元素的缓存\n this.invokerCache.delete(el);\n }\n\n // ==========================================================\n // 内部方法\n // ==========================================================\n\n /**\n * 创建事件 invoker。\n *\n * invoker 持有 value 属性,调用时执行 invoker.value(event)。\n * 修饰符在 invoker 内部处理,更新时仅需替换 value。\n */\n private createInvoker(\n el: HE,\n parsed: ParsedEventInfo,\n initialValue: HostEventHandler,\n ): EventInvoker<HE> {\n let currentValue: HostEventHandler | null = initialValue;\n let disposed = false;\n\n const handler: HostEventHandler = (event: HostEvent) => {\n if (disposed) return;\n\n // 处理修饰符\n if (parsed.modifiers.stop) {\n event.stopPropagation();\n }\n if (parsed.modifiers.prevent) {\n event.preventDefault();\n }\n if (parsed.modifiers.self && event.target !== event.currentTarget) {\n return;\n }\n\n if (currentValue) {\n currentValue(event);\n }\n };\n\n const invoker: EventInvoker<HE> = {\n get value() {\n return currentValue;\n },\n set value(fn: HostEventHandler | null) {\n currentValue = fn;\n },\n parsed,\n el,\n handler,\n dispose: () => {\n disposed = true;\n currentValue = null;\n },\n };\n\n return invoker;\n }\n\n /**\n * 根据 ParsedModifiers 构建 HostEventOptions。\n */\n private buildHostEventOptions(modifiers: ParsedModifiers): HostEventOptions | undefined {\n if (modifiers.capture || modifiers.once || modifiers.passive) {\n const options: HostEventOptions = {};\n if (modifiers.capture) options.capture = true;\n if (modifiers.once) options.once = true;\n if (modifiers.passive) options.passive = true;\n return options;\n }\n return undefined;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,38 +1,38 @@
1
- {
2
- "name": "@lytjs/common-event-normalizer",
3
- "version": "6.5.0",
4
- "description": "Event normalization utilities for parsing event names and managing invoker cache in LytJS",
5
- "main": "./dist/index.cjs",
6
- "module": "./dist/index.mjs",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.mjs",
12
- "require": "./dist/index.cjs"
13
- }
14
- },
15
- "files": [
16
- "dist",
17
- "src"
18
- ],
19
- "scripts": {
20
- "test": "vitest run",
21
- "test:watch": "vitest",
22
- "test:coverage": "vitest run --coverage",
23
- "build": "tsup",
24
- "type-check": "tsc --noEmit",
25
- "clean": "rm -rf dist"
26
- },
27
- "sideEffects": false,
28
- "license": "MIT",
29
- "dependencies": {
30
- "@lytjs/host-contract": "^6.0.0",
31
- "@lytjs/common-events": "^6.0.0"
32
- },
33
- "devDependencies": {
34
- "tsup": "^8.4.0",
35
- "typescript": "^5.8.2",
36
- "vitest": "^3.0.7"
37
- }
38
- }
1
+ {
2
+ "name": "@lytjs/common-event-normalizer",
3
+ "version": "6.6.0",
4
+ "description": "Event normalization utilities for parsing event names and managing invoker cache in LytJS",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "test:coverage": "vitest run --coverage",
23
+ "build": "tsup",
24
+ "type-check": "tsc --noEmit",
25
+ "clean": "rm -rf dist"
26
+ },
27
+ "sideEffects": false,
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@lytjs/host-contract": "workspace:*",
31
+ "@lytjs/common-events": "workspace:*"
32
+ },
33
+ "devDependencies": {
34
+ "tsup": "^8.4.0",
35
+ "typescript": "^5.8.2",
36
+ "vitest": "^3.0.7"
37
+ }
38
+ }
package/src/index.ts CHANGED
@@ -1,378 +1,379 @@
1
- // @lytjs/common-event-normalizer
2
- // 事件归一化:解析事件名、提供 invoker 缓存模式、通过 RendererHost 操作事件
3
-
4
- import type { RendererHost, HostEvent, HostEventHandler, HostEventOptions } from '@lytjs/host-contract';
5
- import { EVENT_MODIFIER_RE } from '@lytjs/common-events';
6
-
7
- // ============================================================
8
- // 类型定义
9
- // ============================================================
10
-
11
- /**
12
- * 事件修饰符解析结果。
13
- */
14
- export interface ParsedModifiers {
15
- /** 是否调用 stopPropagation */
16
- stop: boolean;
17
- /** 是否调用 preventDefault */
18
- prevent: boolean;
19
- /** 是否使用 capture 模式 */
20
- capture: boolean;
21
- /** 是否使用 once 模式 */
22
- once: boolean;
23
- /** 是否仅在 target === currentTarget 时触发 */
24
- self: boolean;
25
- /** 是否使用 passive 模式 */
26
- passive: boolean;
27
- }
28
-
29
- /**
30
- * 解析后的事件信息。
31
- */
32
- export interface ParsedEventInfo {
33
- /** 规范化后的事件名(如 'click') */
34
- name: string;
35
- /** 事件修饰符 */
36
- modifiers: ParsedModifiers;
37
- }
38
-
39
- /**
40
- * 事件 invoker 接口。
41
- *
42
- * 持有 value 属性用于更新,避免重复 addEventListener。
43
- */
44
- export interface EventInvoker<HE> {
45
- /** 当前绑定的事件处理函数 */
46
- value: ((event: HostEvent) => void) | null;
47
- /** 解析后的事件信息 */
48
- parsed: ParsedEventInfo;
49
- /** 宿主元素引用 */
50
- el: HE;
51
- /** 实际的事件处理函数(传给 addEventListener 的函数) */
52
- handler: (event: HostEvent) => void;
53
- /** 取消监听函数 */
54
- dispose: () => void;
55
- }
56
-
57
- /**
58
- * 事件监听器注册条目。
59
- */
60
- export interface EventListenerEntry<HE> {
61
- /** 宿主元素 */
62
- el: HE;
63
- /** 事件名 */
64
- event: string;
65
- /** 处理函数 */
66
- handler: (event: HostEvent) => void;
67
- /** 事件选项 */
68
- options?: { capture?: boolean; once?: boolean; passive?: boolean };
69
- }
70
-
71
- // ============================================================
72
- // 常量
73
- // ============================================================
74
-
75
- /** invoker 缓存类型 */
76
- type InvokerCache<HE> = Record<string, EventInvoker<HE> | undefined>;
77
-
78
- // ============================================================
79
- // EventNormalizer
80
- // ============================================================
81
-
82
- /**
83
- * 事件归一化器。
84
- *
85
- * 负责解析事件名中的修饰符、管理事件 invoker 缓存,
86
- * 通过 RendererHost 接口执行平台无关的事件操作。
87
- *
88
- * @template HN - 宿主节点类型
89
- * @template HE - 宿主元素类型
90
- */
91
- // FIX: DTS build error - HN 必须约束为 object
92
- export class EventNormalizer<HN extends object = object, HE extends HN = HN> {
93
- /** RendererHost 实例 */
94
- private host: RendererHost<HN, HE>;
95
-
96
- /**
97
- * FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储,避免类型断言和属性污染
98
- * 键:宿主元素,值:该元素的事件 invoker 缓存
99
- */
100
- private invokerCache = new WeakMap<HE, InvokerCache<HE>>();
101
-
102
- /**
103
- * 创建事件归一化器实例。
104
- * @param host - RendererHost 实例
105
- */
106
- constructor(host: RendererHost<HN, HE>) {
107
- this.host = host;
108
- }
109
-
110
- // ==========================================================
111
- // 事件名解析
112
- // ==========================================================
113
-
114
- /**
115
- * 解析事件名。
116
- *
117
- * 将各种格式的事件名规范化并提取修饰符:
118
- * - onClick.stop.prevent → { name: 'click', modifiers: { stop: true, prevent: true } }
119
- * - @click.capture → { name: 'click', modifiers: { capture: true } }
120
- * - click → { name: 'click', modifiers: { stop: false, ... } }
121
- *
122
- * @param rawName - 原始事件名
123
- * @returns 解析后的事件信息
124
- */
125
- parseEventName(rawName: string): ParsedEventInfo {
126
- const name = this.normalizeEventName(rawName);
127
- const modifiers = this.parseModifiers(rawName);
128
-
129
- return { name, modifiers };
130
- }
131
-
132
- /**
133
- * 将原始事件名规范化为标准事件名。
134
- *
135
- * 支持格式:@click / onClick / click → click
136
- *
137
- * @param rawName - 原始事件名
138
- * @returns 规范化后的事件名
139
- */
140
- normalizeEventName(rawName: string): string {
141
- // 移除 @ 前缀
142
- let name = rawName.startsWith('@') ? rawName.slice(1) : rawName;
143
-
144
- // 移除 on 前缀(仅当以大写字母开头的 on 前缀时)
145
- // FIX: P2-45 使用 charAt 替代非空断言,更安全地访问字符串字符
146
- if (name.startsWith('on') && name.length > 2 && /^[A-Za-z]$/.test(name.charAt(2))) {
147
- name = name.slice(2);
148
- }
149
-
150
- // 移除修饰符后缀(如 .stop.prevent)
151
- name = name.replace(EVENT_MODIFIER_RE, '');
152
-
153
- // 转为小写
154
- return name.toLowerCase();
155
- }
156
-
157
- /**
158
- * 解析事件名中的修饰符。
159
- *
160
- * @param rawName - 原始事件名
161
- * @returns 修饰符集合
162
- */
163
- parseModifiers(rawName: string): ParsedModifiers {
164
- const modifiers: ParsedModifiers = {
165
- stop: false,
166
- prevent: false,
167
- capture: false,
168
- once: false,
169
- self: false,
170
- passive: false,
171
- };
172
-
173
- // FIX: P2-23 正则表达式缓存 - 模块级预编译正则
174
- const RE_EVENT_MODIFIERS = /\.(stop|prevent|capture|once|self|passive)/g;
175
- // FIX: P2-41 事件捕获选项支持:支持 .capture 修饰符
176
- const modifierMatch = rawName.match(RE_EVENT_MODIFIERS);
177
- if (modifierMatch) {
178
- for (const mod of modifierMatch) {
179
- switch (mod) {
180
- case '.stop':
181
- modifiers.stop = true;
182
- break;
183
- case '.prevent':
184
- modifiers.prevent = true;
185
- break;
186
- case '.capture':
187
- modifiers.capture = true;
188
- break;
189
- case '.once':
190
- modifiers.once = true;
191
- break;
192
- case '.self':
193
- modifiers.self = true;
194
- break;
195
- case '.passive':
196
- modifiers.passive = true;
197
- break;
198
- }
199
- }
200
- }
201
-
202
- return modifiers;
203
- }
204
-
205
- // ==========================================================
206
- // 事件名 → 缓存 key 转换
207
- // ==========================================================
208
-
209
- /**
210
- * 将事件名转换为 invoker 缓存的 key。
211
- *
212
- * click → onClick,mouseenter → onMouseenter
213
- *
214
- * @param rawName - 原始事件名
215
- * @returns 缓存 key
216
- */
217
- getEventKey(rawName: string): string {
218
- const name = this.normalizeEventName(rawName);
219
- // FIX: P2-v11-20 添加空字符串检查,避免 name 为空时 name[0] 返回 undefined
220
- if (!name) return 'on';
221
- return 'on' + name[0]!.toUpperCase() + name.slice(1);
222
- }
223
-
224
- // ==========================================================
225
- // Invoker 缓存模式
226
- // ==========================================================
227
-
228
- /**
229
- * 更新元素上的事件监听(invoker 缓存模式)。
230
- *
231
- * 四种情况:
232
- * 1. nextValue && existingInvoker → 直接替换 invoker.value(O(1) 赋值)
233
- * 2. nextValue && !existingInvoker → 创建 invoker,addEventListener
234
- * 3. !nextValue && existingInvoker → removeEventListener,清除缓存
235
- * 4. !nextValue && !existingInvoker → 无操作
236
- *
237
- * @param el - 宿主元素
238
- * @param rawName - 原始事件名
239
- * @param nextValue - 新的事件处理函数
240
- */
241
- patchEvent(
242
- el: HE,
243
- rawName: string,
244
- nextValue: HostEventHandler | null,
245
- ): void {
246
- const eventKey = this.getEventKey(rawName);
247
- const parsed = this.parseEventName(rawName);
248
-
249
- // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储
250
- let invokers = this.invokerCache.get(el);
251
- if (!invokers) {
252
- invokers = {};
253
- this.invokerCache.set(el, invokers);
254
- }
255
-
256
- const existingInvoker = invokers[eventKey];
257
-
258
- if (nextValue && existingInvoker) {
259
- // 情况 1:有新值 + 有旧 invoker → 直接替换 value
260
- existingInvoker.value = nextValue;
261
- } else if (nextValue && !existingInvoker) {
262
- // 情况 2:有新值 + 无旧 invoker → 创建并绑定
263
- const invoker = this.createInvoker(el, parsed, nextValue);
264
- invokers[eventKey] = invoker;
265
-
266
- const options = this.buildHostEventOptions(parsed.modifiers);
267
- // FIX: P2 保存 addEventListener 返回的 dispose 函数到 invoker,
268
- // 确保后续可以通过 invoker.dispose() 正确移除事件监听
269
- const hostDispose = this.host.addEventListener(el, parsed.name, invoker.handler, options);
270
- invoker.dispose = () => {
271
- hostDispose();
272
- // 同时清理内部状态
273
- (invoker as { value?: HostEventHandler | null }).value = null;
274
- };
275
- } else if (!nextValue && existingInvoker) {
276
- // 情况 3:无新值 + 有旧 invoker → 移除
277
- const options = this.buildHostEventOptions(parsed.modifiers);
278
- this.host.removeEventListener(el, parsed.name, existingInvoker.handler, options);
279
- invokers[eventKey] = undefined;
280
- }
281
- // 情况 4:无新值 + 无旧 invoker → 无操作
282
- }
283
-
284
- /**
285
- * 移除元素上所有通过 invoker 缓存的事件监听。
286
- *
287
- * @param el - 宿主元素
288
- */
289
- removeAllEventListeners(el: HE): void {
290
- // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储
291
- const invokers = this.invokerCache.get(el);
292
- if (!invokers) return;
293
-
294
- // FIX: P2-47 使用 Object.keys 替代 for...in,避免遍历原型链上的属性
295
- for (const eventKey of Object.keys(invokers)) {
296
- const invoker = invokers[eventKey];
297
- if (invoker) {
298
- // FIX: P1-48 使用 invoker.handler 而非原始 handler 引用移除事件监听器,
299
- // 确保能正确匹配 addEventListener 时注册的包装 handler
300
- const options = this.buildHostEventOptions(invoker.parsed.modifiers);
301
- this.host.removeEventListener(el, invoker.parsed.name, invoker.handler, options);
302
- }
303
- }
304
-
305
- // 从 WeakMap 中删除该元素的缓存
306
- this.invokerCache.delete(el);
307
- }
308
-
309
- // ==========================================================
310
- // 内部方法
311
- // ==========================================================
312
-
313
- /**
314
- * 创建事件 invoker。
315
- *
316
- * invoker 持有 value 属性,调用时执行 invoker.value(event)。
317
- * 修饰符在 invoker 内部处理,更新时仅需替换 value。
318
- */
319
- private createInvoker(
320
- el: HE,
321
- parsed: ParsedEventInfo,
322
- initialValue: HostEventHandler,
323
- ): EventInvoker<HE> {
324
- let currentValue: HostEventHandler | null = initialValue;
325
- let disposed = false;
326
-
327
- const handler: HostEventHandler = (event: HostEvent) => {
328
- if (disposed) return;
329
-
330
- // 处理修饰符
331
- if (parsed.modifiers.stop) {
332
- event.stopPropagation();
333
- }
334
- if (parsed.modifiers.prevent) {
335
- event.preventDefault();
336
- }
337
- if (parsed.modifiers.self && event.target !== event.currentTarget) {
338
- return;
339
- }
340
-
341
- if (currentValue) {
342
- currentValue(event);
343
- }
344
- };
345
-
346
- const invoker: EventInvoker<HE> = {
347
- get value() {
348
- return currentValue;
349
- },
350
- set value(fn: HostEventHandler | null) {
351
- currentValue = fn;
352
- },
353
- parsed,
354
- el,
355
- handler,
356
- dispose: () => {
357
- disposed = true;
358
- currentValue = null;
359
- },
360
- };
361
-
362
- return invoker;
363
- }
364
-
365
- /**
366
- * 根据 ParsedModifiers 构建 HostEventOptions。
367
- */
368
- private buildHostEventOptions(modifiers: ParsedModifiers): HostEventOptions | undefined {
369
- if (modifiers.capture || modifiers.once || modifiers.passive) {
370
- const options: HostEventOptions = {};
371
- if (modifiers.capture) options.capture = true;
372
- if (modifiers.once) options.once = true;
373
- if (modifiers.passive) options.passive = true;
374
- return options;
375
- }
376
- return undefined;
377
- }
378
- }
1
+ // @lytjs/common-event-normalizer
2
+ // 事件归一化:解析事件名、提供 invoker 缓存模式、通过 RendererHost 操作事件
3
+
4
+ import type {
5
+ RendererHost,
6
+ HostEvent,
7
+ HostEventHandler,
8
+ HostEventOptions,
9
+ } from '@lytjs/host-contract';
10
+ import { EVENT_MODIFIER_RE } from '@lytjs/common-events';
11
+
12
+ // ============================================================
13
+ // 类型定义
14
+ // ============================================================
15
+
16
+ /**
17
+ * 事件修饰符解析结果。
18
+ */
19
+ export interface ParsedModifiers {
20
+ /** 是否调用 stopPropagation */
21
+ stop: boolean;
22
+ /** 是否调用 preventDefault */
23
+ prevent: boolean;
24
+ /** 是否使用 capture 模式 */
25
+ capture: boolean;
26
+ /** 是否使用 once 模式 */
27
+ once: boolean;
28
+ /** 是否仅在 target === currentTarget 时触发 */
29
+ self: boolean;
30
+ /** 是否使用 passive 模式 */
31
+ passive: boolean;
32
+ }
33
+
34
+ /**
35
+ * 解析后的事件信息。
36
+ */
37
+ export interface ParsedEventInfo {
38
+ /** 规范化后的事件名(如 'click') */
39
+ name: string;
40
+ /** 事件修饰符 */
41
+ modifiers: ParsedModifiers;
42
+ }
43
+
44
+ /**
45
+ * 事件 invoker 接口。
46
+ *
47
+ * 持有 value 属性用于更新,避免重复 addEventListener。
48
+ */
49
+ export interface EventInvoker<HE> {
50
+ /** 当前绑定的事件处理函数 */
51
+ value: ((event: HostEvent) => void) | null;
52
+ /** 解析后的事件信息 */
53
+ parsed: ParsedEventInfo;
54
+ /** 宿主元素引用 */
55
+ el: HE;
56
+ /** 实际的事件处理函数(传给 addEventListener 的函数) */
57
+ handler: (event: HostEvent) => void;
58
+ /** 取消监听函数 */
59
+ dispose: () => void;
60
+ }
61
+
62
+ /**
63
+ * 事件监听器注册条目。
64
+ */
65
+ export interface EventListenerEntry<HE> {
66
+ /** 宿主元素 */
67
+ el: HE;
68
+ /** 事件名 */
69
+ event: string;
70
+ /** 处理函数 */
71
+ handler: (event: HostEvent) => void;
72
+ /** 事件选项 */
73
+ options?: { capture?: boolean; once?: boolean; passive?: boolean };
74
+ }
75
+
76
+ // ============================================================
77
+ // 常量
78
+ // ============================================================
79
+
80
+ /** invoker 缓存类型 */
81
+ type InvokerCache<HE> = Record<string, EventInvoker<HE> | undefined>;
82
+
83
+ // ============================================================
84
+ // EventNormalizer
85
+ // ============================================================
86
+
87
+ /**
88
+ * 事件归一化器。
89
+ *
90
+ * 负责解析事件名中的修饰符、管理事件 invoker 缓存,
91
+ * 通过 RendererHost 接口执行平台无关的事件操作。
92
+ *
93
+ * @template HN - 宿主节点类型
94
+ * @template HE - 宿主元素类型
95
+ */
96
+ // FIX: DTS build error - HN 必须约束为 object
97
+ export class EventNormalizer<HN extends object = object, HE extends HN = HN> {
98
+ /** RendererHost 实例 */
99
+ private host: RendererHost<HN, HE>;
100
+
101
+ /**
102
+ * FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储,避免类型断言和属性污染
103
+ * 键:宿主元素,值:该元素的事件 invoker 缓存
104
+ */
105
+ private invokerCache = new WeakMap<HE, InvokerCache<HE>>();
106
+
107
+ /**
108
+ * 创建事件归一化器实例。
109
+ * @param host - RendererHost 实例
110
+ */
111
+ constructor(host: RendererHost<HN, HE>) {
112
+ this.host = host;
113
+ }
114
+
115
+ // ==========================================================
116
+ // 事件名解析
117
+ // ==========================================================
118
+
119
+ /**
120
+ * 解析事件名。
121
+ *
122
+ * 将各种格式的事件名规范化并提取修饰符:
123
+ * - onClick.stop.prevent → { name: 'click', modifiers: { stop: true, prevent: true } }
124
+ * - @click.capture → { name: 'click', modifiers: { capture: true } }
125
+ * - click → { name: 'click', modifiers: { stop: false, ... } }
126
+ *
127
+ * @param rawName - 原始事件名
128
+ * @returns 解析后的事件信息
129
+ */
130
+ parseEventName(rawName: string): ParsedEventInfo {
131
+ const name = this.normalizeEventName(rawName);
132
+ const modifiers = this.parseModifiers(rawName);
133
+
134
+ return { name, modifiers };
135
+ }
136
+
137
+ /**
138
+ * 将原始事件名规范化为标准事件名。
139
+ *
140
+ * 支持格式:@click / onClick / click → click
141
+ *
142
+ * @param rawName - 原始事件名
143
+ * @returns 规范化后的事件名
144
+ */
145
+ normalizeEventName(rawName: string): string {
146
+ // 移除 @ 前缀
147
+ let name = rawName.startsWith('@') ? rawName.slice(1) : rawName;
148
+
149
+ // 移除 on 前缀(仅当以大写字母开头的 on 前缀时)
150
+ // FIX: P2-45 使用 charAt 替代非空断言,更安全地访问字符串字符
151
+ if (name.startsWith('on') && name.length > 2 && /^[A-Za-z]$/.test(name.charAt(2))) {
152
+ name = name.slice(2);
153
+ }
154
+
155
+ // 移除修饰符后缀(如 .stop.prevent)
156
+ name = name.replace(EVENT_MODIFIER_RE, '');
157
+
158
+ // 转为小写
159
+ return name.toLowerCase();
160
+ }
161
+
162
+ /**
163
+ * 解析事件名中的修饰符。
164
+ *
165
+ * @param rawName - 原始事件名
166
+ * @returns 修饰符集合
167
+ */
168
+ parseModifiers(rawName: string): ParsedModifiers {
169
+ const modifiers: ParsedModifiers = {
170
+ stop: false,
171
+ prevent: false,
172
+ capture: false,
173
+ once: false,
174
+ self: false,
175
+ passive: false,
176
+ };
177
+
178
+ // FIX: P2-23 正则表达式缓存 - 模块级预编译正则
179
+ const RE_EVENT_MODIFIERS = /\.(stop|prevent|capture|once|self|passive)/g;
180
+ // FIX: P2-41 事件捕获选项支持:支持 .capture 修饰符
181
+ const modifierMatch = rawName.match(RE_EVENT_MODIFIERS);
182
+ if (modifierMatch) {
183
+ for (const mod of modifierMatch) {
184
+ switch (mod) {
185
+ case '.stop':
186
+ modifiers.stop = true;
187
+ break;
188
+ case '.prevent':
189
+ modifiers.prevent = true;
190
+ break;
191
+ case '.capture':
192
+ modifiers.capture = true;
193
+ break;
194
+ case '.once':
195
+ modifiers.once = true;
196
+ break;
197
+ case '.self':
198
+ modifiers.self = true;
199
+ break;
200
+ case '.passive':
201
+ modifiers.passive = true;
202
+ break;
203
+ }
204
+ }
205
+ }
206
+
207
+ return modifiers;
208
+ }
209
+
210
+ // ==========================================================
211
+ // 事件名 → 缓存 key 转换
212
+ // ==========================================================
213
+
214
+ /**
215
+ * 将事件名转换为 invoker 缓存的 key
216
+ *
217
+ * click onClick,mouseenter → onMouseenter
218
+ *
219
+ * @param rawName - 原始事件名
220
+ * @returns 缓存 key
221
+ */
222
+ getEventKey(rawName: string): string {
223
+ const name = this.normalizeEventName(rawName);
224
+ // FIX: P2-v11-20 添加空字符串检查,避免 name 为空时 name[0] 返回 undefined
225
+ if (!name) return 'on';
226
+ return 'on' + name[0]!.toUpperCase() + name.slice(1);
227
+ }
228
+
229
+ // ==========================================================
230
+ // Invoker 缓存模式
231
+ // ==========================================================
232
+
233
+ /**
234
+ * 更新元素上的事件监听(invoker 缓存模式)。
235
+ *
236
+ * 四种情况:
237
+ * 1. nextValue && existingInvoker → 直接替换 invoker.value(O(1) 赋值)
238
+ * 2. nextValue && !existingInvoker → 创建 invoker,addEventListener
239
+ * 3. !nextValue && existingInvoker → removeEventListener,清除缓存
240
+ * 4. !nextValue && !existingInvoker → 无操作
241
+ *
242
+ * @param el - 宿主元素
243
+ * @param rawName - 原始事件名
244
+ * @param nextValue - 新的事件处理函数
245
+ */
246
+ patchEvent(el: HE, rawName: string, nextValue: HostEventHandler | null): void {
247
+ const eventKey = this.getEventKey(rawName);
248
+ const parsed = this.parseEventName(rawName);
249
+
250
+ // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储
251
+ let invokers = this.invokerCache.get(el);
252
+ if (!invokers) {
253
+ invokers = {};
254
+ this.invokerCache.set(el, invokers);
255
+ }
256
+
257
+ const existingInvoker = invokers[eventKey];
258
+
259
+ if (nextValue && existingInvoker) {
260
+ // 情况 1:有新值 + 有旧 invoker → 直接替换 value
261
+ existingInvoker.value = nextValue;
262
+ } else if (nextValue && !existingInvoker) {
263
+ // 情况 2:有新值 + 无旧 invoker → 创建并绑定
264
+ const invoker = this.createInvoker(el, parsed, nextValue);
265
+ invokers[eventKey] = invoker;
266
+
267
+ const options = this.buildHostEventOptions(parsed.modifiers);
268
+ // FIX: P2 保存 addEventListener 返回的 dispose 函数到 invoker,
269
+ // 确保后续可以通过 invoker.dispose() 正确移除事件监听
270
+ const hostDispose = this.host.addEventListener(el, parsed.name, invoker.handler, options);
271
+ invoker.dispose = () => {
272
+ hostDispose();
273
+ // 同时清理内部状态
274
+ (invoker as { value?: HostEventHandler | null }).value = null;
275
+ };
276
+ } else if (!nextValue && existingInvoker) {
277
+ // 情况 3:无新值 + 有旧 invoker → 移除
278
+ const options = this.buildHostEventOptions(parsed.modifiers);
279
+ this.host.removeEventListener(el, parsed.name, existingInvoker.handler, options);
280
+ invokers[eventKey] = undefined;
281
+ }
282
+ // 情况 4:无新值 + 无旧 invoker → 无操作
283
+ }
284
+
285
+ /**
286
+ * 移除元素上所有通过 invoker 缓存的事件监听。
287
+ *
288
+ * @param el - 宿主元素
289
+ */
290
+ removeAllEventListeners(el: HE): void {
291
+ // FIX: P2-46 使用 WeakMap 替代 el._vei 属性存储
292
+ const invokers = this.invokerCache.get(el);
293
+ if (!invokers) return;
294
+
295
+ // FIX: P2-47 使用 Object.keys 替代 for...in,避免遍历原型链上的属性
296
+ for (const eventKey of Object.keys(invokers)) {
297
+ const invoker = invokers[eventKey];
298
+ if (invoker) {
299
+ // FIX: P1-48 使用 invoker.handler 而非原始 handler 引用移除事件监听器,
300
+ // 确保能正确匹配 addEventListener 时注册的包装 handler
301
+ const options = this.buildHostEventOptions(invoker.parsed.modifiers);
302
+ this.host.removeEventListener(el, invoker.parsed.name, invoker.handler, options);
303
+ }
304
+ }
305
+
306
+ // 从 WeakMap 中删除该元素的缓存
307
+ this.invokerCache.delete(el);
308
+ }
309
+
310
+ // ==========================================================
311
+ // 内部方法
312
+ // ==========================================================
313
+
314
+ /**
315
+ * 创建事件 invoker。
316
+ *
317
+ * invoker 持有 value 属性,调用时执行 invoker.value(event)
318
+ * 修饰符在 invoker 内部处理,更新时仅需替换 value。
319
+ */
320
+ private createInvoker(
321
+ el: HE,
322
+ parsed: ParsedEventInfo,
323
+ initialValue: HostEventHandler,
324
+ ): EventInvoker<HE> {
325
+ let currentValue: HostEventHandler | null = initialValue;
326
+ let disposed = false;
327
+
328
+ const handler: HostEventHandler = (event: HostEvent) => {
329
+ if (disposed) return;
330
+
331
+ // 处理修饰符
332
+ if (parsed.modifiers.stop) {
333
+ event.stopPropagation();
334
+ }
335
+ if (parsed.modifiers.prevent) {
336
+ event.preventDefault();
337
+ }
338
+ if (parsed.modifiers.self && event.target !== event.currentTarget) {
339
+ return;
340
+ }
341
+
342
+ if (currentValue) {
343
+ currentValue(event);
344
+ }
345
+ };
346
+
347
+ const invoker: EventInvoker<HE> = {
348
+ get value() {
349
+ return currentValue;
350
+ },
351
+ set value(fn: HostEventHandler | null) {
352
+ currentValue = fn;
353
+ },
354
+ parsed,
355
+ el,
356
+ handler,
357
+ dispose: () => {
358
+ disposed = true;
359
+ currentValue = null;
360
+ },
361
+ };
362
+
363
+ return invoker;
364
+ }
365
+
366
+ /**
367
+ * 根据 ParsedModifiers 构建 HostEventOptions。
368
+ */
369
+ private buildHostEventOptions(modifiers: ParsedModifiers): HostEventOptions | undefined {
370
+ if (modifiers.capture || modifiers.once || modifiers.passive) {
371
+ const options: HostEventOptions = {};
372
+ if (modifiers.capture) options.capture = true;
373
+ if (modifiers.once) options.once = true;
374
+ if (modifiers.passive) options.passive = true;
375
+ return options;
376
+ }
377
+ return undefined;
378
+ }
379
+ }