diff --git a/CHANGELOG.md b/CHANGELOG.md index fe351268a..032a5f3a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 9ae397813..6861dc2b1 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -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", 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/package.json b/packages/compiler-dom/package.json index 3cc86ddd0..d82b11093 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -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", 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 01d3eebe9..9c5489900 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) + } +} diff --git a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts index c139a3d7b..d5374ae89 100644 --- a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts @@ -597,11 +597,29 @@ const props = defineProps({ foo: String }) foo: Foo }>() `, + { + propsDestructure: false, + }, ) expect(content).toMatch(`const { foo } = __props`) assertCode(content) }) + test('prohibiting reactive destructure', () => { + expect(() => + compile( + ``, + { + propsDestructure: 'error', + }, + ), + ).toThrow() + }) + describe('errors', () => { test('w/ both type and non-type args', () => { expect(() => { diff --git a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts index 3843ef921..20f2c432d 100644 --- a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts @@ -6,7 +6,6 @@ describe('sfc reactive props destructure', () => { function compile(src: string, options?: Partial) { return compileSFCScript(src, { inlineTemplate: true, - propsDestructure: true, ...options, }) } diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index 273aa3116..cb574181f 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -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", diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index d1794484a..989e1cf2b 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -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 diff --git a/packages/compiler-sfc/src/script/definePropsDestructure.ts b/packages/compiler-sfc/src/script/definePropsDestructure.ts index e4a59aca7..34bc7a428 100644 --- a/packages/compiler-sfc/src/script/definePropsDestructure.ts +++ b/packages/compiler-sfc/src/script/definePropsDestructure.ts @@ -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, ) { - if (!ctx.options.propsDestructure) { + if (ctx.options.propsDestructure === false) { return } diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index 637af44b6..377ed64f5 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -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", 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..077f1abc0 100644 --- a/packages/dts-test/defineComponent.test-d.tsx +++ b/packages/dts-test/defineComponent.test-d.tsx @@ -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 => {}, ['']) ; }) +// #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 @@ -1533,3 +1623,146 @@ declare const MyButton: DefineComponent< {} > ; + +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 + ; + // @ts-expect-error + ; + ; + + 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) + }, + }) + + ; id.toFixed(2)} /> + ; id.toUpperCase()} /> + // @ts-expect-error + ; id.slice(1)} /> + // @ts-expect-error + ; 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) + }, + }) + + ; id.toFixed(2)} /> + ; id.toUpperCase()} /> + // @ts-expect-error + ; id.slice(1)} /> + // @ts-expect-error + ; 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>(false) + expectType(props.foo) + }, +}) 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/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/package.json b/packages/reactivity/package.json index a2540725b..2dda9fb4c 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -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", 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 } 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) { diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index 3dc61d024..66f99cd94 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -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", diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 8c0492e1c..7fce96586 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -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, Defaults = ExtractDefaultPropTypes, S extends SlotsType = {}, + LC extends Record = {}, + Directives extends Record = {}, + 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 -// 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 = {}, + Directives extends Record = {}, + Exposed extends string = string, + Provide extends ComponentProvideOptions = ComponentProvideOptions, + // resolved types + ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions + ? TypeEmitsToOptions + : RuntimeEmitsOptions, + InferredProps = unknown extends TypeProps + ? string extends RuntimePropsKeys + ? ComponentObjectPropsOptions extends RuntimePropsOptions + ? {} + : ExtractPropTypes + : { [key in RuntimePropsKeys]?: any } + : TypeProps, + ResolvedProps = Readonly>, >( - options: ComponentOptionsWithoutProps< - Props, - RawBindings, - D, - C, - M, + options: { + props?: (RuntimePropsOptions & ThisType) | 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 - >, + RuntimeEmitsOptions, + RuntimeEmitsKeys, + {}, // Defaults + InjectOptions, + InjectKeys, + Slots, + LocalComponents, + Directives, + Exposed, + Provide + > & + ThisType< + CreateComponentPublicInstance< + ResolvedProps, + SetupBindings, + Data, + Computed, + Methods, + Mixin, + Extends, + ResolvedEmits, + RuntimeEmitsKeys, + {}, + false, + InjectOptions, + Slots, + LocalComponents, + Directives, + Exposed + > + >, ): DefineComponent< - Props, - RawBindings, - D, - C, - M, + InferredProps, + SetupBindings, + Data, + Computed, + Methods, Mixin, Extends, - E, - EE, + ResolvedEmits, + RuntimeEmitsKeys, PublicProps, - ResolveProps, - ExtractDefaultPropTypes, - S -> - -// 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, - Mixin, - Extends, - E, - EE, - PublicProps, - ResolveProps, - ExtractDefaultPropTypes, - 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, - 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, - ExtractDefaultPropTypes, - S + ResolvedProps, + ExtractDefaultPropTypes, + 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 diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts index 382bb30b3..dbe27dde4 100644 --- a/packages/runtime-core/src/apiSetupHelpers.ts +++ b/packages/runtime-core/src/apiSetupHelpers.ts @@ -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( export function defineEmits( emitOptions: E, ): EmitFn -export function defineEmits< - T extends ((...args: any[]) => any) | Record, ->(): T extends (...args: any[]) => any ? T : ShortEmits +export function defineEmits(): T extends ( + ...args: any[] +) => any + ? T + : ShortEmits // implementation export function defineEmits() { if (__DEV__) { @@ -146,6 +148,10 @@ export function defineEmits() { return null as any } +export type ComponentTypeEmits = + | ((...args: any[]) => any) + | Record + type RecordToUnion> = T[keyof T] type ShortEmits> = 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`) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 1011e1756..a60434ad4 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -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 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/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index 4551235bc..eb80d02af 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -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,23 +38,41 @@ export type ObjectEmitsOptions = Record< export type EmitsOptions = ObjectEmitsOptions | string[] -export type EmitsToProps = T extends string[] - ? { - [K in `on${Capitalize}`]?: (...args: any[]) => any - } - : T extends ObjectEmitsOptions +export type EmitsToProps = + T extends string[] ? { - [K in `on${Capitalize}`]?: K extends `on${infer C}` - ? ( - ...args: T[Uncapitalize] extends (...args: infer P) => any - ? P - : T[Uncapitalize] extends null - ? any[] - : never - ) => any - : never + [K in `on${Capitalize}`]?: (...args: any[]) => any } - : {} + : T extends ObjectEmitsOptions + ? { + [K in `on${Capitalize}`]?: K extends `on${infer C}` + ? ( + ...args: T[Uncapitalize] extends (...args: infer P) => any + ? P + : T[Uncapitalize] extends null + ? any[] + : never + ) => any + : never + } + : {} + +export type TypeEmitsToOptions = + T extends Record + ? { + [K in keyof T]: T[K] extends [...args: infer Args] + ? (...args: Args) => any + : () => any + } + : T extends (...args: any[]) => any + ? ParametersToFns> + : {} + +type ParametersToFns = { + [K in T[0]]: K extends `${infer C}` + ? (...args: T extends [C, ...infer Args] ? Args : never) => any + : never +} export type ShortEmitsToObject = E extends Record diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 1017f8b64..786e09d4d 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -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, + LC extends Record = {}, + Directives extends Record = {}, + Exposed extends string = string, + Provide extends ComponentProvideOptions = ComponentProvideOptions, +> extends LegacyOptions, 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 - 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 ----------------------------------------------------- @@ -212,117 +226,24 @@ export interface RuntimeCompilerOptions { delimiters?: [string, string] } -export type ComponentOptionsWithoutProps< +export type ComponentOptions< Props = {}, - RawBindings = {}, - D = {}, - C extends ComputedOptions = {}, - M extends MethodOptions = {}, - Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, - Extends extends ComponentOptionsMixin = ComponentOptionsMixin, - E extends EmitsOptions = EmitsOptions, + RawBindings = any, + D = any, + C extends ComputedOptions = any, + M extends MethodOptions = any, + Mixin extends ComponentOptionsMixin = any, + Extends extends ComponentOptionsMixin = any, + E extends EmitsOptions = any, EE extends string = string, + Defaults = {}, I extends ComponentInjectOptions = {}, II extends string = string, S extends SlotsType = {}, - PE = Props & EmitsToProps, -> = 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>>, -> = 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 & EmitsToProps>>, - Defaults = ExtractDefaultPropTypes, + LC extends Record = {}, + Directives extends Record = {}, + Exposed extends string = string, + Provide extends ComponentProvideOptions = ComponentProvideOptions, > = ComponentOptionsBase< Props, RawBindings, @@ -336,48 +257,11 @@ export type ComponentOptionsWithObjectProps< Defaults, I, II, - S -> & { - props: PropsOptions & ThisType -} & ThisType< - CreateComponentPublicInstance< - Props, - RawBindings, - D, - C, - M, - Mixin, - Extends, - E, - Props, - Defaults, - false, - I, - S - > - > - -export type ComponentOptions< - Props = {}, - RawBindings = any, - D = any, - C extends ComputedOptions = any, - M extends MethodOptions = any, - Mixin extends ComponentOptionsMixin = any, - Extends extends ComponentOptionsMixin = any, - E extends EmitsOptions = any, - S extends SlotsType = any, -> = ComponentOptionsBase< - Props, - RawBindings, - D, - C, - M, - Mixin, - Extends, - E, - string, - S + S, + LC, + Directives, + Exposed, + Provide > & ThisType< CreateComponentPublicInstance< @@ -389,7 +273,13 @@ export type ComponentOptions< Mixin, Extends, E, - Readonly + Readonly, + 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 = {}, + Directives extends Record = {}, + Exposed extends string = string, + Provide extends ComponentProvideOptions = ComponentProvideOptions, + TE extends ComponentTypeEmits = {}, + ResolvedEmits extends EmitsOptions = {} extends E + ? TypeEmitsToOptions + : E, + PE = Props & EmitsToProps, +> = 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 = {}, + Directives extends Record = {}, + Exposed extends string = string, + Provide extends ComponentProvideOptions = ComponentProvideOptions, + Props = Prettify>>, +> = 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 = {}, + Directives extends Record = {}, + Exposed extends string = string, + Provide extends ComponentProvideOptions = ComponentProvideOptions, + Props = Prettify & EmitsToProps>>, + Defaults = ExtractDefaultPropTypes, +> = ComponentOptionsBase< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE, + Defaults, + I, + II, + S, + LC, + Directives, + Exposed, + Provide +> & { + props: PropsOptions & ThisType +} & ThisType< + CreateComponentPublicInstance< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + Props, + Defaults, + false, + I, + S, + LC, + Directives + > + > diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 74f77db0b..7bd0feb7f 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -67,7 +67,7 @@ export interface PropOptions { skipFactory?: boolean } -export type PropType = PropConstructor | PropConstructor[] +export type PropType = PropConstructor | (PropConstructor | null)[] type PropConstructor = | { new (...args: any[]): T & {} } @@ -107,8 +107,10 @@ type DefaultKeys = { : never }[keyof T] -type InferPropType = [T] extends [null] - ? any // null & true would fail to infer +type InferPropType = [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] extends [null] ? Date : [T] extends [(infer U)[] | { type: (infer U)[] }] ? U extends DateConstructor - ? Date | InferPropType - : InferPropType + ? Date | InferPropType + : InferPropType : [T] extends [Prop] ? unknown extends V ? IfAny @@ -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): string { +function getType(ctor: Prop | null): string { // Early return for null to avoid unnecessary computations if (ctor === null) { return 'null' @@ -614,7 +616,7 @@ function getType(ctor: Prop): string { return '' } -function isSameType(a: Prop, b: Prop): boolean { +function isSameType(a: Prop | null, b: Prop | 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, diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index 3266299c5..538f6279a 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -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 = 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/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 86bc0b53a..ff2dcb7c9 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -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 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( - ' 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) @@ -193,7 +174,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) } @@ -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( + ' 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 { @@ -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 diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index 475c495e9..3d130bde4 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -26,19 +26,29 @@ import type { ComponentPublicInstance } from './componentPublicInstance' import { mapCompatDirectiveHook } from './compat/customDirective' import { pauseTracking, resetTracking, traverse } from '@vue/reactivity' -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 d314cdbc2..e9347faa9 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -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, 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__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index fb746f72c..cc56de2d6 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -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)), ] }, 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/package.json b/packages/runtime-dom/package.json index 0b9f25d06..bf567a8ef 100644 --- a/packages/runtime-dom/package.json +++ b/packages/runtime-dom/package.json @@ -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", diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 01ce2bad4..017284662 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -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( ) => RawBindings | RenderFunction, ): VueElementConstructor -// 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 = {}, + Directives extends Record = {}, + Exposed extends string = string, + Provide extends ComponentProvideOptions = ComponentProvideOptions, + // resolved types + InferredProps = string extends PropsKeys + ? ComponentObjectPropsOptions extends RuntimePropsOptions + ? {} + : ExtractPropTypes + : { [key in PropsKeys]?: any }, + ResolvedProps = InferredProps & EmitsToProps, >( - options: ComponentOptionsWithoutProps< - Props, - RawBindings, - D, - C, - M, + options: { + props?: (RuntimePropsOptions & ThisType) | PropsKeys[] + } & ComponentOptionsBase< + ResolvedProps, + SetupBindings, + Data, + Computed, + Methods, Mixin, Extends, - E, - EE, - I, - II, - S - > & { styles?: string[] }, -): VueElementConstructor - -// 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, - EE extends string = string, - I extends ComponentInjectOptions = {}, - II extends string = string, - S extends SlotsType = {}, ->( - options: ComponentOptionsWithArrayProps< - PropNames, - RawBindings, - D, - C, - M, - 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, - RawBindings, - D, - C extends ComputedOptions = {}, - M extends MethodOptions = {}, - Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, - Extends extends ComponentOptionsMixin = ComponentOptionsMixin, - E extends EmitsOptions = Record, - 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> + RuntimeEmitsOptions, + EmitsKeys, + {}, // Defaults + InjectOptions, + InjectKeys, + Slots, + LocalComponents, + Directives, + Exposed, + Provide + > & + ThisType< + CreateComponentPublicInstance< + Readonly, + SetupBindings, + Data, + Computed, + Methods, + Mixin, + Extends, + RuntimeEmitsOptions, + EmitsKeys, + {}, + false, + InjectOptions, + Slots, + LocalComponents, + Directives, + Exposed + > + >, +): VueElementConstructor // overload 5: defining a custom element from the returned value of // `defineComponent` 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 = { //