*/
propsDefaults: Data | null
+ // lifecycle
+ isMounted: boolean
+ isUnmounted: boolean
+ isDeactivated: boolean
+
+ // for vapor the following two are dev only
+ /**
+ * resolved props options
+ * @internal
+ */
+ propsOptions?: NormalizedPropsOptions
+ /**
+ * resolved emits options
+ * @internal
+ */
+ emitsOptions?: ObjectEmitsOptions | null
+
// the following are for error handling logic only
proxy?: any
/**
asyncResolved: boolean
// lifecycle
- isMounted: boolean
- isUnmounted: boolean
- isDeactivated: boolean
/**
* @internal
*/
type ComponentInternalInstance,
type ComponentOptions,
type ConcreteComponent,
+ type GenericComponentInstance,
formatComponentName,
} from './component'
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
...rawArgs: any[]
): ComponentPublicInstance | null | undefined {
if (instance.isUnmounted) return
- const props = instance.vnode.props || EMPTY_OBJ
+ return baseEmit(
+ instance,
+ instance.vnode.props || EMPTY_OBJ,
+ defaultPropGetter,
+ event,
+ ...rawArgs,
+ )
+}
+/**
+ * @internal for vapor only
+ */
+export function baseEmit(
+ instance: GenericComponentInstance,
+ props: Record<string, any>,
+ getter: (props: Record<string, any>, key: string) => unknown,
+ event: string,
+ ...rawArgs: any[]
+): ComponentPublicInstance | null | undefined {
if (__DEV__) {
- const {
- emitsOptions,
- propsOptions: [propsOptions],
- } = instance
+ const { emitsOptions, propsOptions } = instance
if (emitsOptions) {
if (
!(event in emitsOptions) &&
event.startsWith(compatModelEventPrefix))
)
) {
- if (!propsOptions || !(toHandlerKey(camelize(event)) in propsOptions)) {
+ if (
+ !propsOptions ||
+ !propsOptions[0] ||
+ !(toHandlerKey(camelize(event)) in propsOptions[0])
+ ) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
`the emits option nor as an "${toHandlerKey(camelize(event))}" prop.`,
if (__DEV__) {
const lowerCaseEvent = event.toLowerCase()
- if (lowerCaseEvent !== event && props[toHandlerKey(lowerCaseEvent)]) {
+ if (
+ lowerCaseEvent !== event &&
+ getter(props, toHandlerKey(lowerCaseEvent))
+ ) {
warn(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(
let handlerName
let handler =
- props[(handlerName = toHandlerKey(event))] ||
+ getter(props, (handlerName = toHandlerKey(event))) ||
// also try camelCase event handler (#2249)
- props[(handlerName = toHandlerKey(camelize(event)))]
+ getter(props, (handlerName = toHandlerKey(camelize(event))))
// for v-model update:xxx events, also trigger kebab-case equivalent
// for props passed via kebab-case
if (!handler && isModelListener) {
- handler = props[(handlerName = toHandlerKey(hyphenate(event)))]
+ handler = getter(props, (handlerName = toHandlerKey(hyphenate(event))))
}
if (handler) {
callWithAsyncErrorHandling(
- handler,
+ handler as Function,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
args,
)
}
- if (__COMPAT__) {
- compatModelEmit(instance, event, args)
- return compatInstanceEmit(instance, event, args)
+ if (__COMPAT__ && args) {
+ compatModelEmit(instance as ComponentInternalInstance, event, args)
+ return compatInstanceEmit(
+ instance as ComponentInternalInstance,
+ event,
+ args,
+ )
}
}
+function defaultPropGetter(props: Record<string, any>, key: string): unknown {
+ return props[key]
+}
+
export function normalizeEmitsOptions(
comp: ConcreteComponent,
appContext: AppContext,
/* eslint-disable no-restricted-globals */
import type { App } from './apiCreateApp'
import { Comment, Fragment, Static, Text } from './vnode'
-import type { ComponentInternalInstance } from './component'
+import type { GenericComponentInstance } from './component'
interface AppRecord {
id: number
)
export const devtoolsComponentRemoved = (
- component: ComponentInternalInstance,
+ component: GenericComponentInstance,
): void => {
if (
devtools &&
}
}
-type DevtoolsComponentHook = (component: ComponentInternalInstance) => void
+type DevtoolsComponentHook = (component: GenericComponentInstance) => void
/*! #__NO_SIDE_EFFECTS__ */
function createDevtoolsComponentHook(
hook: DevtoolsHooks,
): DevtoolsComponentHook {
- return (component: ComponentInternalInstance) => {
+ return (component: GenericComponentInstance) => {
emit(
hook,
component.appContext.app,
/*@__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_END)
type DevtoolsPerformanceHook = (
- component: ComponentInternalInstance,
+ component: GenericComponentInstance,
type: string,
time: number,
) => void
function createDevtoolsPerformanceHook(
hook: DevtoolsHooks,
): DevtoolsPerformanceHook {
- return (component: ComponentInternalInstance, type: string, time: number) => {
+ return (component: GenericComponentInstance, type: string, time: number) => {
emit(hook, component.appContext.app, component.uid, component, type, time)
}
}
export function devtoolsComponentEmit(
- component: ComponentInternalInstance,
+ component: GenericComponentInstance,
event: string,
params: any[],
): void {
baseNormalizePropsOptions,
resolvePropValue,
} from './componentProps'
-export { isEmitListener } from './componentEmits'
+export { baseEmit, isEmitListener } from './componentEmits'
export { type SchedulerJob, queueJob } from './scheduler'
export {
type ComponentInternalOptions,
/**
* Cached normalized props proxy handlers.
*/
- __propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
+ __propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
/**
* Cached normalized emits options.
*/
block: Block
scope: EffectScope
+ rawProps: RawProps | undefined
props: Record<string, any>
attrs: Record<string, any>
exposed?: Record<string, any>
hasFallthrough: boolean
+ isMounted: boolean
+ isUnmounted: boolean
+ isDeactivated: boolean
// LifecycleHooks.ERROR_CAPTURED
ec: LifecycleHook
+ // dev only
+ propsOptions?: NormalizedPropsOptions
+ emitsOptions?: ObjectEmitsOptions | null
+
constructor(comp: VaporComponent, rawProps?: RawProps) {
this.uid = nextUid()
this.type = comp
this.parent = currentInstance
- this.appContext = currentInstance ? currentInstance.appContext : null! // TODO
+ // @ts-expect-error TODO use proper appContext
+ this.appContext = currentInstance ? currentInstance.appContext : {}
this.block = null! // to be set
this.scope = new EffectScope(true)
+ this.rawProps = rawProps
this.provides = this.refs = EMPTY_OBJ
- this.emitted = null
- this.ec = null
+ this.emitted = this.ec = null
+ this.isMounted = this.isUnmounted = this.isDeactivated = false
// init props
this.propsDefaults = null
this.hasFallthrough = false
- if (comp.props && rawProps && rawProps.$) {
+ if (rawProps && rawProps.$) {
// has dynamic props, use proxy
const handlers = getDynamicPropsHandlers(comp, this)
- this.props = new Proxy(rawProps, handlers[0])
+ this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ
this.attrs = new Proxy(rawProps, handlers[1])
this.hasFallthrough = true
} else {
-import type { EmitFn, ObjectEmitsOptions } from '@vue/runtime-core'
+import {
+ type EmitFn,
+ type ObjectEmitsOptions,
+ baseEmit,
+} from '@vue/runtime-core'
import {
type VaporComponent,
type VaporComponentInstance,
currentInstance,
} from './component'
-import { NOOP, isArray } from '@vue/shared'
+import { NOOP, hasOwn, isArray } from '@vue/shared'
+import { resolveSource } from './componentProps'
/**
* The logic from core isn't too reusable so it's better to duplicate here
event: string,
...rawArgs: any[]
): void {
- // TODO extract reusable logic from core
+ const rawProps = instance.rawProps
+ if (!rawProps || instance.isUnmounted) return
+ baseEmit(instance, rawProps, propGetter, event, ...rawArgs)
+}
+
+function propGetter(rawProps: Record<string, any>, key: string) {
+ const dynamicSources = rawProps.$
+ if (dynamicSources) {
+ let i = dynamicSources.length
+ while (i--) {
+ const source = resolveSource(dynamicSources[i])
+ if (hasOwn(source, key)) return source[key]
+ }
+ }
+ return rawProps[key] && rawProps[key]()
}
const { props, attrs } = instance
const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
const emitsOptions = normalizeEmitsOptions(comp)
+
+ // for dev emit check
+ if (__DEV__) {
+ instance.propsOptions = normalizePropsOptions(comp)
+ instance.emitsOptions = emitsOptions
+ }
+
for (const key in rawProps) {
const normalizedKey = camelize(key)
const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
}
// TODO optimization: maybe convert functions into computeds
-function resolveSource(source: PropSource): Record<string, any> {
+export function resolveSource(source: PropSource): Record<string, any> {
return isFunction(source) ? source() : source
}
+const passThrough = (val: any) => val
+
export function getDynamicPropsHandlers(
comp: VaporComponent,
instance: VaporComponentInstance,
-): [ProxyHandler<RawProps>, ProxyHandler<RawProps>] {
+): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
if (comp.__propsHandlers) {
return comp.__propsHandlers
}
let normalizedKeys: string[] | undefined
- const propsOptions = normalizePropsOptions(comp)[0]!
+ const propsOptions = normalizePropsOptions(comp)[0]
const emitsOptions = normalizeEmitsOptions(comp)
- const isProp = (key: string) => hasOwn(propsOptions, key)
+ const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO
const getProp = (target: RawProps, key: string, asProp: boolean) => {
if (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
+ const castProp = propsOptions
+ ? (value: any, isAbsent = false) =>
+ asProp
+ ? resolvePropValue(
+ propsOptions,
+ key as string,
+ value,
+ instance,
+ resolveDefault,
+ isAbsent,
+ )
+ : value
+ : passThrough
if (key in target) {
return castProp(resolveSource(target[key as string]))
return castProp(undefined, true)
}
- const propsHandlers = {
- get: (target, key: string) => getProp(target, key, true),
- has: (_, key: string) => isProp(key),
- getOwnPropertyDescriptor(target, key: string) {
- if (isProp(key)) {
- return {
- configurable: true,
- enumerable: true,
- get: () => getProp(target, key, true),
- }
- }
- },
- ownKeys: () =>
- normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
- set: NO,
- deleteProperty: NO,
- } satisfies ProxyHandler<RawProps>
+ const propsHandlers = propsOptions
+ ? ({
+ get: (target, key: string) => getProp(target, key, true),
+ has: (_, key: string) => isProp(key),
+ getOwnPropertyDescriptor(target, key: string) {
+ if (isProp(key)) {
+ return {
+ configurable: true,
+ enumerable: true,
+ get: () => getProp(target, key, true),
+ }
+ }
+ },
+ ownKeys: () =>
+ normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
+ set: NO,
+ deleteProperty: NO,
+ } satisfies ProxyHandler<RawProps>)
+ : null
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
while (i--) {
}
}
}
- return false
+ return hasOwn(target, key)
}
const attrsHandlers = {
}
},
ownKeys(target) {
- const staticKeys = Object.keys(target)
+ const keys = Object.keys(target)
if (target.$) {
let i = target.$.length
while (i--) {
- staticKeys.push(...Object.keys(resolveSource(target.$[i])))
+ keys.push(...Object.keys(resolveSource(target.$[i])))
}
}
- return staticKeys.filter(key => hasAttr(target, key))
+ return keys.filter(key => hasAttr(target, key))
},
set: NO,
deleteProperty: NO,
export { createComponent as createComponentSimple } from './component'
export { renderEffect as renderEffectSimple } from './renderEffect'
export { createVaporApp as createVaporAppSimple } from './apiCreateApp'
+export { useEmit } from './componentEmits'