diff --git a/packages/compiler-core/__tests__/transforms/vBind.spec.ts b/packages/compiler-core/__tests__/transforms/vBind.spec.ts
index 84b9ee8ca..be063b8a9 100644
--- a/packages/compiler-core/__tests__/transforms/vBind.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vBind.spec.ts
@@ -408,4 +408,22 @@ describe('compiler: transform v-bind', () => {
},
})
})
+
+ test('error on invalid argument for same-name shorthand', () => {
+ const onError = vi.fn()
+ parseWithVBind(`
`, { onError })
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
+ loc: {
+ start: {
+ line: 1,
+ column: 13,
+ },
+ end: {
+ line: 1,
+ column: 18,
+ },
+ },
+ })
+ })
})
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index 6728a80d4..cd6a443c5 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -78,6 +78,7 @@ export enum ErrorCodes {
X_V_FOR_MALFORMED_EXPRESSION,
X_V_FOR_TEMPLATE_KEY_PLACEMENT,
X_V_BIND_NO_EXPRESSION,
+ X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
X_V_ON_NO_EXPRESSION,
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
X_V_SLOT_MIXED_SLOT_USAGE,
@@ -156,6 +157,7 @@ export const errorMessages: Record = {
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: ` key should be placed on the tag.`,
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
+ [ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT]: `v-bind with same-name shorthand only allows static argument.`,
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on outlet.`,
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
diff --git a/packages/compiler-core/src/transforms/vBind.ts b/packages/compiler-core/src/transforms/vBind.ts
index d12177549..234cf1fbc 100644
--- a/packages/compiler-core/src/transforms/vBind.ts
+++ b/packages/compiler-core/src/transforms/vBind.ts
@@ -2,6 +2,7 @@ import type { DirectiveTransform } from '../transform'
import {
type ExpressionNode,
NodeTypes,
+ type SimpleExpressionNode,
createObjectProperty,
createSimpleExpression,
} from '../ast'
@@ -17,10 +18,45 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
const { modifiers, loc } = dir
const arg = dir.arg!
- // :arg is replaced by :arg="arg"
let { exp } = dir
- if (!exp && arg.type === NodeTypes.SIMPLE_EXPRESSION) {
- const propName = camelize(arg.content)
+
+ // handle empty expression
+ if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) {
+ if (!__BROWSER__) {
+ // #10280 only error against empty expression in non-browser build
+ // because :foo in in-DOM templates will be parsed into :foo="" by the
+ // browser
+ context.onError(
+ createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
+ )
+ return {
+ props: [
+ createObjectProperty(arg, createSimpleExpression('', true, loc)),
+ ],
+ }
+ } else {
+ exp = undefined
+ }
+ }
+
+ // same-name shorthand - :arg is expanded to :arg="arg"
+ if (!exp) {
+ if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
+ // only simple expression is allowed for same-name shorthand
+ context.onError(
+ createCompilerError(
+ ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
+ arg.loc,
+ ),
+ )
+ return {
+ props: [
+ createObjectProperty(arg, createSimpleExpression('', true, loc)),
+ ],
+ }
+ }
+
+ const propName = camelize((arg as SimpleExpressionNode).content)
exp = dir.exp = createSimpleExpression(propName, false, arg.loc)
if (!__BROWSER__) {
exp = dir.exp = processExpression(exp, context)
@@ -57,16 +93,6 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
}
}
- if (
- !exp ||
- (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
- ) {
- context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
- return {
- props: [createObjectProperty(arg, createSimpleExpression('', true, loc))],
- }
- }
-
return {
props: [createObjectProperty(arg, exp)],
}
diff --git a/packages/runtime-core/__tests__/apiCreateApp.spec.ts b/packages/runtime-core/__tests__/apiCreateApp.spec.ts
index f8dcd6aee..05e51e43b 100644
--- a/packages/runtime-core/__tests__/apiCreateApp.spec.ts
+++ b/packages/runtime-core/__tests__/apiCreateApp.spec.ts
@@ -123,6 +123,13 @@ describe('api: createApp', () => {
expect(app.runWithContext(() => inject('foo'))).toBe(1)
+ expect(
+ app.runWithContext(() => {
+ app.runWithContext(() => {})
+ return inject('foo')
+ }),
+ ).toBe(1)
+
// ensure the context is restored
inject('foo')
expect('inject() can only be used inside setup').toHaveBeenWarned()
diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts
index 7da2bfb13..f4754fdb7 100644
--- a/packages/runtime-core/src/apiCreateApp.ts
+++ b/packages/runtime-core/src/apiCreateApp.ts
@@ -386,11 +386,12 @@ export function createAppAPI(
},
runWithContext(fn) {
+ const lastApp = currentApp
currentApp = app
try {
return fn()
} finally {
- currentApp = null
+ currentApp = lastApp
}
},
})
diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts
index 7f991bc9f..b652edeac 100644
--- a/packages/runtime-core/src/rendererTemplateRef.ts
+++ b/packages/runtime-core/src/rendererTemplateRef.ts
@@ -81,10 +81,9 @@ export function setRef(
} else {
const _isString = isString(ref)
const _isRef = isRef(ref)
- const isVFor = rawRef.f
if (_isString || _isRef) {
const doSet = () => {
- if (isVFor) {
+ if (rawRef.f) {
const existing = _isString
? hasOwn(setupState, ref)
? setupState[ref]
@@ -119,15 +118,14 @@ export function setRef(
warn('Invalid template ref type:', ref, `(${typeof ref})`)
}
}
- // #9908 ref on v-for mutates the same array for both mount and unmount
- // and should be done together
- if (isUnmount || isVFor) {
- doSet()
- } else {
- // #1789: set new refs in a post job so that they don't get overwritten
- // by unmounting ones.
+ if (value) {
+ // #1789: for non-null values, set them after render
+ // null values means this is unmount and it should not overwrite another
+ // ref with the same key
;(doSet as SchedulerJob).id = -1
queuePostRenderEffect(doSet, parentSuspense)
+ } else {
+ doSet()
}
} else if (__DEV__) {
warn('Invalid template ref type:', ref, `(${typeof ref})`)
diff --git a/packages/runtime-dom/__tests__/directives/vShow.spec.ts b/packages/runtime-dom/__tests__/directives/vShow.spec.ts
index 70b69f2df..0c299b51b 100644
--- a/packages/runtime-dom/__tests__/directives/vShow.spec.ts
+++ b/packages/runtime-dom/__tests__/directives/vShow.spec.ts
@@ -211,4 +211,69 @@ describe('runtime-dom: v-show directive', () => {
await nextTick()
expect($div.style.display).toEqual('')
})
+
+ // #10151
+ test('should respect the display value when v-show value is true', async () => {
+ const isVisible = ref(false)
+ const useDisplayStyle = ref(true)
+ const compStyle = ref({
+ display: 'none',
+ })
+ const withoutDisplayStyle = {
+ margin: '10px',
+ }
+
+ const Component = {
+ setup() {
+ return () => {
+ return withVShow(
+ h('div', {
+ style: useDisplayStyle.value
+ ? compStyle.value
+ : withoutDisplayStyle,
+ }),
+ isVisible.value,
+ )
+ }
+ },
+ }
+ render(h(Component), root)
+
+ const $div = root.children[0]
+
+ expect($div.style.display).toEqual('none')
+
+ isVisible.value = true
+ await nextTick()
+ expect($div.style.display).toEqual('none')
+
+ compStyle.value.display = 'block'
+ await nextTick()
+ expect($div.style.display).toEqual('block')
+
+ compStyle.value.display = 'inline-block'
+ await nextTick()
+ expect($div.style.display).toEqual('inline-block')
+
+ isVisible.value = false
+ await nextTick()
+ expect($div.style.display).toEqual('none')
+
+ isVisible.value = true
+ await nextTick()
+ expect($div.style.display).toEqual('inline-block')
+
+ useDisplayStyle.value = false
+ await nextTick()
+ expect($div.style.display).toEqual('')
+ expect(getComputedStyle($div).display).toEqual('block')
+
+ isVisible.value = false
+ await nextTick()
+ expect($div.style.display).toEqual('none')
+
+ isVisible.value = true
+ await nextTick()
+ expect($div.style.display).toEqual('')
+ })
})
diff --git a/packages/runtime-dom/src/directives/vShow.ts b/packages/runtime-dom/src/directives/vShow.ts
index 2ab25136e..d8aab92e7 100644
--- a/packages/runtime-dom/src/directives/vShow.ts
+++ b/packages/runtime-dom/src/directives/vShow.ts
@@ -22,7 +22,7 @@ export const vShow: ObjectDirective & { name?: 'show' } = {
}
},
updated(el, { value, oldValue }, { transition }) {
- if (!value === !oldValue) return
+ if (!value === !oldValue && el.style.display === el[vShowOldKey]) return
if (transition) {
if (value) {
transition.beforeEnter(el)
diff --git a/packages/runtime-dom/src/modules/style.ts b/packages/runtime-dom/src/modules/style.ts
index 6341c8a12..ef2c55dbb 100644
--- a/packages/runtime-dom/src/modules/style.ts
+++ b/packages/runtime-dom/src/modules/style.ts
@@ -38,6 +38,7 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
// so we always keep the current `display` value regardless of the `style`
// value, thus handing over control to `v-show`.
if (vShowOldKey in el) {
+ el[vShowOldKey] = style.display
style.display = currentDisplay
}
}