diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index db52bc88c..abeb74a6d 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -282,9 +282,13 @@ export function normalizeEmitsOptions( return normalized } -// Check if an incoming prop key is a declared emit event listener. -// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are -// both considered matched listeners. +/** + * Check if an incoming prop key is a declared emit event listener. + * e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are + * both considered matched listeners. + * + * @internal for vapor only + */ export function isEmitListener( options: ObjectEmitsOptions | null, key: string, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index d9af62eee..9ff31e6d4 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -488,4 +488,5 @@ export const DeprecationTypes = ( // change without notice between versions. User code should never rely on them. export { baseNormalizePropsOptions, resolvePropValue } from './componentProps' +export { isEmitListener } from './componentEmits' export { type SchedulerJob, queueJob } from './scheduler' diff --git a/packages/runtime-vapor/src/_new/component.ts b/packages/runtime-vapor/src/_new/component.ts index 2e27d4fc7..e3281f2fd 100644 --- a/packages/runtime-vapor/src/_new/component.ts +++ b/packages/runtime-vapor/src/_new/component.ts @@ -3,6 +3,7 @@ import { EffectScope, type EmitsOptions, type NormalizedPropsOptions, + type ObjectEmitsOptions, } from '@vue/runtime-core' import type { Block } from '../block' import type { Data } from '@vue/runtime-shared' @@ -44,6 +45,7 @@ export interface ObjectComponent interface SharedInternalOptions { __propsOptions?: NormalizedPropsOptions __propsHandlers?: [ProxyHandler, ProxyHandler] + __emitsOptions?: ObjectEmitsOptions } // Note: can't mark this whole interface internal because some public interfaces diff --git a/packages/runtime-vapor/src/_new/componentEmits.ts b/packages/runtime-vapor/src/_new/componentEmits.ts new file mode 100644 index 000000000..c07c44ce0 --- /dev/null +++ b/packages/runtime-vapor/src/_new/componentEmits.ts @@ -0,0 +1,26 @@ +import type { ObjectEmitsOptions } from '@vue/runtime-core' +import type { Component } from './component' +import { isArray } from '@vue/shared' + +/** + * The logic from core isn't too reusable so it's better to duplicate here + */ +export function normalizeEmitsOptions( + comp: Component, +): ObjectEmitsOptions | null { + const cached = comp.__emitsOptions + if (cached) return cached + + const raw = comp.emits + if (!raw) return null + + let normalized: ObjectEmitsOptions + if (isArray(raw)) { + normalized = {} + for (const key in raw) normalized[key] = null + } else { + normalized = raw + } + + return (comp.__emitsOptions = normalized) +} diff --git a/packages/runtime-vapor/src/_new/componentProps.ts b/packages/runtime-vapor/src/_new/componentProps.ts index 03f6082e4..105639725 100644 --- a/packages/runtime-vapor/src/_new/componentProps.ts +++ b/packages/runtime-vapor/src/_new/componentProps.ts @@ -3,8 +3,10 @@ import type { Component, ComponentInstance } from './component' import { type NormalizedPropsOptions, baseNormalizePropsOptions, + isEmitListener, resolvePropValue, } from '@vue/runtime-core' +import { normalizeEmitsOptions } from './componentEmits' export interface RawProps { [key: string]: PropSource @@ -23,7 +25,7 @@ export function initStaticProps( let hasAttrs = false const { props, attrs } = instance const [propsOptions, needCastKeys] = normalizePropsOptions(comp) - // TODO emits filtering + const emitsOptions = normalizeEmitsOptions(comp) for (const key in rawProps) { const normalizedKey = camelize(key) const needCast = needCastKeys && needCastKeys.includes(normalizedKey) @@ -54,7 +56,7 @@ export function initStaticProps( ) : source } - } else { + } else if (!isEmitListener(emitsOptions, key)) { if (isFunction(source)) { Object.defineProperty(attrs, key, { enumerable: true, @@ -102,44 +104,48 @@ export function getDynamicPropsHandlers( } let normalizedKeys: string[] | undefined const propsOptions = normalizePropsOptions(comp)[0]! - const isProp = (key: string | symbol) => hasOwn(propsOptions, key) + const emitsOptions = normalizeEmitsOptions(comp) + const isProp = (key: string) => hasOwn(propsOptions, key) - const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => { - if (key !== '$' && (asProp ? isProp(key) : !isProp(key))) { - const castProp = (value: any, isAbsent = false) => - asProp - ? resolvePropValue( - propsOptions, - key as string, - value, - instance, - resolveDefault, - isAbsent, - ) - : value + const getProp = (target: RawProps, key: string, asProp: boolean) => { + if (key === '$') return + if (asProp) { + if (!isProp(key)) return + } else if (isProp(key) || isEmitListener(emitsOptions, key)) { + return + } + const castProp = (value: any, isAbsent = false) => + asProp + ? resolvePropValue( + propsOptions, + key as string, + value, + instance, + resolveDefault, + isAbsent, + ) + : value - if (key in target) { - // TODO default value, casting, etc. - return castProp(resolveSource(target[key as string])) - } - if (target.$) { - let i = target.$.length - let source - while (i--) { - source = resolveSource(target.$[i]) - if (hasOwn(source, key)) { - return castProp(source[key]) - } + if (key in target) { + return castProp(resolveSource(target[key as string])) + } + if (target.$) { + let i = target.$.length + let source + while (i--) { + source = resolveSource(target.$[i]) + if (hasOwn(source, key)) { + return castProp(source[key]) } } - return castProp(undefined, true) } + return castProp(undefined, true) } const propsHandlers = { - get: (target, key) => getProp(target, key, true), - has: (_, key) => isProp(key), - getOwnPropertyDescriptor(target, key) { + get: (target, key: string) => getProp(target, key, true), + has: (_, key: string) => isProp(key), + getOwnPropertyDescriptor(target, key: string) { if (isProp(key)) { return { configurable: true, @@ -154,8 +160,9 @@ export function getDynamicPropsHandlers( deleteProperty: NO, } satisfies ProxyHandler - const hasAttr = (target: RawProps, key: string | symbol) => { - if (key === '$' || isProp(key)) return false + const hasAttr = (target: RawProps, key: string) => { + if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key)) + return false if (hasOwn(target, key)) return true if (target.$) { let i = target.$.length @@ -169,9 +176,9 @@ export function getDynamicPropsHandlers( } const attrsHandlers = { - get: (target, key) => getProp(target, key, false), + get: (target, key: string) => getProp(target, key, false), has: hasAttr, - getOwnPropertyDescriptor(target, key) { + getOwnPropertyDescriptor(target, key: string) { if (hasAttr(target, key)) { return { configurable: true, @@ -181,16 +188,14 @@ export function getDynamicPropsHandlers( } }, ownKeys(target) { - const staticKeys = Object.keys(target).filter( - key => key !== '$' && !isProp(key), - ) + const staticKeys = Object.keys(target) if (target.$) { let i = target.$.length while (i--) { staticKeys.push(...Object.keys(resolveSource(target.$[i]))) } } - return staticKeys + return staticKeys.filter(key => hasAttr(target, key)) }, set: NO, deleteProperty: NO,