From a084df151537276a663b672f3852255a4798a5e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kevin=20Deng=20=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90?=
Date: Mon, 22 Apr 2024 15:03:39 +0800
Subject: [PATCH 1/8] dx(compiler-dom): warn on invalid html nesting (#10734)
---
.../transforms/validateHtmlNesting.spec.ts | 20 ++
packages/compiler-dom/src/htmlNesting.ts | 195 ++++++++++++++++++
packages/compiler-dom/src/index.ts | 3 +-
.../src/transforms/validateHtmlNesting.ts | 27 +++
4 files changed, 244 insertions(+), 1 deletion(-)
create mode 100644 packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts
create mode 100644 packages/compiler-dom/src/htmlNesting.ts
create mode 100644 packages/compiler-dom/src/transforms/validateHtmlNesting.ts
diff --git a/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts b/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts
new file mode 100644
index 000000000..ad9f91713
--- /dev/null
+++ b/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts
@@ -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(`
`, {
+ onWarn: e => (err = e),
+ })
+ expect(err).toBeDefined()
+ expect(err!.message).toMatch(` cannot be child of
`)
+ })
+
+ it('should not warn with select > hr', () => {
+ let err: CompilerError | undefined
+ compile(``, {
+ onWarn: e => (err = e),
+ })
+ expect(err).toBeUndefined()
+ })
+})
diff --git a/packages/compiler-dom/src/htmlNesting.ts b/packages/compiler-dom/src/htmlNesting.ts
new file mode 100644
index 000000000..cb0a7626d
--- /dev/null
+++ b/packages/compiler-dom/src/htmlNesting.ts
@@ -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> = {
+ 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> = {
+ // 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> = {
+ 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> = {
+ 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,
+}
diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts
index a3a738a8f..809f37080 100644
--- a/packages/compiler-dom/src/index.ts
+++ b/packages/compiler-dom/src/index.ts
@@ -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 = {
diff --git a/packages/compiler-dom/src/transforms/validateHtmlNesting.ts b/packages/compiler-dom/src/transforms/validateHtmlNesting.ts
new file mode 100644
index 000000000..540c0c258
--- /dev/null
+++ b/packages/compiler-dom/src/transforms/validateHtmlNesting.ts
@@ -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)
+ }
+}
From 85f3592725e18316762abb86dd521ebf10ad22e3 Mon Sep 17 00:00:00 2001
From: Evan You
Date: Wed, 24 Apr 2024 17:27:51 +0800
Subject: [PATCH 2/8] refactor: should throw errors in computed getters
---
packages/reactivity/src/effect.ts | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts
index 90db99cf7..e361e8540 100644
--- a/packages/reactivity/src/effect.ts
+++ b/packages/reactivity/src/effect.ts
@@ -375,12 +375,13 @@ export function refreshComputed(computed: ComputedRefImpl) {
}
} catch (err) {
dep.version++
+ throw err
+ } finally {
+ activeSub = prevSub
+ shouldTrack = prevShouldTrack
+ cleanupDeps(computed)
+ computed.flags &= ~EffectFlags.RUNNING
}
-
- activeSub = prevSub
- shouldTrack = prevShouldTrack
- cleanupDeps(computed)
- computed.flags &= ~EffectFlags.RUNNING
}
function removeSub(link: Link) {
From 28841fee43a45c37905c2c1ed9ace23067539045 Mon Sep 17 00:00:00 2001
From: Wick
Date: Thu, 25 Apr 2024 10:30:51 +0800
Subject: [PATCH 3/8] fix(reactivity): fix call sequence of ontrigger in effect
(#10501)
---
packages/reactivity/__tests__/effect.spec.ts | 71 ++++++++++++++++++++
packages/reactivity/src/dep.ts | 53 ++++++++++-----
2 files changed, 109 insertions(+), 15 deletions(-)
diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts
index 3936216ae..fba816da0 100644
--- a/packages/reactivity/__tests__/effect.spec.ts
+++ b/packages/reactivity/__tests__/effect.spec.ts
@@ -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 })
diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts
index 0dccf40aa..f4e4fd971 100644
--- a/packages/reactivity/src/dep.ts
+++ b/packages/reactivity/src/dep.ts
@@ -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__ &&
- link.sub.onTrigger &&
- !(link.sub.flags & EffectFlags.NOTIFIED)
- ) {
- link.sub.onTrigger(
- extend(
- {
- effect: link.sub,
- },
- debugInfo,
- ),
- )
+ 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__ &&
+ head.sub.onTrigger &&
+ !(head.sub.flags & EffectFlags.NOTIFIED)
+ ) {
+ head.sub.onTrigger(
+ extend(
+ {
+ 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
}
From 0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0 Mon Sep 17 00:00:00 2001
From: edison
Date: Thu, 25 Apr 2024 15:19:53 +0800
Subject: [PATCH 4/8] feat(transition): support directly nesting Teleport
inside Transition (#6548)
close #5836
---
.../src/components/BaseTransition.ts | 57 +++++++-----
packages/vue/__tests__/e2e/Transition.spec.ts | 89 +++++++++++++++++++
2 files changed, 122 insertions(+), 24 deletions(-)
diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts
index f54a65702..38fa44551 100644
--- a/packages/runtime-core/src/components/BaseTransition.ts
+++ b/packages/runtime-core/src/components/BaseTransition.ts
@@ -18,6 +18,7 @@ import { 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'
import { SchedulerJobFlags } from '../scheduler'
@@ -152,27 +153,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(
- ' can only be used on a single element or component. ' +
- 'Use 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)
@@ -194,7 +175,7 @@ const BaseTransitionImpl: ComponentOptions = {
// in the case of , we need to
// compare the type of the kept-alive children.
- const innerChild = getKeepAliveChild(child)
+ const innerChild = getInnerChild(child)
if (!innerChild) {
return emptyPlaceholder(child)
}
@@ -208,7 +189,7 @@ const BaseTransitionImpl: ComponentOptions = {
setTransitionHooks(innerChild, enterHooks)
const oldChild = instance.subTree
- const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
+ const oldInnerChild = oldChild && getInnerChild(oldChild)
// handle mode
if (
@@ -268,6 +249,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(
+ ' can only be used on a single element or component. ' +
+ 'Use 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 {
@@ -458,8 +463,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
diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts
index e8d6d1e04..d52af21ce 100644
--- a/packages/vue/__tests__/e2e/Transition.spec.ts
+++ b/packages/vue/__tests__/e2e/Transition.spec.ts
@@ -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: `
+
+
+
+
+
+ content
+
+
+
+
+ `,
+ 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('')
+ expect(await html('#container')).toBe(
+ '',
+ )
+
+ 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(
+ 'content
',
+ )
+
+ // 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('')
+ expect(await html('#container')).toBe(
+ '',
+ )
+ },
+ E2E_TIMEOUT,
+ )
+ })
+
describe('transition with v-show', () => {
test(
'named transition with v-show',
From 4cc9ca870c37b35d0600d3a6838baf935be5b7c2 Mon Sep 17 00:00:00 2001
From: Carlos Rodrigues
Date: Thu, 25 Apr 2024 09:04:03 +0100
Subject: [PATCH 5/8] types(defineComponent): support for GlobalComponents,
typed Directives and respect `expose` on defineComponent (#3399)
close #3367
---
.../componentTypeExtensions.test-d.tsx | 12 ++-
packages/dts-test/defineComponent.test-d.tsx | 90 +++++++++++++++++
packages/dts-test/directives.test-d.ts | 58 +++++++++++
.../runtime-core/src/apiDefineComponent.ts | 72 ++++++++++++--
packages/runtime-core/src/component.ts | 46 +++++++++
packages/runtime-core/src/componentOptions.ts | 99 ++++++++++++++++---
.../src/componentPublicInstance.ts | 44 +++++++--
packages/runtime-core/src/directives.ts | 83 +++++++++++-----
packages/runtime-core/src/index.ts | 2 +
.../runtime-core/types/globalComponents.d.ts | 11 +++
.../__tests__/directives/vOn.spec.ts | 2 +-
packages/runtime-dom/src/directives/vModel.ts | 18 +++-
packages/runtime-dom/src/directives/vOn.ts | 57 +++++++----
packages/runtime-dom/src/index.ts | 23 +++++
.../vue-compat/__tests__/instance.spec.ts | 2 +-
15 files changed, 533 insertions(+), 86 deletions(-)
create mode 100644 packages/dts-test/directives.test-d.ts
create mode 100644 packages/runtime-core/types/globalComponents.d.ts
diff --git a/packages/dts-test/componentTypeExtensions.test-d.tsx b/packages/dts-test/componentTypeExtensions.test-d.tsx
index 683641919..fe58c0a64 100644
--- a/packages/dts-test/componentTypeExtensions.test-d.tsx
+++ b/packages/dts-test/componentTypeExtensions.test-d.tsx
@@ -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(Custom.directives!.test)
+expectType>(Custom.components!.RouterView)
expectType()
expectType()
expectType()
diff --git a/packages/dts-test/defineComponent.test-d.tsx b/packages/dts-test/defineComponent.test-d.tsx
index 41646751b..44a00d4e5 100644
--- a/packages/dts-test/defineComponent.test-d.tsx
+++ b/packages/dts-test/defineComponent.test-d.tsx
@@ -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 => {}, [''])
;
})
+// #3367 expose components types
+describe('expose component types', () => {
+ const child = defineComponent({
+ props: {
+ a: String,
+ },
+ })
+
+ const parent = defineComponent({
+ components: {
+ child,
+ child2: {
+ template: ``,
+ },
+ },
+ })
+
+ expectType(parent.components!.child)
+ expectType(parent.components!.child2)
+
+ // global components
+ expectType>(
+ new parent.components!.KeepAlive().$props,
+ )
+ expectType>(new child.components!.KeepAlive().$props)
+
+ // runtime-dom components
+ expectType>(
+ new parent.components!.Transition().$props,
+ )
+ expectType>(
+ new child.components!.Transition().$props,
+ )
+})
+
+describe('directive typing', () => {
+ const customDirective: Directive = {
+ created(_) {},
+ }
+
+ const comp = defineComponent({
+ props: {
+ a: String,
+ },
+ directives: {
+ customDirective,
+ localDirective: {
+ created(_, { arg }) {
+ expectType(arg)
+ },
+ },
+ },
+ })
+
+ expectType(comp.directives!.customDirective)
+ expectType(comp.directives!.localDirective)
+
+ // global directive
+ expectType(comp.directives!.vShow)
+})
+
+describe('expose typing', () => {
+ const Comp = defineComponent({
+ expose: ['a', 'b'],
+ props: {
+ some: String,
+ },
+ data() {
+ return { a: 1, b: '2', c: 1 }
+ },
+ })
+
+ expectType>(Comp.expose!)
+
+ const vm = new Comp()
+ // internal should still be exposed
+ vm.$props
+
+ expectType(vm.a)
+ expectType(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
diff --git a/packages/dts-test/directives.test-d.ts b/packages/dts-test/directives.test-d.ts
new file mode 100644
index 000000000..5b87ebf71
--- /dev/null
+++ b/packages/dts-test/directives.test-d.ts
@@ -0,0 +1,58 @@
+import { type Directive, type ObjectDirective, vModelText } from 'vue'
+import { describe, expectType } from './utils'
+
+type ExtractBinding = 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>
+
+describe('vmodel', () => {
+ expectType>(
+ vModelText,
+ )
+ // @ts-expect-error
+ expectType>(vModelText)
+})
+
+describe('custom', () => {
+ expectType<{
+ value: number
+ oldValue: number | null
+ arg?: 'Arg'
+ modifiers: Record<'a' | 'b', boolean>
+ }>(testDirective())
+
+ expectType<{
+ value: number
+ oldValue: number | null
+ arg?: 'Arg'
+ modifiers: Record<'a' | 'b', boolean>
+ // @ts-expect-error
+ }>(testDirective())
+
+ expectType<{
+ value: number
+ oldValue: number | null
+ arg?: 'Arg'
+ modifiers: Record<'a' | 'b', boolean>
+ // @ts-expect-error
+ }>(testDirective())
+
+ expectType<{
+ value: number
+ oldValue: number | null
+ arg?: 'Arg'
+ modifiers: Record<'a' | 'b', boolean>
+ // @ts-expect-error
+ }>(testDirective())
+})
diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts
index 8c0492e1c..46e1d59a8 100644
--- a/packages/runtime-core/src/apiDefineComponent.ts
+++ b/packages/runtime-core/src/apiDefineComponent.ts
@@ -6,13 +6,17 @@ import type {
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps,
ComponentOptionsWithoutProps,
+ ComponentProvideOptions,
ComputedOptions,
MethodOptions,
RenderFunction,
} from './componentOptions'
import type {
AllowedComponentProps,
+ Component,
ComponentCustomProps,
+ GlobalComponents,
+ GlobalDirectives,
SetupContext,
} from './component'
import type {
@@ -29,6 +33,7 @@ import type {
CreateComponentPublicInstance,
} from './componentPublicInstance'
import type { SlotsType } from './componentSlots'
+import type { Directive } from './directives'
export type PublicProps = VNodeProps &
AllowedComponentProps &
@@ -55,6 +60,10 @@ export type DefineComponent<
Props = ResolveProps,
Defaults = ExtractDefaultPropTypes,
S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
@@ -69,7 +78,10 @@ export type DefineComponent<
Defaults,
true,
{},
- S
+ S,
+ LC & GlobalComponents,
+ Directives & GlobalDirectives,
+ Exposed
>
> &
ComponentOptionsBase<
@@ -85,7 +97,11 @@ export type DefineComponent<
Defaults,
{},
string,
- S
+ S,
+ LC & GlobalComponents,
+ Directives & GlobalDirectives,
+ Exposed,
+ Provide
> &
PP
@@ -166,9 +182,13 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
- S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
+ S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
>(
options: ComponentOptionsWithoutProps<
Props,
@@ -182,7 +202,11 @@ export function defineComponent<
EE,
I,
II,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
>,
): DefineComponent<
Props,
@@ -197,7 +221,11 @@ export function defineComponent<
PublicProps,
ResolveProps,
ExtractDefaultPropTypes,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
>
// overload 3: object format with array props declaration
@@ -216,6 +244,10 @@ export function defineComponent<
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Readonly<{ [key in PropNames]?: any }>,
>(
options: ComponentOptionsWithArrayProps<
@@ -230,7 +262,11 @@ export function defineComponent<
EE,
I,
II,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
>,
): DefineComponent<
Props,
@@ -245,7 +281,11 @@ export function defineComponent<
PublicProps,
ResolveProps,
ExtractDefaultPropTypes,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
>
// overload 4: object format with object props declaration
@@ -262,9 +302,13 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
- S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
+ S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
@@ -278,7 +322,11 @@ export function defineComponent<
EE,
I,
II,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
>,
): DefineComponent<
PropsOptions,
@@ -293,7 +341,11 @@ export function defineComponent<
PublicProps,
ResolveProps,
ExtractDefaultPropTypes,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
>
// implementation, close to no-op
diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts
index b8466923a..68d908310 100644
--- a/packages/runtime-core/src/component.ts
+++ b/packages/runtime-core/src/component.ts
@@ -86,6 +86,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'
+
export type Data = Record
/**
@@ -126,6 +133,45 @@ export type ComponentInstance = 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 {}
+
+/**
+ * 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 {
+ Teleport: DefineComponent
+ Suspense: DefineComponent
+ KeepAlive: DefineComponent
+ BaseTransition: DefineComponent
+}
+
/**
* Default allowed non-declared props on component in TSX
*/
diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts
index 65b952b0b..da1dfcec0 100644
--- a/packages/runtime-core/src/componentOptions.ts
+++ b/packages/runtime-core/src/componentOptions.ts
@@ -112,7 +112,11 @@ export interface ComponentOptionsBase<
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
-> extends LegacyOptions,
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+> extends LegacyOptions,
ComponentInternalOptions,
ComponentCustomOptions {
setup?: (
@@ -136,13 +140,16 @@ export interface ComponentOptionsBase<
// Luckily `render()` doesn't need any arguments nor does it care about return
// type.
render?: Function
- components?: Record
- directives?: Record
+ // NOTE: extending both LC and Record allows objects to be forced
+ // to be of type Component, while still inferring LC generic
+ components?: LC & Record
+ // NOTE: extending both Directives and Record allows objects to be forced
+ // to be of type Directive, while still inferring Directives generic
+ directives?: Directives & Record
inheritAttrs?: boolean
emits?: (E | EE[]) & ThisType
slots?: S
- // TODO infer public instance type based on exposed keys
- expose?: string[]
+ expose?: Exposed[]
serverPrefetch?(): void | Promise
// Runtime compiler only -----------------------------------------------------
@@ -224,6 +231,10 @@ export type ComponentOptionsWithoutProps<
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
PE = Props & EmitsToProps,
> = ComponentOptionsBase<
PE,
@@ -238,7 +249,11 @@ export type ComponentOptionsWithoutProps<
{},
I,
II,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
> & {
props?: undefined
} & ThisType<
@@ -255,7 +270,10 @@ export type ComponentOptionsWithoutProps<
{},
false,
I,
- S
+ S,
+ LC,
+ Directives,
+ Exposed
>
>
@@ -272,6 +290,10 @@ export type ComponentOptionsWithArrayProps<
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify>>,
> = ComponentOptionsBase<
Props,
@@ -286,7 +308,11 @@ export type ComponentOptionsWithArrayProps<
{},
I,
II,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
> & {
props: PropNames[]
} & ThisType<
@@ -303,7 +329,10 @@ export type ComponentOptionsWithArrayProps<
{},
false,
I,
- S
+ S,
+ LC,
+ Directives,
+ Exposed
>
>
@@ -320,6 +349,10 @@ export type ComponentOptionsWithObjectProps<
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify & EmitsToProps>>,
Defaults = ExtractDefaultPropTypes,
> = ComponentOptionsBase<
@@ -335,7 +368,11 @@ export type ComponentOptionsWithObjectProps<
Defaults,
I,
II,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
> & {
props: PropsOptions & ThisType
} & ThisType<
@@ -352,7 +389,9 @@ export type ComponentOptionsWithObjectProps<
Defaults,
false,
I,
- S
+ S,
+ LC,
+ Directives
>
>
@@ -365,7 +404,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 = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentOptionsBase<
Props,
RawBindings,
@@ -375,8 +422,15 @@ export type ComponentOptions<
Mixin,
Extends,
E,
- string,
- S
+ EE,
+ Defaults,
+ I,
+ II,
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
> &
ThisType<
CreateComponentPublicInstance<
@@ -388,7 +442,13 @@ export type ComponentOptions<
Mixin,
Extends,
E,
- Readonly
+ Readonly,
+ Defaults,
+ false,
+ I,
+ S,
+ LC,
+ Directives
>
>
@@ -403,6 +463,12 @@ export type ComponentOptionsMixin = ComponentOptionsBase<
any,
any,
any,
+ any,
+ any,
+ any,
+ any,
+ any,
+ any,
any
>
@@ -464,6 +530,7 @@ interface LegacyOptions<
Extends extends ComponentOptionsMixin,
I extends ComponentInjectOptions,
II extends string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
> {
compatConfig?: CompatConfig
@@ -497,7 +564,7 @@ interface LegacyOptions<
computed?: C
methods?: M
watch?: ComponentWatchOptions
- provide?: ComponentProvideOptions
+ provide?: Provide
inject?: I | II[]
// assets
diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts
index deb9ff69b..864b9786e 100644
--- a/packages/runtime-core/src/componentPublicInstance.ts
+++ b/packages/runtime-core/src/componentPublicInstance.ts
@@ -1,4 +1,5 @@
import {
+ type Component,
type ComponentInternalInstance,
type Data,
getExposeProxy,
@@ -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 =
infer Defaults,
any,
any,
+ any,
+ any,
+ any,
+ any,
any
>
? OptionTypesType &
@@ -157,6 +164,9 @@ export type CreateComponentPublicInstance<
MakeDefaultsOptional extends boolean = false,
I extends ComponentInjectOptions = {},
S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
PublicMixin = IntersectionMixin & IntersectionMixin,
PublicP = UnwrapMixinsType & EnsureNonVoid,
PublicB = UnwrapMixinsType & EnsureNonVoid,
@@ -167,6 +177,7 @@ export type CreateComponentPublicInstance<
EnsureNonVoid,
PublicDefaults = UnwrapMixinsType &
EnsureNonVoid,
+ 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
+
// 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,
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>> &
- ShallowUnwrapRef &
- UnwrapNestedRefs &
- ExtractComputedReturns &
- M &
- ComponentCustomProperties &
- InjectToObject
+} & ExposedKeys<
+ IfAny>> &
+ ShallowUnwrapRef &
+ UnwrapNestedRefs &
+ ExtractComputedReturns &
+ M &
+ ComponentCustomProperties &
+ InjectToObject,
+ Exposed
+>
export type PublicPropertiesMap = Record<
string,
diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts
index daaf28b15..c6dce57c1 100644
--- a/packages/runtime-core/src/directives.ts
+++ b/packages/runtime-core/src/directives.ts
@@ -26,19 +26,29 @@ import { mapCompatDirectiveHook } from './compat/customDirective'
import { pauseTracking, resetTracking } from '@vue/reactivity'
import { traverse } from './apiWatch'
-export interface DirectiveBinding {
+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
+ value: Value
+ oldValue: Value | null
+ arg?: Arg
+ modifiers: DirectiveModifiers
+ dir: ObjectDirective
}
-export type DirectiveHook | null, V = any> = (
- el: T,
- binding: DirectiveBinding,
- vnode: VNode,
+export type DirectiveHook<
+ HostElement = any,
+ Prev = VNode | null,
+ Value = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> = (
+ el: HostElement,
+ binding: DirectiveBinding,
+ vnode: VNode,
prevVNode: Prev,
) => void
@@ -47,25 +57,52 @@ export type SSRDirectiveHook = (
vnode: VNode,
) => Data | undefined
-export interface ObjectDirective {
- created?: DirectiveHook
- beforeMount?: DirectiveHook
- mounted?: DirectiveHook
- beforeUpdate?: DirectiveHook, V>
- updated?: DirectiveHook, V>
- beforeUnmount?: DirectiveHook
- unmounted?: DirectiveHook
+export interface ObjectDirective<
+ HostElement = any,
+ Value = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> {
+ created?: DirectiveHook
+ beforeMount?: DirectiveHook
+ mounted?: DirectiveHook
+ beforeUpdate?: DirectiveHook<
+ HostElement,
+ VNode,
+ Value,
+ Modifiers,
+ Arg
+ >
+ updated?: DirectiveHook<
+ HostElement,
+ VNode,
+ Value,
+ Modifiers,
+ Arg
+ >
+ beforeUnmount?: DirectiveHook
+ unmounted?: DirectiveHook
getSSRProps?: SSRDirectiveHook
deep?: boolean
}
-export type FunctionDirective = DirectiveHook
+export type FunctionDirective<
+ HostElement = any,
+ V = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> = DirectiveHook
-export type Directive =
- | ObjectDirective
- | FunctionDirective
+export type Directive<
+ HostElement = any,
+ Value = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> =
+ | ObjectDirective
+ | FunctionDirective
-export type DirectiveModifiers = Record
+export type DirectiveModifiers = Record
export function validateDirectiveName(name: string) {
if (isBuiltInDirective(name)) {
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index 94b298504..5d36407ba 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -248,6 +248,8 @@ export type {
SetupContext,
ComponentCustomProps,
AllowedComponentProps,
+ GlobalComponents,
+ GlobalDirectives,
ComponentInstance,
} from './component'
export type {
diff --git a/packages/runtime-core/types/globalComponents.d.ts b/packages/runtime-core/types/globalComponents.d.ts
new file mode 100644
index 000000000..a4abd6d1f
--- /dev/null
+++ b/packages/runtime-core/types/globalComponents.d.ts
@@ -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
+ Suspense: DefineComponent
+ KeepAlive: DefineComponent
+ BaseTransition: DefineComponent
+ }
+}
diff --git a/packages/runtime-dom/__tests__/directives/vOn.spec.ts b/packages/runtime-dom/__tests__/directives/vOn.spec.ts
index 03620f747..ef7ee346b 100644
--- a/packages/runtime-dom/__tests__/directives/vOn.spec.ts
+++ b/packages/runtime-dom/__tests__/directives/vOn.spec.ts
@@ -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')
diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts
index b0ea41728..cb599656f 100644
--- a/packages/runtime-dom/src/directives/vModel.ts
+++ b/packages/runtime-dom/src/directives/vModel.ts
@@ -39,14 +39,17 @@ function onCompositionEnd(e: Event) {
const assignKey = Symbol('_assign')
-type ModelDirective = ObjectDirective<
- T & { [assignKey]: AssignerFn; _assigning?: boolean }
+type ModelDirective = 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 = {
},
}
-export const vModelSelect: ModelDirective = {
+export const vModelSelect: ModelDirective = {
//