| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- import {
- EMPTY_ARR,
- NO,
- YES,
- camelize,
- hasOwn,
- isArray,
- isFunction,
- isString,
- } from '@vue/shared'
- import type { VaporComponent, VaporComponentInstance } from './component'
- import {
- type NormalizedPropsOptions,
- baseNormalizePropsOptions,
- currentInstance,
- isEmitListener,
- popWarningContext,
- pushWarningContext,
- resolvePropValue,
- simpleSetCurrentInstance,
- validateProps,
- warn,
- } from '@vue/runtime-dom'
- import { normalizeEmitsOptions } from './componentEmits'
- import { renderEffect } from './renderEffect'
- export type RawProps = Record<string, () => unknown> & {
- // generated by compiler for :[key]="x" or v-bind="x"
- $?: DynamicPropsSource[]
- }
- export type DynamicPropsSource =
- | (() => Record<string, unknown>)
- | Record<string, () => unknown>
- // TODO optimization: maybe convert functions into computeds
- export function resolveSource(
- source: Record<string, any> | (() => Record<string, any>),
- ): Record<string, any> {
- return isFunction(source) ? source() : source
- }
- export function getPropsProxyHandlers(
- comp: VaporComponent,
- ): [
- ProxyHandler<VaporComponentInstance> | null,
- ProxyHandler<VaporComponentInstance>,
- ] {
- if (comp.__propsHandlers) {
- return comp.__propsHandlers
- }
- const propsOptions = normalizePropsOptions(comp)[0]
- const emitsOptions = normalizeEmitsOptions(comp)
- const isProp = (
- propsOptions
- ? (key: string | symbol) =>
- isString(key) && hasOwn(propsOptions, camelize(key))
- : NO
- ) as (key: string | symbol) => key is string
- const isAttr = propsOptions
- ? (key: string) =>
- key !== '$' && !isProp(key) && !isEmitListener(emitsOptions, key)
- : YES
- const getProp = (instance: VaporComponentInstance, key: string | symbol) => {
- if (!isProp(key)) return
- const rawProps = instance.rawProps
- const dynamicSources = rawProps.$
- if (dynamicSources) {
- let i = dynamicSources.length
- let source, isDynamic, rawKey
- while (i--) {
- source = dynamicSources[i]
- isDynamic = isFunction(source)
- source = isDynamic ? (source as Function)() : source
- for (rawKey in source) {
- if (camelize(rawKey) === key) {
- return resolvePropValue(
- propsOptions!,
- key,
- isDynamic ? source[rawKey] : source[rawKey](),
- instance,
- resolveDefault,
- )
- }
- }
- }
- }
- for (const rawKey in rawProps) {
- if (camelize(rawKey) === key) {
- return resolvePropValue(
- propsOptions!,
- key,
- rawProps[rawKey](),
- instance,
- resolveDefault,
- )
- }
- }
- return resolvePropValue(
- propsOptions!,
- key,
- undefined,
- instance,
- resolveDefault,
- true,
- )
- }
- const propsHandlers = propsOptions
- ? ({
- get: (target, key) => getProp(target, key),
- has: (_, key) => isProp(key),
- ownKeys: () => Object.keys(propsOptions),
- getOwnPropertyDescriptor(target, key) {
- if (isProp(key)) {
- return {
- configurable: true,
- enumerable: true,
- get: () => getProp(target, key),
- }
- }
- },
- } satisfies ProxyHandler<VaporComponentInstance>)
- : null
- if (__DEV__ && propsOptions) {
- Object.assign(propsHandlers!, {
- set: propsSetDevTrap,
- deleteProperty: propsDeleteDevTrap,
- })
- }
- const getAttr = (target: RawProps, key: string) => {
- if (!isProp(key) && !isEmitListener(emitsOptions, key)) {
- return getAttrFromRawProps(target, key)
- }
- }
- const hasAttr = (target: RawProps, key: string) => {
- if (isAttr(key)) {
- return hasAttrFromRawProps(target, key)
- } else {
- return false
- }
- }
- const attrsHandlers = {
- get: (target, key: string) => getAttr(target.rawProps, key),
- has: (target, key: string) => hasAttr(target.rawProps, key),
- ownKeys: target => getKeysFromRawProps(target.rawProps).filter(isAttr),
- getOwnPropertyDescriptor(target, key: string) {
- if (hasAttr(target.rawProps, key)) {
- return {
- configurable: true,
- enumerable: true,
- get: () => getAttr(target.rawProps, key),
- }
- }
- },
- } satisfies ProxyHandler<VaporComponentInstance>
- if (__DEV__) {
- Object.assign(attrsHandlers, {
- set: propsSetDevTrap,
- deleteProperty: propsDeleteDevTrap,
- })
- }
- return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
- }
- export function getAttrFromRawProps(rawProps: RawProps, key: string): unknown {
- if (key === '$') return
- // need special merging behavior for class & style
- const merged = key === 'class' || key === 'style' ? ([] as any[]) : undefined
- const dynamicSources = rawProps.$
- if (dynamicSources) {
- let i = dynamicSources.length
- let source, isDynamic
- while (i--) {
- source = dynamicSources[i]
- isDynamic = isFunction(source)
- source = isDynamic ? (source as Function)() : source
- if (hasOwn(source, key)) {
- const value = isDynamic ? source[key] : source[key]()
- if (merged) {
- merged.push(value)
- } else {
- return value
- }
- }
- }
- }
- if (hasOwn(rawProps, key)) {
- if (merged) {
- merged.push(rawProps[key]())
- } else {
- return rawProps[key]()
- }
- }
- return merged
- }
- export function hasAttrFromRawProps(rawProps: RawProps, key: string): boolean {
- if (key === '$') return false
- const dynamicSources = rawProps.$
- if (dynamicSources) {
- let i = dynamicSources.length
- while (i--) {
- if (hasOwn(resolveSource(dynamicSources[i]), key)) {
- return true
- }
- }
- }
- return hasOwn(rawProps, key)
- }
- export function getKeysFromRawProps(rawProps: RawProps): string[] {
- const keys: string[] = []
- for (const key in rawProps) {
- if (key !== '$') keys.push(key)
- }
- const dynamicSources = rawProps.$
- if (dynamicSources) {
- let i = dynamicSources.length
- let source
- while (i--) {
- source = resolveSource(dynamicSources[i])
- for (const key in source) {
- keys.push(key)
- }
- }
- }
- return Array.from(new Set(keys))
- }
- export function normalizePropsOptions(
- comp: VaporComponent,
- ): NormalizedPropsOptions {
- const cached = comp.__propsOptions
- if (cached) return cached
- const raw = comp.props
- if (!raw) return EMPTY_ARR as []
- const normalized: NormalizedPropsOptions[0] = {}
- const needCastKeys: NormalizedPropsOptions[1] = []
- baseNormalizePropsOptions(raw, normalized, needCastKeys)
- return (comp.__propsOptions = [normalized, needCastKeys])
- }
- function resolveDefault(
- factory: (props: Record<string, any>) => unknown,
- instance: VaporComponentInstance,
- ) {
- const prev = currentInstance
- simpleSetCurrentInstance(instance)
- const res = factory.call(null, instance.props)
- simpleSetCurrentInstance(prev, instance)
- return res
- }
- export function hasFallthroughAttrs(
- comp: VaporComponent,
- rawProps: RawProps | null | undefined,
- ): boolean {
- if (rawProps) {
- // determine fallthrough
- if (rawProps.$ || !comp.props) {
- return true
- } else {
- // check if rawProps contains any keys not declared
- const propsOptions = normalizePropsOptions(comp)[0]!
- for (const key in rawProps) {
- if (!hasOwn(propsOptions, camelize(key))) {
- return true
- }
- }
- }
- }
- return false
- }
- /**
- * dev only
- */
- export function setupPropsValidation(instance: VaporComponentInstance): void {
- const rawProps = instance.rawProps
- if (!rawProps) return
- renderEffect(() => {
- pushWarningContext(instance)
- validateProps(
- resolveDynamicProps(rawProps),
- instance.props,
- normalizePropsOptions(instance.type)[0]!,
- )
- popWarningContext()
- }, true /* noLifecycle */)
- }
- export function resolveDynamicProps(props: RawProps): Record<string, unknown> {
- const mergedRawProps: Record<string, any> = {}
- for (const key in props) {
- if (key !== '$') {
- mergedRawProps[key] = props[key]()
- }
- }
- if (props.$) {
- for (const source of props.$) {
- const isDynamic = isFunction(source)
- const resolved = isDynamic ? source() : source
- for (const key in resolved) {
- const value = isDynamic ? resolved[key] : (resolved[key] as Function)()
- if (key === 'class' || key === 'style') {
- const existing = mergedRawProps[key]
- if (isArray(existing)) {
- existing.push(value)
- } else {
- mergedRawProps[key] = [existing, value]
- }
- } else {
- mergedRawProps[key] = value
- }
- }
- }
- }
- return mergedRawProps
- }
- function propsSetDevTrap(_: any, key: string | symbol) {
- warn(
- `Attempt to mutate prop ${JSON.stringify(key)} failed. Props are readonly.`,
- )
- return true
- }
- function propsDeleteDevTrap(_: any, key: string | symbol) {
- warn(
- `Attempt to delete prop ${JSON.stringify(key)} failed. Props are readonly.`,
- )
- return true
- }
|