Merge remote-tracking branch 'upstream/minor'

This commit is contained in:
三咲智子 Kevin Deng 2024-04-29 17:49:04 +09:00
commit fb58e65d3d
No known key found for this signature in database
47 changed files with 1653 additions and 558 deletions

View File

@ -1,3 +1,26 @@
# [3.5.0-alpha.1](https://github.com/vuejs/core/compare/v3.4.25...v3.5.0-alpha.1) (2024-04-29)
### Bug Fixes
* **reactivity:** fix call sequence of ontrigger in effect ([#10501](https://github.com/vuejs/core/issues/10501)) ([28841fe](https://github.com/vuejs/core/commit/28841fee43a45c37905c2c1ed9ace23067539045))
### Features
* **compiler-sfc:** enable reactive props destructure by default ([d2dac0e](https://github.com/vuejs/core/commit/d2dac0e359c47d1ed0aa77eda488e76fd6466d2d))
* **reactivity:** `onEffectCleanup` API ([2cc5615](https://github.com/vuejs/core/commit/2cc5615590de77126e8df46136de0240dbde5004)), closes [#10173](https://github.com/vuejs/core/issues/10173)
* **reactivity:** add failSilently argument for onScopeDispose ([9a936aa](https://github.com/vuejs/core/commit/9a936aaec489c79433a32791ecf5ddb1739a62bd))
* **transition:** support directly nesting Teleport inside Transition ([#6548](https://github.com/vuejs/core/issues/6548)) ([0e6e3c7](https://github.com/vuejs/core/commit/0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0)), closes [#5836](https://github.com/vuejs/core/issues/5836)
* **types:** provide internal options for directly using user types in language tools ([#10801](https://github.com/vuejs/core/issues/10801)) ([75c8cf6](https://github.com/vuejs/core/commit/75c8cf63a1ef30ac84f91282d66ad3f57c6612e9))
### Performance Improvements
* **reactivity:** optimize array tracking ([#9511](https://github.com/vuejs/core/issues/9511)) ([70196a4](https://github.com/vuejs/core/commit/70196a40cc078f50fcc1110c38c06fbcc70b205e)), closes [#4318](https://github.com/vuejs/core/issues/4318)
## [3.4.25](https://github.com/vuejs/core/compare/v3.4.24...v3.4.25) (2024-04-24)

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",

View File

@ -0,0 +1,20 @@
import { type CompilerError, compile } from '../../src'
describe('validate html nesting', () => {
it('should warn with p > div', () => {
let err: CompilerError | undefined
compile(`<p><div></div></p>`, {
onWarn: e => (err = e),
})
expect(err).toBeDefined()
expect(err!.message).toMatch(`<div> cannot be child of <p>`)
})
it('should not warn with select > hr', () => {
let err: CompilerError | undefined
compile(`<select><hr></select>`, {
onWarn: e => (err = e),
})
expect(err).toBeUndefined()
})
})

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",

View File

@ -0,0 +1,195 @@
/**
* Copied from https://github.com/MananTank/validate-html-nesting
* with ISC license
*
* To avoid runtime dependency on validate-html-nesting
* This file should not change very often in the original repo
* but we may need to keep it up-to-date from time to time.
*/
/**
* returns true if given parent-child nesting is valid HTML
*/
export function isValidHTMLNesting(parent: string, child: string): boolean {
// if we know the list of children that are the only valid children for the given parent
if (parent in onlyValidChildren) {
return onlyValidChildren[parent].has(child)
}
// if we know the list of parents that are the only valid parents for the given child
if (child in onlyValidParents) {
return onlyValidParents[child].has(parent)
}
// if we know the list of children that are NOT valid for the given parent
if (parent in knownInvalidChildren) {
// check if the child is in the list of invalid children
// if so, return false
if (knownInvalidChildren[parent].has(child)) return false
}
// if we know the list of parents that are NOT valid for the given child
if (child in knownInvalidParents) {
// check if the parent is in the list of invalid parents
// if so, return false
if (knownInvalidParents[child].has(parent)) return false
}
return true
}
const headings = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
const emptySet = new Set([])
/**
* maps element to set of elements that can be it's children, no other */
const onlyValidChildren: Record<string, Set<string>> = {
head: new Set([
'base',
'basefront',
'bgsound',
'link',
'meta',
'title',
'noscript',
'noframes',
'style',
'script',
'template',
]),
optgroup: new Set(['option']),
select: new Set(['optgroup', 'option', 'hr']),
// table
table: new Set(['caption', 'colgroup', 'tbody', 'tfoot', 'thead']),
tr: new Set(['td', 'th']),
colgroup: new Set(['col']),
tbody: new Set(['tr']),
thead: new Set(['tr']),
tfoot: new Set(['tr']),
// these elements can not have any children elements
script: emptySet,
iframe: emptySet,
option: emptySet,
textarea: emptySet,
style: emptySet,
title: emptySet,
}
/** maps elements to set of elements which can be it's parent, no other */
const onlyValidParents: Record<string, Set<string>> = {
// sections
html: emptySet,
body: new Set(['html']),
head: new Set(['html']),
// table
td: new Set(['tr']),
colgroup: new Set(['table']),
caption: new Set(['table']),
tbody: new Set(['table']),
tfoot: new Set(['table']),
col: new Set(['colgroup']),
th: new Set(['tr']),
thead: new Set(['table']),
tr: new Set(['tbody', 'thead', 'tfoot']),
// data list
dd: new Set(['dl', 'div']),
dt: new Set(['dl', 'div']),
// other
figcaption: new Set(['figure']),
// li: new Set(["ul", "ol"]),
summary: new Set(['details']),
area: new Set(['map']),
} as const
/** maps element to set of elements that can not be it's children, others can */
const knownInvalidChildren: Record<string, Set<string>> = {
p: new Set([
'address',
'article',
'aside',
'blockquote',
'center',
'details',
'dialog',
'dir',
'div',
'dl',
'fieldset',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'header',
'hgroup',
'hr',
'li',
'main',
'nav',
'menu',
'ol',
'p',
'pre',
'section',
'table',
'ul',
]),
svg: new Set([
'b',
'blockquote',
'br',
'code',
'dd',
'div',
'dl',
'dt',
'em',
'embed',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hr',
'i',
'img',
'li',
'menu',
'meta',
'ol',
'p',
'pre',
'ruby',
's',
'small',
'span',
'strong',
'sub',
'sup',
'table',
'u',
'ul',
'var',
]),
} as const
/** maps element to set of elements that can not be it's parent, others can */
const knownInvalidParents: Record<string, Set<string>> = {
a: new Set(['a']),
button: new Set(['button']),
dd: new Set(['dd', 'dt']),
dt: new Set(['dd', 'dt']),
form: new Set(['form']),
li: new Set(['li']),
h1: headings,
h2: headings,
h3: headings,
h4: headings,
h5: headings,
h6: headings,
}

View File

@ -19,13 +19,14 @@ import { transformShow } from './transforms/vShow'
import { transformTransition } from './transforms/Transition'
import { stringifyStatic } from './transforms/stringifyStatic'
import { ignoreSideEffectTags } from './transforms/ignoreSideEffectTags'
import { validateHtmlNesting } from './transforms/validateHtmlNesting'
import { extend } from '@vue/shared'
export { parserOptions }
export const DOMNodeTransforms: NodeTransform[] = [
transformStyle,
...(__DEV__ ? [transformTransition] : []),
...(__DEV__ ? [transformTransition, validateHtmlNesting] : []),
]
export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {

View File

@ -0,0 +1,27 @@
import {
type CompilerError,
ElementTypes,
type NodeTransform,
NodeTypes,
} from '@vue/compiler-core'
import { isValidHTMLNesting } from '../htmlNesting'
export const validateHtmlNesting: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT &&
context.parent &&
context.parent.type === NodeTypes.ELEMENT &&
context.parent.tagType === ElementTypes.ELEMENT &&
!isValidHTMLNesting(context.parent.tag, node.tag)
) {
const error = new SyntaxError(
`<${node.tag}> cannot be child of <${context.parent.tag}>, ` +
'according to HTML specifications. ' +
'This can cause hydration errors or ' +
'potentially disrupt future functionality.',
) as CompilerError
error.loc = node.loc
context.onWarn(error)
}
}

View File

@ -597,11 +597,29 @@ const props = defineProps({ foo: String })
foo: Foo
}>()
</script>`,
{
propsDestructure: false,
},
)
expect(content).toMatch(`const { foo } = __props`)
assertCode(content)
})
test('prohibiting reactive destructure', () => {
expect(() =>
compile(
`<script setup lang="ts">
const { foo } = defineProps<{
foo: Foo
}>()
</script>`,
{
propsDestructure: 'error',
},
),
).toThrow()
})
describe('errors', () => {
test('w/ both type and non-type args', () => {
expect(() => {

View File

@ -6,7 +6,6 @@ describe('sfc reactive props destructure', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, {
inlineTemplate: true,
propsDestructure: true,
...options,
})
}

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js",

View File

@ -106,10 +106,11 @@ export interface SFCScriptCompileOptions {
*/
hoistStatic?: boolean
/**
* (**Experimental**) Enable reactive destructure for `defineProps`
* @default false
* Set to `false` to disable reactive destructure for `defineProps` (pre-3.5
* behavior), or set to `'error'` to throw hard error on props destructures.
* @default true
*/
propsDestructure?: boolean
propsDestructure?: boolean | 'error'
/**
* File system access methods to be used when resolving types
* imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten

View File

@ -22,23 +22,17 @@ import { genPropsAccessExp } from '@vue/shared'
import { isCallOf, resolveObjectKey } from './utils'
import type { ScriptCompileContext } from './context'
import { DEFINE_PROPS } from './defineProps'
import { warnOnce } from '../warn'
export function processPropsDestructure(
ctx: ScriptCompileContext,
declId: ObjectPattern,
) {
if (!ctx.options.propsDestructure) {
if (ctx.options.propsDestructure === 'error') {
ctx.error(`Props destructure is explicitly prohibited via config.`, declId)
} else if (ctx.options.propsDestructure === false) {
return
}
warnOnce(
`This project is using reactive props destructure, which is an experimental ` +
`feature. It may receive breaking changes or be removed in the future, so ` +
`use at your own risk.\n` +
`To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/502.`,
)
ctx.propsDestructureDecl = declId
const registerBinding = (
@ -104,7 +98,7 @@ export function transformDestructuredProps(
ctx: ScriptCompileContext,
vueImportAliases: Record<string, string>,
) {
if (!ctx.options.propsDestructure) {
if (ctx.options.propsDestructure === false) {
return
}

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-ssr",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",

View File

@ -1,4 +1,4 @@
import { defineComponent } from 'vue'
import { type DefineComponent, type Directive, defineComponent } from 'vue'
import { expectType } from './utils'
declare module 'vue' {
@ -6,6 +6,14 @@ declare module 'vue' {
test?(n: number): void
}
interface GlobalDirectives {
test: Directive
}
interface GlobalComponents {
RouterView: DefineComponent<{}>
}
interface ComponentCustomProperties {
state?: 'stopped' | 'running'
}
@ -46,6 +54,8 @@ export const Custom = defineComponent({
},
})
expectType<Directive>(Custom.directives!.test)
expectType<DefineComponent<{}>>(Custom.components!.RouterView)
expectType<JSX.Element>(<Custom baz={1} />)
expectType<JSX.Element>(<Custom custom={1} baz={1} />)
expectType<JSX.Element>(<Custom bar="bar" baz={1} />)

View File

@ -15,7 +15,7 @@ import {
withKeys,
withModifiers,
} from 'vue'
import { type IsUnion, describe, expectType } from './utils'
import { type IsAny, type IsUnion, describe, expectType } from './utils'
describe('with object props', () => {
interface ExpectedProps {
@ -1501,18 +1501,108 @@ describe('should work when props type is incompatible with setup returned type '
describe('withKeys and withModifiers as pro', () => {
const onKeydown = withKeys(e => {}, [''])
// @ts-expect-error invalid modifiers
const onClick = withModifiers(e => {}, [''])
;<input onKeydown={onKeydown} onClick={onClick} />
})
// #3367 expose components types
describe('expose component types', () => {
const child = defineComponent({
props: {
a: String,
},
})
const parent = defineComponent({
components: {
child,
child2: {
template: `<div></div>`,
},
},
})
expectType<typeof child>(parent.components!.child)
expectType<Component>(parent.components!.child2)
// global components
expectType<Readonly<KeepAliveProps>>(
new parent.components!.KeepAlive().$props,
)
expectType<Readonly<KeepAliveProps>>(new child.components!.KeepAlive().$props)
// runtime-dom components
expectType<Readonly<TransitionProps>>(
new parent.components!.Transition().$props,
)
expectType<Readonly<TransitionProps>>(
new child.components!.Transition().$props,
)
})
describe('directive typing', () => {
const customDirective: Directive = {
created(_) {},
}
const comp = defineComponent({
props: {
a: String,
},
directives: {
customDirective,
localDirective: {
created(_, { arg }) {
expectType<string | undefined>(arg)
},
},
},
})
expectType<typeof customDirective>(comp.directives!.customDirective)
expectType<Directive>(comp.directives!.localDirective)
// global directive
expectType<typeof vShow>(comp.directives!.vShow)
})
describe('expose typing', () => {
const Comp = defineComponent({
expose: ['a', 'b'],
props: {
some: String,
},
data() {
return { a: 1, b: '2', c: 1 }
},
})
expectType<Array<'a' | 'b'>>(Comp.expose!)
const vm = new Comp()
// internal should still be exposed
vm.$props
expectType<number>(vm.a)
expectType<string>(vm.b)
// @ts-expect-error shouldn't be exposed
vm.c
})
import type {
AllowedComponentProps,
ComponentCustomProps,
ComponentOptionsMixin,
DefineComponent,
Directive,
EmitsOptions,
ExtractPropTypes,
KeepAliveProps,
TransitionProps,
VNodeProps,
vShow,
} from 'vue'
// code generated by tsc / vue-tsc, make sure this continues to work
@ -1533,3 +1623,146 @@ declare const MyButton: DefineComponent<
{}
>
;<MyButton class="x" />
describe('__typeProps backdoor for union type for conditional props', () => {
interface CommonProps {
size?: 'xl' | 'l' | 'm' | 's' | 'xs'
}
type ConditionalProps =
| {
color?: 'normal' | 'primary' | 'secondary'
appearance?: 'normal' | 'outline' | 'text'
}
| {
color: 'white'
appearance: 'outline'
}
type Props = CommonProps & ConditionalProps
const Comp = defineComponent({
__typeProps: {} as Props,
})
// @ts-expect-error
;<Comp color="white" />
// @ts-expect-error
;<Comp color="white" appearance="normal" />
;<Comp color="white" appearance="outline" />
const c = new Comp()
// @ts-expect-error
c.$props = { color: 'white' }
// @ts-expect-error
c.$props = { color: 'white', appearance: 'text' }
c.$props = { color: 'white', appearance: 'outline' }
})
describe('__typeEmits backdoor, 3.3+ object syntax', () => {
type Emits = {
change: [id: number]
update: [value: string]
}
const Comp = defineComponent({
__typeEmits: {} as Emits,
mounted() {
this.$props.onChange?.(123)
// @ts-expect-error
this.$props.onChange?.('123')
this.$props.onUpdate?.('foo')
// @ts-expect-error
this.$props.onUpdate?.(123)
// @ts-expect-error
this.$emit('foo')
this.$emit('change', 123)
// @ts-expect-error
this.$emit('change', '123')
this.$emit('update', 'test')
// @ts-expect-error
this.$emit('update', 123)
},
})
;<Comp onChange={id => id.toFixed(2)} />
;<Comp onUpdate={id => id.toUpperCase()} />
// @ts-expect-error
;<Comp onChange={id => id.slice(1)} />
// @ts-expect-error
;<Comp onUpdate={id => id.toFixed(2)} />
const c = new Comp()
// @ts-expect-error
c.$emit('foo')
c.$emit('change', 123)
// @ts-expect-error
c.$emit('change', '123')
c.$emit('update', 'test')
// @ts-expect-error
c.$emit('update', 123)
})
describe('__typeEmits backdoor, call signature syntax', () => {
type Emits = {
(e: 'change', id: number): void
(e: 'update', value: string): void
}
const Comp = defineComponent({
__typeEmits: {} as Emits,
mounted() {
this.$props.onChange?.(123)
// @ts-expect-error
this.$props.onChange?.('123')
this.$props.onUpdate?.('foo')
// @ts-expect-error
this.$props.onUpdate?.(123)
// @ts-expect-error
this.$emit('foo')
this.$emit('change', 123)
// @ts-expect-error
this.$emit('change', '123')
this.$emit('update', 'test')
// @ts-expect-error
this.$emit('update', 123)
},
})
;<Comp onChange={id => id.toFixed(2)} />
;<Comp onUpdate={id => id.toUpperCase()} />
// @ts-expect-error
;<Comp onChange={id => id.slice(1)} />
// @ts-expect-error
;<Comp onUpdate={id => id.toFixed(2)} />
const c = new Comp()
// @ts-expect-error
c.$emit('foo')
c.$emit('change', 123)
// @ts-expect-error
c.$emit('change', '123')
c.$emit('update', 'test')
// @ts-expect-error
c.$emit('update', 123)
})
defineComponent({
props: {
foo: [String, null],
},
setup(props) {
expectType<IsAny<typeof props.foo>>(false)
expectType<string | null | undefined>(props.foo)
},
})

View File

@ -0,0 +1,58 @@
import { type Directive, type ObjectDirective, vModelText } from 'vue'
import { describe, expectType } from './utils'
type ExtractBinding<T> = T extends (
el: any,
binding: infer B,
vnode: any,
prev: any,
) => any
? B
: never
declare function testDirective<
Value,
Modifiers extends string = string,
Arg extends string = string,
>(): ExtractBinding<Directive<any, Value, Modifiers, Arg>>
describe('vmodel', () => {
expectType<ObjectDirective<any, any, 'trim' | 'number' | 'lazy', string>>(
vModelText,
)
// @ts-expect-error
expectType<ObjectDirective<any, any, 'not-valid', string>>(vModelText)
})
describe('custom', () => {
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
}>(testDirective<number, 'a' | 'b', 'Arg'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<number, 'a', 'Arg'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<number, 'a' | 'b', 'Argx'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<string, 'a' | 'b', 'Arg'>())
})

View File

@ -769,6 +769,32 @@ describe('reactivity/effect', () => {
])
})
it('debug: the call sequence of onTrack', () => {
const seq: number[] = []
const s = ref(0)
const track1 = () => seq.push(1)
const track2 = () => seq.push(2)
effect(
() => {
s.value
},
{
onTrack: track1,
},
)
effect(
() => {
s.value
},
{
onTrack: track2,
},
)
expect(seq.toString()).toBe('1,2')
})
it('events: onTrigger', () => {
let events: DebuggerEvent[] = []
let dummy
@ -807,6 +833,51 @@ describe('reactivity/effect', () => {
})
})
it('debug: the call sequence of onTrigger', () => {
const seq: number[] = []
const s = ref(0)
const trigger1 = () => seq.push(1)
const trigger2 = () => seq.push(2)
const trigger3 = () => seq.push(3)
const trigger4 = () => seq.push(4)
effect(
() => {
s.value
},
{
onTrigger: trigger1,
},
)
effect(
() => {
s.value
effect(
() => {
s.value
effect(
() => {
s.value
},
{
onTrigger: trigger4,
},
)
},
{
onTrigger: trigger3,
},
)
},
{
onTrigger: trigger2,
},
)
s.value++
expect(seq.toString()).toBe('1,2,3,4')
})
it('stop', () => {
let dummy
const obj = reactive({ prop: 1 })

View File

@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",

View File

@ -27,12 +27,23 @@ export class Dep {
* Link between this dep and the current active effect
*/
activeLink?: Link = undefined
/**
* Doubly linked list representing the subscribing effects (tail)
*/
subs?: Link = undefined
constructor(public computed?: ComputedRefImpl) {}
/**
* Doubly linked list representing the subscribing effects (head)
* DEV only, for invoking onTrigger hooks in correct order
*/
subsHead?: Link
constructor(public computed?: ComputedRefImpl) {
if (__DEV__) {
this.subsHead = undefined
}
}
track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
if (!activeSub || !shouldTrack) {
@ -113,21 +124,28 @@ export class Dep {
notify(debugInfo?: DebuggerEventExtraInfo) {
startBatch()
try {
for (let link = this.subs; link; link = link.prevSub) {
if (__DEV__) {
// subs are notified and batched in reverse-order and then invoked in
// original order at the end of the batch, but onTrigger hooks should
// be invoked in original order here.
for (let head = this.subsHead; head; head = head.nextSub) {
if (
__DEV__ &&
link.sub.onTrigger &&
!(link.sub.flags & EffectFlags.NOTIFIED)
head.sub.onTrigger &&
!(head.sub.flags & EffectFlags.NOTIFIED)
) {
link.sub.onTrigger(
head.sub.onTrigger(
extend(
{
effect: link.sub,
effect: head.sub,
},
debugInfo,
),
)
}
}
}
for (let link = this.subs; link; link = link.prevSub) {
link.sub.notify()
}
} finally {
@ -152,6 +170,11 @@ function addSub(link: Link) {
link.prevSub = currentTail
if (currentTail) currentTail.nextSub = link
}
if (__DEV__ && link.dep.subsHead === undefined) {
link.dep.subsHead = link
}
link.dep.subs = link
}

View File

@ -375,13 +375,14 @@ export function refreshComputed(computed: ComputedRefImpl) {
}
} catch (err) {
dep.version++
}
throw err
} finally {
activeSub = prevSub
shouldTrack = prevShouldTrack
cleanupDeps(computed)
computed.flags &= ~EffectFlags.RUNNING
}
}
function removeSub(link: Link) {
const { dep, prevSub, nextSub } = link

View File

@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",

View File

@ -3,16 +3,17 @@ import type {
ComponentOptions,
ComponentOptionsBase,
ComponentOptionsMixin,
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps,
ComponentOptionsWithoutProps,
ComponentProvideOptions,
ComputedOptions,
MethodOptions,
RenderFunction,
} from './componentOptions'
import type {
AllowedComponentProps,
Component,
ComponentCustomProps,
GlobalComponents,
GlobalDirectives,
SetupContext,
} from './component'
import type {
@ -21,7 +22,11 @@ import type {
ExtractDefaultPropTypes,
ExtractPropTypes,
} from './componentProps'
import type { EmitsOptions, EmitsToProps } from './componentEmits'
import type {
EmitsOptions,
EmitsToProps,
TypeEmitsToOptions,
} from './componentEmits'
import { extend, isFunction } from '@vue/shared'
import type { VNodeProps } from './vnode'
import type {
@ -29,6 +34,8 @@ import type {
CreateComponentPublicInstance,
} from './componentPublicInstance'
import type { SlotsType } from './componentSlots'
import type { Directive } from './directives'
import type { ComponentTypeEmits } from './apiSetupHelpers'
export type PublicProps = VNodeProps &
AllowedComponentProps &
@ -55,6 +62,11 @@ export type DefineComponent<
Props = ResolveProps<PropsOrPropOptions, E>,
Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
MakeDefaultsOptional extends boolean = true,
> = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
@ -67,9 +79,12 @@ export type DefineComponent<
E,
PP & Props,
Defaults,
true,
MakeDefaultsOptional,
{},
S
S,
LC & GlobalComponents,
Directives & GlobalDirectives,
Exposed
>
> &
ComponentOptionsBase<
@ -85,7 +100,11 @@ export type DefineComponent<
Defaults,
{},
string,
S
S,
LC & GlobalComponents,
Directives & GlobalDirectives,
Exposed,
Provide
> &
PP
@ -153,147 +172,114 @@ export function defineComponent<
},
): DefineSetupFnComponent<Props, E, S>
// overload 2: object format with no props
// (uses user defined props interface)
// return type is for Vetur and TSX support
// overload 2: defineComponent with options object, infer props from options
export function defineComponent<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
// props
TypeProps,
RuntimePropsOptions extends
ComponentObjectPropsOptions = ComponentObjectPropsOptions,
RuntimePropsKeys extends string = string,
// emits
TypeEmits extends ComponentTypeEmits = {},
RuntimeEmitsOptions extends EmitsOptions = {},
RuntimeEmitsKeys extends string = string,
// other options
Data = {},
SetupBindings = {},
Computed extends ComputedOptions = {},
Methods extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
InjectOptions extends ComponentInjectOptions = {},
InjectKeys extends string = string,
Slots extends SlotsType = {},
LocalComponents extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
// resolved types
ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions
? TypeEmitsToOptions<TypeEmits>
: RuntimeEmitsOptions,
InferredProps = unknown extends TypeProps
? string extends RuntimePropsKeys
? ComponentObjectPropsOptions extends RuntimePropsOptions
? {}
: ExtractPropTypes<RuntimePropsOptions>
: { [key in RuntimePropsKeys]?: any }
: TypeProps,
ResolvedProps = Readonly<InferredProps & EmitsToProps<ResolvedEmits>>,
>(
options: ComponentOptionsWithoutProps<
Props,
RawBindings,
D,
C,
M,
options: {
props?: (RuntimePropsOptions & ThisType<void>) | RuntimePropsKeys[]
/**
* @private for language-tools use only
*/
__typeProps?: TypeProps
/**
* @private for language-tools use only
*/
__typeEmits?: TypeEmits
} & ComponentOptionsBase<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
E,
EE,
I,
II,
S
>,
): DefineComponent<
Props,
RawBindings,
D,
C,
M,
RuntimeEmitsOptions,
RuntimeEmitsKeys,
{}, // Defaults
InjectOptions,
InjectKeys,
Slots,
LocalComponents,
Directives,
Exposed,
Provide
> &
ThisType<
CreateComponentPublicInstance<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
E,
EE,
PublicProps,
ResolveProps<Props, E>,
ExtractDefaultPropTypes<Props>,
S
ResolvedEmits,
RuntimeEmitsKeys,
{},
false,
InjectOptions,
Slots,
LocalComponents,
Directives,
Exposed
>
// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: any }
// return type is for Vetur and TSX support
export function defineComponent<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
Props = Readonly<{ [key in PropNames]?: any }>,
>(
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
>,
): DefineComponent<
Props,
RawBindings,
D,
C,
M,
InferredProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
E,
EE,
ResolvedEmits,
RuntimeEmitsKeys,
PublicProps,
ResolveProps<Props, E>,
ExtractDefaultPropTypes<Props>,
S
>
// overload 4: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function defineComponent<
// the Readonly constraint allows TS to treat the type of { required: true }
// as constant instead of boolean.
PropsOptions extends Readonly<ComponentPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
>,
): DefineComponent<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
PublicProps,
ResolveProps<PropsOptions, E>,
ExtractDefaultPropTypes<PropsOptions>,
S
ResolvedProps,
ExtractDefaultPropTypes<RuntimePropsOptions>,
Slots,
LocalComponents,
Directives,
Exposed,
Provide,
// MakeDefaultsOptional - if TypeProps is provided, set to false to use
// user props types verbatim
unknown extends TypeProps ? true : false
>
// implementation, close to no-op

View File

@ -16,8 +16,8 @@ import {
} from './component'
import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
import type {
ComponentOptionsBase,
ComponentOptionsMixin,
ComponentOptionsWithoutProps,
ComputedOptions,
MethodOptions,
} from './componentOptions'
@ -135,9 +135,11 @@ export function defineEmits<EE extends string = string>(
export function defineEmits<E extends EmitsOptions = EmitsOptions>(
emitOptions: E,
): EmitFn<E>
export function defineEmits<
T extends ((...args: any[]) => any) | Record<string, any[]>,
>(): T extends (...args: any[]) => any ? T : ShortEmits<T>
export function defineEmits<T extends ComponentTypeEmits>(): T extends (
...args: any[]
) => any
? T
: ShortEmits<T>
// implementation
export function defineEmits() {
if (__DEV__) {
@ -146,6 +148,10 @@ export function defineEmits() {
return null as any
}
export type ComponentTypeEmits =
| ((...args: any[]) => any)
| Record<string, any[]>
type RecordToUnion<T extends Record<string, any>> = T[keyof T]
type ShortEmits<T extends Record<string, any>> = UnionToIntersection<
@ -191,15 +197,33 @@ export function defineOptions<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
>(
options?: ComponentOptionsWithoutProps<
options?: ComponentOptionsBase<
{},
RawBindings,
D,
C,
M,
Mixin,
Extends
> & { emits?: undefined; expose?: undefined; slots?: undefined },
Extends,
{}
> & {
/**
* props should be defined via defineProps().
*/
props: never
/**
* emits should be defined via defineEmits().
*/
emits?: never
/**
* expose should be defined via defineExpose().
*/
expose?: never
/**
* slots should be defined via defineSlots().
*/
slots?: never
},
): void {
if (__DEV__) {
warnRuntimeUsage(`defineOptions`)

View File

@ -87,6 +87,13 @@ import {
import type { SchedulerJob } from './scheduler'
import type { LifecycleHooks } from './enums'
// Augment GlobalComponents
import type { TeleportProps } from './components/Teleport'
import type { SuspenseProps } from './components/Suspense'
import type { KeepAliveProps } from './components/KeepAlive'
import type { BaseTransitionProps } from './components/BaseTransition'
import type { DefineComponent } from './apiDefineComponent'
/**
* Public utility type for extracting the instance type of a component.
* Works with all valid component definition types. This is intended to replace
@ -125,6 +132,45 @@ export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
*/
export interface ComponentCustomProps {}
/**
* For globally defined Directives
* Here is an example of adding a directive `VTooltip` as global directive:
*
* @example
* ```ts
* import VTooltip from 'v-tooltip'
*
* declare module '@vue/runtime-core' {
* interface GlobalDirectives {
* VTooltip
* }
* }
* ```
*/
export interface GlobalDirectives extends Record<string, Directive> {}
/**
* For globally defined Components
* Here is an example of adding a component `RouterView` as global component:
*
* @example
* ```ts
* import { RouterView } from 'vue-router'
*
* declare module '@vue/runtime-core' {
* interface GlobalComponents {
* RouterView
* }
* }
* ```
*/
export interface GlobalComponents extends Record<string, Component> {
Teleport: DefineComponent<TeleportProps>
Suspense: DefineComponent<SuspenseProps>
KeepAlive: DefineComponent<KeepAliveProps>
BaseTransition: DefineComponent<BaseTransitionProps>
}
/**
* Default allowed non-declared props on component in TSX
*/

View File

@ -1,5 +1,6 @@
import {
EMPTY_OBJ,
type OverloadParameters,
type UnionToIntersection,
camelize,
extend,
@ -28,6 +29,7 @@ import {
compatModelEmit,
compatModelEventPrefix,
} from './compat/componentVModel'
import type { ComponentTypeEmits } from './apiSetupHelpers'
export type ObjectEmitsOptions = Record<
string,
@ -36,7 +38,8 @@ export type ObjectEmitsOptions = Record<
export type EmitsOptions = ObjectEmitsOptions | string[]
export type EmitsToProps<T extends EmitsOptions> = T extends string[]
export type EmitsToProps<T extends EmitsOptions | ComponentTypeEmits> =
T extends string[]
? {
[K in `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
}
@ -54,6 +57,23 @@ export type EmitsToProps<T extends EmitsOptions> = T extends string[]
}
: {}
export type TypeEmitsToOptions<T extends ComponentTypeEmits> =
T extends Record<string, any[]>
? {
[K in keyof T]: T[K] extends [...args: infer Args]
? (...args: Args) => any
: () => any
}
: T extends (...args: any[]) => any
? ParametersToFns<OverloadParameters<T>>
: {}
type ParametersToFns<T extends any[]> = {
[K in T[0]]: K extends `${infer C}`
? (...args: T extends [C, ...infer Args] ? Args : never) => any
: never
}
export type ShortEmitsToObject<E> =
E extends Record<string, any[]>
? {

View File

@ -55,7 +55,11 @@ import type {
ExtractDefaultPropTypes,
ExtractPropTypes,
} from './componentProps'
import type { EmitsOptions, EmitsToProps } from './componentEmits'
import type {
EmitsOptions,
EmitsToProps,
TypeEmitsToOptions,
} from './componentEmits'
import type { Directive } from './directives'
import {
type ComponentPublicInstance,
@ -77,7 +81,10 @@ import {
import type { OptionMergeFunction } from './apiCreateApp'
import { LifecycleHooks } from './enums'
import type { SlotsType } from './componentSlots'
import { normalizePropsOrEmits } from './apiSetupHelpers'
import {
type ComponentTypeEmits,
normalizePropsOrEmits,
} from './apiSetupHelpers'
/**
* Interface for declaring custom options.
@ -113,7 +120,11 @@ export interface ComponentOptionsBase<
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, I, II>,
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, I, II, Provide>,
ComponentInternalOptions,
ComponentCustomOptions {
setup?: (
@ -137,13 +148,16 @@ export interface ComponentOptionsBase<
// Luckily `render()` doesn't need any arguments nor does it care about return
// type.
render?: Function
components?: Record<string, Component>
directives?: Record<string, Directive>
// NOTE: extending both LC and Record<string, Component> allows objects to be forced
// to be of type Component, while still inferring LC generic
components?: LC & Record<string, Component>
// NOTE: extending both Directives and Record<string, Directive> allows objects to be forced
// to be of type Directive, while still inferring Directives generic
directives?: Directives & Record<string, Directive>
inheritAttrs?: boolean
emits?: (E | EE[]) & ThisType<void>
slots?: S
// TODO infer public instance type based on exposed keys
expose?: string[]
expose?: Exposed[]
serverPrefetch?(): void | Promise<any>
// Runtime compiler only -----------------------------------------------------
@ -212,151 +226,6 @@ export interface RuntimeCompilerOptions {
delimiters?: [string, string]
}
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
PE = Props & EmitsToProps<E>,
> = ComponentOptionsBase<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S
> & {
props?: undefined
} & ThisType<
CreateComponentPublicInstance<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
PE,
{},
false,
I,
S
>
>
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S
> & {
props: PropNames[]
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
{},
false,
I,
S
>
>
export type ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
Defaults = ExtractDefaultPropTypes<PropsOptions>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Defaults,
I,
II,
S
> & {
props: PropsOptions & ThisType<void>
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
Defaults,
false,
I,
S
>
>
export type ComponentOptions<
Props = {},
RawBindings = any,
@ -366,7 +235,15 @@ export type ComponentOptions<
Mixin extends ComponentOptionsMixin = any,
Extends extends ComponentOptionsMixin = any,
E extends EmitsOptions = any,
S extends SlotsType = any,
EE extends string = string,
Defaults = {},
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentOptionsBase<
Props,
RawBindings,
@ -376,8 +253,15 @@ export type ComponentOptions<
Mixin,
Extends,
E,
string,
S
EE,
Defaults,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> &
ThisType<
CreateComponentPublicInstance<
@ -389,7 +273,13 @@ export type ComponentOptions<
Mixin,
Extends,
E,
Readonly<Props>
Readonly<Props>,
Defaults,
false,
I,
S,
LC,
Directives
>
>
@ -404,6 +294,12 @@ export type ComponentOptionsMixin = ComponentOptionsBase<
any,
any,
any,
any,
any,
any,
any,
any,
any,
any
>
@ -465,6 +361,7 @@ interface LegacyOptions<
Extends extends ComponentOptionsMixin,
I extends ComponentInjectOptions,
II extends string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> {
compatConfig?: CompatConfig
@ -498,7 +395,7 @@ interface LegacyOptions<
computed?: C
methods?: M
watch?: ComponentWatchOptions
provide?: ComponentProvideOptions
provide?: Provide
inject?: I | II[]
// assets
@ -1199,3 +1096,203 @@ function mergeWatchOptions(
}
return merged
}
// Deprecated legacy types, kept because they were previously exported ---------
/**
* @deprecated
*/
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
TE extends ComponentTypeEmits = {},
ResolvedEmits extends EmitsOptions = {} extends E
? TypeEmitsToOptions<TE>
: E,
PE = Props & EmitsToProps<ResolvedEmits>,
> = ComponentOptionsBase<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props?: never
/**
* @private for language-tools use only
*/
__typeProps?: Props
/**
* @private for language-tools use only
*/
__typeEmits?: TE
} & ThisType<
CreateComponentPublicInstance<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
ResolvedEmits,
EE,
{},
false,
I,
S,
LC,
Directives,
Exposed
>
>
/**
* @deprecated
*/
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props: PropNames[]
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
{},
false,
I,
S,
LC,
Directives,
Exposed
>
>
/**
* @deprecated
*/
export type ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
Defaults = ExtractDefaultPropTypes<PropsOptions>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Defaults,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props: PropsOptions & ThisType<void>
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
Defaults,
false,
I,
S,
LC,
Directives
>
>

View File

@ -67,7 +67,7 @@ export interface PropOptions<T = any, D = T> {
skipFactory?: boolean
}
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
export type PropType<T> = PropConstructor<T> | (PropConstructor<T> | null)[]
type PropConstructor<T = any> =
| { new (...args: any[]): T & {} }
@ -107,8 +107,10 @@ type DefaultKeys<T> = {
: never
}[keyof T]
type InferPropType<T> = [T] extends [null]
? any // null & true would fail to infer
type InferPropType<T, NullAsAny = true> = [T] extends [null]
? NullAsAny extends true
? any
: null
: [T] extends [{ type: null | true }]
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
@ -119,8 +121,8 @@ type InferPropType<T> = [T] extends [null]
? Date
: [T] extends [(infer U)[] | { type: (infer U)[] }]
? U extends DateConstructor
? Date | InferPropType<U>
: InferPropType<U>
? Date | InferPropType<U, false>
: InferPropType<U, false>
: [T] extends [Prop<infer V, infer D>]
? unknown extends V
? IfAny<V, V, D>
@ -594,7 +596,7 @@ function validatePropName(key: string) {
// use function string name to check type constructors
// so that it works across vms / iframes.
function getType(ctor: Prop<any>): string {
function getType(ctor: Prop<any> | null): string {
// Early return for null to avoid unnecessary computations
if (ctor === null) {
return 'null'
@ -614,7 +616,7 @@ function getType(ctor: Prop<any>): string {
return ''
}
function isSameType(a: Prop<any>, b: Prop<any>): boolean {
function isSameType(a: Prop<any> | null, b: Prop<any> | null): boolean {
return getType(a) === getType(b)
}
@ -707,24 +709,27 @@ type AssertionResult = {
/**
* dev only
*/
function assertType(value: unknown, type: PropConstructor): AssertionResult {
function assertType(
value: unknown,
type: PropConstructor | null,
): AssertionResult {
let valid
const expectedType = getType(type)
if (isSimpleType(expectedType)) {
if (expectedType === 'null') {
valid = value === null
} else if (isSimpleType(expectedType)) {
const t = typeof value
valid = t === expectedType.toLowerCase()
// for primitive wrapper objects
if (!valid && t === 'object') {
valid = value instanceof type
valid = value instanceof (type as PropConstructor)
}
} else if (expectedType === 'Object') {
valid = isObject(value)
} else if (expectedType === 'Array') {
valid = isArray(value)
} else if (expectedType === 'null') {
valid = value === null
} else {
valid = value instanceof type
valid = value instanceof (type as PropConstructor)
}
return {
valid,

View File

@ -1,4 +1,5 @@
import {
type Component,
type ComponentInternalInstance,
getExposeProxy,
isStatefulComponent,
@ -35,6 +36,7 @@ import {
type ComponentInjectOptions,
type ComponentOptionsBase,
type ComponentOptionsMixin,
type ComponentProvideOptions,
type ComputedOptions,
type ExtractComputedReturns,
type InjectToObject,
@ -51,6 +53,7 @@ import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning'
import { installCompatInstanceProperties } from './compat/instance'
import type { Directive } from './directives'
/**
* Custom properties added to component instances in any way and can be accessed through `this`
@ -99,6 +102,10 @@ type MixinToOptionTypes<T> =
infer Defaults,
any,
any,
any,
any,
any,
any,
any
>
? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
@ -157,6 +164,9 @@ export type CreateComponentPublicInstance<
MakeDefaultsOptional extends boolean = false,
I extends ComponentInjectOptions = {},
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
@ -167,6 +177,7 @@ export type CreateComponentPublicInstance<
EnsureNonVoid<M>,
PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
EnsureNonVoid<Defaults>,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentPublicInstance<
PublicP,
PublicB,
@ -190,11 +201,22 @@ export type CreateComponentPublicInstance<
Defaults,
{},
string,
S
S,
LC,
Directives,
Exposed,
Provide
>,
I,
S
S,
Exposed
>
export type ExposedKeys<
T,
Exposed extends string & keyof T,
> = '' extends Exposed ? T : Pick<T, Exposed>
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
export type ComponentPublicInstance<
@ -210,6 +232,7 @@ export type ComponentPublicInstance<
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>,
I extends ComponentInjectOptions = {},
S extends SlotsType = {},
Exposed extends string = '',
> = {
$: ComponentInternalInstance
$data: D
@ -233,13 +256,16 @@ export type ComponentPublicInstance<
: (...args: any) => any,
options?: WatchOptions,
): WatchStopHandle
} & IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> &
} & ExposedKeys<
IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> &
ShallowUnwrapRef<B> &
UnwrapNestedRefs<D> &
ExtractComputedReturns<C> &
M &
ComponentCustomProperties &
InjectToObject<I>
InjectToObject<I>,
Exposed
>
export type PublicPropertiesMap = Record<
string,

View File

@ -18,6 +18,7 @@ import { SchedulerJobFlags, toRaw } from '@vue/reactivity'
import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared'
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
import { isTeleport } from './Teleport'
import type { RendererElement } from '../renderer'
type Hook<T = () => void> = T | T[]
@ -151,27 +152,7 @@ const BaseTransitionImpl: ComponentOptions = {
return
}
let child: VNode = children[0]
if (children.length > 1) {
let hasFound = false
// locate first non-comment child
for (const c of children) {
if (c.type !== Comment) {
if (__DEV__ && hasFound) {
// warn more than one non-comment child
warn(
'<transition> can only be used on a single element or component. ' +
'Use <transition-group> for lists.',
)
break
}
child = c
hasFound = true
if (!__DEV__) break
}
}
}
const child: VNode = findNonCommentChild(children)
// there's no need to track reactivity for these props so use the raw
// props for a bit better perf
const rawProps = toRaw(props)
@ -193,7 +174,7 @@ const BaseTransitionImpl: ComponentOptions = {
// in the case of <transition><keep-alive/></transition>, we need to
// compare the type of the kept-alive children.
const innerChild = getKeepAliveChild(child)
const innerChild = getInnerChild(child)
if (!innerChild) {
return emptyPlaceholder(child)
}
@ -207,7 +188,7 @@ const BaseTransitionImpl: ComponentOptions = {
setTransitionHooks(innerChild, enterHooks)
const oldChild = instance.subTree
const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
const oldInnerChild = oldChild && getInnerChild(oldChild)
// handle mode
if (
@ -267,6 +248,30 @@ if (__COMPAT__) {
BaseTransitionImpl.__isBuiltIn = true
}
function findNonCommentChild(children: VNode[]): VNode {
let child: VNode = children[0]
if (children.length > 1) {
let hasFound = false
// locate first non-comment child
for (const c of children) {
if (c.type !== Comment) {
if (__DEV__ && hasFound) {
// warn more than one non-comment child
warn(
'<transition> can only be used on a single element or component. ' +
'Use <transition-group> for lists.',
)
break
}
child = c
hasFound = true
if (!__DEV__) break
}
}
}
return child
}
// export the public type for h/tsx inference
// also to avoid inline import() in generated d.ts files
export const BaseTransition = BaseTransitionImpl as unknown as {
@ -457,8 +462,12 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined {
}
}
function getKeepAliveChild(vnode: VNode): VNode | undefined {
function getInnerChild(vnode: VNode): VNode | undefined {
if (!isKeepAlive(vnode)) {
if (isTeleport(vnode.type) && vnode.children) {
return findNonCommentChild(vnode.children as VNode[])
}
return vnode
}
// #7121 ensure get the child component subtree in case

View File

@ -26,19 +26,29 @@ import type { ComponentPublicInstance } from './componentPublicInstance'
import { mapCompatDirectiveHook } from './compat/customDirective'
import { pauseTracking, resetTracking, traverse } from '@vue/reactivity'
export interface DirectiveBinding<V = any> {
export interface DirectiveBinding<
Value = any,
Modifiers extends string = string,
Arg extends string = string,
> {
instance: ComponentPublicInstance | null
value: V
oldValue: V | null
arg?: string
modifiers: DirectiveModifiers
dir: ObjectDirective<any, V>
value: Value
oldValue: Value | null
arg?: Arg
modifiers: DirectiveModifiers<Modifiers>
dir: ObjectDirective<any, Value>
}
export type DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = (
el: T,
binding: DirectiveBinding<V>,
vnode: VNode<any, T>,
export type DirectiveHook<
HostElement = any,
Prev = VNode<any, HostElement> | null,
Value = any,
Modifiers extends string = string,
Arg extends string = string,
> = (
el: HostElement,
binding: DirectiveBinding<Value, Modifiers, Arg>,
vnode: VNode<any, HostElement>,
prevVNode: Prev,
) => void
@ -47,25 +57,52 @@ export type SSRDirectiveHook = (
vnode: VNode,
) => Data | undefined
export interface ObjectDirective<T = any, V = any> {
created?: DirectiveHook<T, null, V>
beforeMount?: DirectiveHook<T, null, V>
mounted?: DirectiveHook<T, null, V>
beforeUpdate?: DirectiveHook<T, VNode<any, T>, V>
updated?: DirectiveHook<T, VNode<any, T>, V>
beforeUnmount?: DirectiveHook<T, null, V>
unmounted?: DirectiveHook<T, null, V>
export interface ObjectDirective<
HostElement = any,
Value = any,
Modifiers extends string = string,
Arg extends string = string,
> {
created?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
beforeMount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
mounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
beforeUpdate?: DirectiveHook<
HostElement,
VNode<any, HostElement>,
Value,
Modifiers,
Arg
>
updated?: DirectiveHook<
HostElement,
VNode<any, HostElement>,
Value,
Modifiers,
Arg
>
beforeUnmount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
unmounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
getSSRProps?: SSRDirectiveHook
deep?: boolean
}
export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V>
export type FunctionDirective<
HostElement = any,
V = any,
Modifiers extends string = string,
Arg extends string = string,
> = DirectiveHook<HostElement, any, V, Modifiers, Arg>
export type Directive<T = any, V = any> =
| ObjectDirective<T, V>
| FunctionDirective<T, V>
export type Directive<
HostElement = any,
Value = any,
Modifiers extends string = string,
Arg extends string = string,
> =
| ObjectDirective<HostElement, Value, Modifiers, Arg>
| FunctionDirective<HostElement, Value, Modifiers, Arg>
export type DirectiveModifiers = Record<string, boolean>
export type DirectiveModifiers<K extends string = string> = Record<K, boolean>
export function validateDirectiveName(name: string) {
if (isBuiltInDirective(name)) {

View File

@ -77,6 +77,7 @@ export {
withDefaults,
type DefineProps,
type ModelRef,
type ComponentTypeEmits,
} from './apiSetupHelpers'
/**
@ -249,6 +250,8 @@ export type {
SetupContext,
ComponentCustomProps,
AllowedComponentProps,
GlobalComponents,
GlobalDirectives,
ComponentInstance,
} from './component'
export type {
@ -259,9 +262,6 @@ export type {
export type {
ComponentOptions,
ComponentOptionsMixin,
ComponentOptionsWithoutProps,
ComponentOptionsWithObjectProps,
ComponentOptionsWithArrayProps,
ComponentCustomOptions,
ComponentOptionsBase,
ComponentProvideOptions,
@ -271,7 +271,11 @@ export type {
RuntimeCompilerOptions,
ComponentInjectOptions,
} from './componentOptions'
export type { EmitsOptions, ObjectEmitsOptions } from './componentEmits'
export type {
EmitsOptions,
ObjectEmitsOptions,
EmitsToProps,
} from './componentEmits'
export type {
ComponentPublicInstance,
ComponentCustomProperties,

View File

@ -0,0 +1,11 @@
// Note: this file is auto concatenated to the end of the bundled d.ts during
// build.
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Teleport: DefineComponent<TeleportProps>
Suspense: DefineComponent<SuspenseProps>
KeepAlive: DefineComponent<KeepAliveProps>
BaseTransition: DefineComponent<BaseTransitionProps>
}
}

View File

@ -88,10 +88,14 @@ describe('defineCustomElement', () => {
describe('props', () => {
const E = defineCustomElement({
props: ['foo', 'bar', 'bazQux'],
props: {
foo: [String, null],
bar: Object,
bazQux: null,
},
render() {
return [
h('div', null, this.foo),
h('div', null, this.foo || ''),
h('div', null, this.bazQux || (this.bar && this.bar.x)),
]
},

View File

@ -43,7 +43,7 @@ describe('runtime-dom: v-on directive', () => {
})
test('it should support key modifiers and system modifiers', () => {
const keyNames = ['ctrl', 'shift', 'meta', 'alt']
const keyNames = ['ctrl', 'shift', 'meta', 'alt'] as const
keyNames.forEach(keyName => {
const el = document.createElement('div')

View File

@ -1,6 +1,6 @@
{
"name": "@vue/runtime-dom",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",

View File

@ -1,16 +1,19 @@
import {
type Component,
type ComponentInjectOptions,
type ComponentInternalInstance,
type ComponentObjectPropsOptions,
type ComponentOptions,
type ComponentOptionsBase,
type ComponentOptionsMixin,
type ComponentOptionsWithArrayProps,
type ComponentOptionsWithObjectProps,
type ComponentOptionsWithoutProps,
type ComponentPropsOptions,
type ComponentProvideOptions,
type ComputedOptions,
type ConcreteComponent,
type CreateComponentPublicInstance,
type DefineComponent,
type Directive,
type EmitsOptions,
type EmitsToProps,
type ExtractPropTypes,
type MethodOptions,
type RenderFunction,
@ -41,98 +44,79 @@ export function defineCustomElement<Props, RawBindings = object>(
) => RawBindings | RenderFunction,
): VueElementConstructor<Props>
// overload 2: object format with no props
// overload 2: defineCustomElement with options object, infer props from options
export function defineCustomElement<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
// props
RuntimePropsOptions extends
ComponentObjectPropsOptions = ComponentObjectPropsOptions,
PropsKeys extends string = string,
// emits
RuntimeEmitsOptions extends EmitsOptions = {},
EmitsKeys extends string = string,
// other options
Data = {},
SetupBindings = {},
Computed extends ComputedOptions = {},
Methods extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
InjectOptions extends ComponentInjectOptions = {},
InjectKeys extends string = string,
Slots extends SlotsType = {},
LocalComponents extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
// resolved types
InferredProps = string extends PropsKeys
? ComponentObjectPropsOptions extends RuntimePropsOptions
? {}
: ExtractPropTypes<RuntimePropsOptions>
: { [key in PropsKeys]?: any },
ResolvedProps = InferredProps & EmitsToProps<RuntimeEmitsOptions>,
>(
options: ComponentOptionsWithoutProps<
Props,
RawBindings,
D,
C,
M,
options: {
props?: (RuntimePropsOptions & ThisType<void>) | PropsKeys[]
} & ComponentOptionsBase<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
E,
EE,
I,
II,
S
> & { styles?: string[] },
): VueElementConstructor<Props>
// overload 3: object format with array props declaration
export function defineCustomElement<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
>(
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
RuntimeEmitsOptions,
EmitsKeys,
{}, // Defaults
InjectOptions,
InjectKeys,
Slots,
LocalComponents,
Directives,
Exposed,
Provide
> &
ThisType<
CreateComponentPublicInstance<
Readonly<ResolvedProps>,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
E,
EE,
I,
II,
S
> & { styles?: string[] },
): VueElementConstructor<{ [K in PropNames]: any }>
// overload 4: object format with object props declaration
export function defineCustomElement<
PropsOptions extends Readonly<ComponentPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
> & { styles?: string[] },
): VueElementConstructor<ExtractPropTypes<PropsOptions>>
RuntimeEmitsOptions,
EmitsKeys,
{},
false,
InjectOptions,
Slots,
LocalComponents,
Directives,
Exposed
>
>,
): VueElementConstructor<ResolvedProps>
// overload 5: defining a custom element from the returned value of
// `defineComponent`

View File

@ -39,14 +39,17 @@ function onCompositionEnd(e: Event) {
const assignKey = Symbol('_assign')
type ModelDirective<T> = ObjectDirective<
T & { [assignKey]: AssignerFn; _assigning?: boolean }
type ModelDirective<T, Modifiers extends string = string> = ObjectDirective<
T & { [assignKey]: AssignerFn; _assigning?: boolean },
any,
Modifiers
>
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement
HTMLInputElement | HTMLTextAreaElement,
'trim' | 'number' | 'lazy'
> = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
el[assignKey] = getModelAssigner(vnode)
@ -183,7 +186,7 @@ export const vModelRadio: ModelDirective<HTMLInputElement> = {
},
}
export const vModelSelect: ModelDirective<HTMLSelectElement> = {
export const vModelSelect: ModelDirective<HTMLSelectElement, 'number'> = {
// <select multiple> value need to be deep traversed
deep: true,
created(el, { value, modifiers: { number } }, vnode) {
@ -363,3 +366,10 @@ export function initVModelForSSR() {
}
}
}
export type VModelDirective =
| typeof vModelText
| typeof vModelCheckbox
| typeof vModelSelect
| typeof vModelRadio
| typeof vModelDynamic

View File

@ -1,33 +1,40 @@
import {
type ComponentInternalInstance,
DeprecationTypes,
type Directive,
type LegacyConfig,
compatUtils,
getCurrentInstance,
} from '@vue/runtime-core'
import { hyphenate, isArray } from '@vue/shared'
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta']
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'] as const
type SystemModifiers = (typeof systemModifiers)[number]
type CompatModifiers = keyof typeof keyNames
export type VOnModifiers = SystemModifiers | ModifierGuards | CompatModifiers
type KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent
const modifierGuards: Record<
string,
(e: Event, modifiers: string[]) => void | boolean
> = {
stop: e => e.stopPropagation(),
prevent: e => e.preventDefault(),
self: e => e.target !== e.currentTarget,
ctrl: e => !(e as KeyedEvent).ctrlKey,
shift: e => !(e as KeyedEvent).shiftKey,
alt: e => !(e as KeyedEvent).altKey,
meta: e => !(e as KeyedEvent).metaKey,
left: e => 'button' in e && (e as MouseEvent).button !== 0,
middle: e => 'button' in e && (e as MouseEvent).button !== 1,
right: e => 'button' in e && (e as MouseEvent).button !== 2,
const modifierGuards = {
stop: (e: Event) => e.stopPropagation(),
prevent: (e: Event) => e.preventDefault(),
self: (e: Event) => e.target !== e.currentTarget,
ctrl: (e: Event) => !(e as KeyedEvent).ctrlKey,
shift: (e: Event) => !(e as KeyedEvent).shiftKey,
alt: (e: Event) => !(e as KeyedEvent).altKey,
meta: (e: Event) => !(e as KeyedEvent).metaKey,
left: (e: Event) => 'button' in e && (e as MouseEvent).button !== 0,
middle: (e: Event) => 'button' in e && (e as MouseEvent).button !== 1,
right: (e: Event) => 'button' in e && (e as MouseEvent).button !== 2,
exact: (e, modifiers) =>
systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)),
}
} satisfies Record<
string,
| ((e: Event) => void | boolean)
| ((e: Event, modifiers: string[]) => void | boolean)
>
type ModifierGuards = keyof typeof modifierGuards
/**
* @private
@ -36,7 +43,7 @@ export const withModifiers = <
T extends (event: Event, ...args: unknown[]) => any,
>(
fn: T & { _withMods?: { [key: string]: T } },
modifiers: string[],
modifiers: VOnModifiers[],
) => {
const cache = fn._withMods || (fn._withMods = {})
const cacheKey = modifiers.join('.')
@ -44,7 +51,7 @@ export const withModifiers = <
cache[cacheKey] ||
(cache[cacheKey] = ((event, ...args) => {
for (let i = 0; i < modifiers.length; i++) {
const guard = modifierGuards[modifiers[i]]
const guard = modifierGuards[modifiers[i] as ModifierGuards]
if (guard && guard(event, modifiers)) return
}
return fn(event, ...args)
@ -54,7 +61,7 @@ export const withModifiers = <
// Kept for 2.x compat.
// Note: IE11 compat for `spacebar` and `del` is removed for now.
const keyNames: Record<string, string | string[]> = {
const keyNames = {
esc: 'escape',
space: ' ',
up: 'arrow-up',
@ -62,7 +69,7 @@ const keyNames: Record<string, string | string[]> = {
right: 'arrow-right',
down: 'arrow-down',
delete: 'backspace',
}
} satisfies Record<string, string | string[]>
/**
* @private
@ -101,7 +108,13 @@ export const withKeys = <T extends (event: KeyboardEvent) => any>(
}
const eventKey = hyphenate(event.key)
if (modifiers.some(k => k === eventKey || keyNames[k] === eventKey)) {
if (
modifiers.some(
k =>
k === eventKey ||
keyNames[k as unknown as CompatModifiers] === eventKey,
)
) {
return fn(event)
}
@ -133,3 +146,5 @@ export const withKeys = <T extends (event: KeyboardEvent) => any>(
}) as T)
)
}
export type VOnDirective = Directive<any, any, VOnModifiers>

View File

@ -1,7 +1,9 @@
import {
type App,
type CreateAppFunction,
type DefineComponent,
DeprecationTypes,
type Directive,
type ElementNamespace,
type HydrationRenderer,
type Renderer,
@ -25,6 +27,11 @@ import {
isSVGTag,
isString,
} from '@vue/shared'
import type { TransitionProps } from './components/Transition'
import type { TransitionGroupProps } from './components/TransitionGroup'
import type { vShow } from './directives/vShow'
import type { VOnDirective } from './directives/vOn'
import type { VModelDirective } from './directives/vModel'
declare module '@vue/reactivity' {
export interface RefUnwrapBailTypes {
@ -32,6 +39,22 @@ declare module '@vue/reactivity' {
}
}
declare module '@vue/runtime-core' {
interface GlobalComponents {
Transition: DefineComponent<TransitionProps>
TransitionGroup: DefineComponent<TransitionGroupProps>
}
interface GlobalDirectives {
vShow: typeof vShow
vOn: VOnDirective
vBind: VModelDirective
vIf: Directive<any, boolean>
VOnce: Directive
VSlot: Directive
}
}
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)
// lazy create the renderer - this makes core renderer logic tree-shakable

View File

@ -74,7 +74,7 @@ function eventHandler(
let handler = getter()
if (!handler) return
if (modifiers) handler = withModifiers(handler, modifiers)
if (modifiers) handler = withModifiers(handler, modifiers as any[])
if (keys) handler = withKeys(handler, keys)
handler && handler(...args)
}

View File

@ -1,6 +1,6 @@
{
"name": "@vue/server-renderer",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "@vue/server-renderer",
"main": "index.js",
"module": "dist/server-renderer.esm-bundler.js",

View File

@ -1,6 +1,6 @@
{
"name": "@vue/shared",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "internal utils shared across @vue packages",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",

View File

@ -23,3 +23,34 @@ export type Awaited<T> = T extends null | undefined
: T // non-object or non-thenable
export type Data = Record<string, unknown>
/**
* Utility for extracting the parameters from a function overload (for typed emits)
* https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709
*/
export type OverloadParameters<T extends (...args: any[]) => any> = Parameters<
OverloadUnion<T>
>
type OverloadProps<TOverload> = Pick<TOverload, keyof TOverload>
type OverloadUnionRecursive<
TOverload,
TPartialOverload = unknown,
> = TOverload extends (...args: infer TArgs) => infer TReturn
? TPartialOverload extends TOverload
? never
:
| OverloadUnionRecursive<
TPartialOverload & TOverload,
TPartialOverload &
((...args: TArgs) => TReturn) &
OverloadProps<TOverload>
>
| ((...args: TArgs) => TReturn)
: never
type OverloadUnion<TOverload extends (...args: any[]) => any> = Exclude<
OverloadUnionRecursive<(() => never) & TOverload>,
TOverload extends () => never ? never : () => never
>

View File

@ -292,7 +292,7 @@ describe('INSTANCE_SCOPED_SLOTS', () => {
components: {
child: {
compatConfig: { RENDER_FUNCTION: false },
render() {
render(this: LegacyPublicInstance) {
normalSlots = this.$slots
scopedSlots = this.$scopedSlots
},

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compat",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "Vue 3 compatibility build for Vue 2",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",

View File

@ -1725,6 +1725,95 @@ describe('e2e: Transition', () => {
)
})
describe('transition with Teleport', () => {
test(
'apply transition to teleport child',
async () => {
await page().evaluate(() => {
const { createApp, ref, h } = (window as any).Vue
createApp({
template: `
<div id="target"></div>
<div id="container">
<transition>
<Teleport to="#target">
<!-- comment -->
<Comp v-if="toggle" class="test">content</Comp>
</Teleport>
</transition>
</div>
<button id="toggleBtn" @click="click">button</button>
`,
components: {
Comp: {
setup() {
return () => h('div', { class: 'test' }, 'content')
},
},
},
setup: () => {
const toggle = ref(false)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
},
}).mount('#app')
})
expect(await html('#target')).toBe('<!-- comment --><!--v-if-->')
expect(await html('#container')).toBe(
'<!--teleport start--><!--teleport end-->',
)
const classWhenTransitionStart = () =>
page().evaluate(() => {
;(document.querySelector('#toggleBtn') as any)!.click()
return Promise.resolve().then(() => {
// find the class of teleported node
return document
.querySelector('#target div')!
.className.split(/\s+/g)
})
})
// enter
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-enter-from',
'v-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-enter-active',
'v-enter-to',
])
await transitionFinish()
expect(await html('#target')).toBe(
'<!-- comment --><div class="test">content</div>',
)
// leave
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-leave-from',
'v-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-leave-active',
'v-leave-to',
])
await transitionFinish()
expect(await html('#target')).toBe('<!-- comment --><!--v-if-->')
expect(await html('#container')).toBe(
'<!--teleport start--><!--teleport end-->',
)
},
E2E_TIMEOUT,
)
})
describe('transition with v-show', () => {
test(
'named transition with v-show',

View File

@ -1,6 +1,6 @@
{
"name": "vue",
"version": "3.4.25",
"version": "3.5.0-alpha.1",
"description": "The progressive JavaScript framework for building modern web UI.",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",