提交 52c12fce 编写于 作者: fxy060608's avatar fxy060608

fix(mp): v-model with modifiers (#3381)

上级 bc78cc6d
......@@ -22,6 +22,39 @@ describe('compiler: transform v-model', () => {
}
)
})
test(`component v-model with number`, () => {
assert(
`<Comp v-model.number="model" />`,
`<comp u-i="2a9ec0b0-0" bindupdateModelValue="{{a}}" u-p="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _o(_m($event => _ctx.model = $event, { number: true }, true)), b: _p({ modelValue: _ctx.model }) }
}`,
{
cacheHandlers: true,
}
)
})
test(`component v-model with trim`, () => {
assert(
`<Comp v-model.trim="model" />`,
`<comp u-i="2a9ec0b0-0" bindupdateModelValue="{{a}}" u-p="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _o(_m($event => _ctx.model = $event, { trim: true }, true)), b: _p({ modelValue: _ctx.model }) }
}`,
{
cacheHandlers: true,
}
)
})
test(`component v-model with number and trim`, () => {
assert(
`<Comp v-model.trim.number="model" />`,
`<comp u-i="2a9ec0b0-0" bindupdateModelValue="{{a}}" u-p="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _o(_m($event => _ctx.model = $event, { trim: true, number: true }, true)), b: _p({ modelValue: _ctx.model }) }
}`
)
})
test(`input,textarea v-model`, () => {
assert(
`<input v-model="model" />`,
......@@ -44,6 +77,33 @@ describe('compiler: transform v-model', () => {
`<input bindinput="{{a}}" value="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _o([$event => _ctx.model = $event.detail.value, _ctx.input]), b: _ctx.model }
}`
)
})
test(`input v-model with number`, () => {
assert(
`<input v-model.number="model" />`,
`<input value="{{a}}" bindinput="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.model, b: _o(_m($event => _ctx.model = $event.detail.value, { number: true })) }
}`
)
})
test(`input v-model with number and trim`, () => {
assert(
`<input v-model.trim.number="model" />`,
`<input value="{{a}}" bindinput="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.model, b: _o(_m($event => _ctx.model = $event.detail.value, { trim: true, number: true })) }
}`
)
})
test(`input v-model.number + v-on`, () => {
assert(
`<input @input="input" v-model.number="model" />`,
`<input bindinput="{{a}}" value="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _o([_m($event => _ctx.model = $event.detail.value, { number: true }), _ctx.input]), b: _ctx.model }
}`
)
})
......
......@@ -13,6 +13,7 @@ export const WITH_SCOPED_SLOT = Symbol(`withScopedSlot`)
export const STRINGIFY_STYLE = Symbol(`stringifyStyle`)
export const NORMALIZE_CLASS = Symbol(`normalizeClass`)
export const TO_DISPLAY_STRING = Symbol(`toDisplayString`)
export const WITH_MODEL_MODIFIERS = Symbol(`withModelModifiers`)
registerRuntimeHelpers({
[V_ON]: 'o',
[V_FOR]: 'f',
......@@ -26,5 +27,6 @@ registerRuntimeHelpers({
[WITH_SCOPED_SLOT]: 'w',
[STRINGIFY_STYLE]: 's',
[NORMALIZE_CLASS]: 'n',
[TO_DISPLAY_STRING]: `t`,
[TO_DISPLAY_STRING]: 't',
[WITH_MODEL_MODIFIERS]: 'm',
})
......@@ -12,6 +12,11 @@ import {
createCompoundExpression,
DirectiveTransform,
TransformContext as VueTransformContext,
isSimpleIdentifier,
isStaticExp,
createObjectProperty,
createSimpleExpression,
ConstantTypes,
} from '@vue/compiler-core'
import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
......@@ -19,8 +24,9 @@ import {
createBindDirectiveNode,
createOnDirectiveNode,
ATTR_DATASET_EVENT_OPTS,
isSimpleExpressionNode,
} from '@dcloudio/uni-cli-shared'
import { V_ON } from '../runtimeHelpers'
import { V_ON, WITH_MODEL_MODIFIERS } from '../runtimeHelpers'
import { genExpr } from '../codegen'
import { TransformContext } from '../transform'
import { DirectiveTransformResult } from './transformElement'
......@@ -75,6 +81,29 @@ export const transformModel: DirectiveTransform = (
)
}
if (dir.modifiers.length) {
const arg = dir.arg
const modifiers = dir.modifiers
.map((m) => (isSimpleIdentifier(m) ? m : JSON.stringify(m)) + `: true`)
.join(`, `)
const modifiersKey = arg
? isStaticExp(arg)
? `${arg.content}Modifiers`
: createCompoundExpression([arg, ' + "Modifiers"'])
: `modelModifiers`
baseResult.props.push(
createObjectProperty(
modifiersKey,
createSimpleExpression(
`{ ${modifiers} }`,
false,
dir.loc,
ConstantTypes.CAN_HOIST
)
)
)
}
return transformElementVModel(
baseResult.props,
node,
......@@ -98,6 +127,7 @@ function transformElementVModel(
context: TransformContext
) {
const dirs = transformVModel(props, node, context, {
isComponent: false,
binding: 'value',
event: 'input',
formatEventCode(code) {
......@@ -230,6 +260,7 @@ function transformComponentVModel(
) {
return {
props: transformVModel(props, node, context, {
isComponent: true,
formatEventCode(code) {
return code
},
......@@ -242,16 +273,18 @@ function transformVModel(
node: ElementNode,
context: TransformContext,
{
isComponent,
binding,
event,
formatEventCode,
}: {
isComponent: boolean
binding?: string
event?: string
formatEventCode: (code: string) => string
}
) {
if (props.length !== 2) {
if (props.length < 2) {
return []
}
const { key: modelValueArg, value: modelValeExpr } = props[0]
......@@ -270,15 +303,22 @@ function transformVModel(
binding || modelValueArg.content,
genExpr(modelValeExpr as ExpressionNode)
)
const modifiers = parseVModelModifiers(props[2])
// onUpdateExpr 通常是 ExpressionNode 或者被 cache 的 ExpressionNode
const vOnValue = (
onUpdateExpr.type === NodeTypes.JS_CACHE_EXPRESSION
? onUpdateExpr.value
: onUpdateExpr
) as ExpressionNode
const vOnUpdate = createOnDirectiveNode(
event || camelize(onUpdateArg.content.replace('onUpdate:', 'update-')),
formatEventCode(
genExpr(
wrapperVOn(
// onUpdateExpr 通常是 ExpressionNode 或者被 cache 的 ExpressionNode
(onUpdateExpr.type === NodeTypes.JS_CACHE_EXPRESSION
? onUpdateExpr.value
: onUpdateExpr) as ExpressionNode,
modifiers
? wrapperVModelModifiers(vOnValue, modifiers, context, isComponent)
: vOnValue,
node,
context
)
......@@ -287,3 +327,29 @@ function transformVModel(
)
return [vBindModelValue, vOnUpdate]
}
function parseVModelModifiers(property?: Property) {
if (
property &&
isSimpleExpressionNode(property.key) &&
property.key.content.endsWith('Modifiers') &&
isSimpleExpressionNode(property.value)
) {
return property.value.content
}
}
function wrapperVModelModifiers(
exp: ExpressionNode,
modifiers: string,
context: TransformContext,
isComponent = false
) {
return createCompoundExpression([
`${context.helperString(WITH_MODEL_MODIFIERS)}(`,
exp,
',',
modifiers,
`${isComponent ? `, true` : ``}`,
`)`,
])
}
......@@ -5563,6 +5563,30 @@ function setRef(ref, id, opts = {}) {
$templateRefs.push({ i: id, r: ref, k: opts.k, f: opts.f });
}
function withModelModifiers(fn, { number, trim }, isComponent = false) {
if (isComponent) {
return (...args) => {
if (trim) {
args = args.map((a) => a.trim());
}
else if (number) {
args = args.map(toNumber);
}
return fn(...args);
};
}
return (event) => {
const value = event.detail.value;
if (trim) {
event.detail.value = value.trim();
}
else if (number) {
event.detail.value = toNumber(value);
}
return fn(event);
};
}
function setupDevtoolsPlugin() {
// noop
}
......@@ -5579,7 +5603,8 @@ const h = (str) => hyphenate(str);
const n = (value) => normalizeClass(value);
const t = (val) => toDisplayString(val);
const p = (props) => renderProps(props);
const sr = (ref, id, opts) => setRef(ref, id, opts);
const sr = (ref, id, opts) => setRef(ref, id, opts);
const m = (fn, modifiers, isComponent = false) => withModelModifiers(fn, modifiers, isComponent);
function createApp(rootComponent, rootProps = null) {
rootComponent && (rootComponent.mpType = 'app');
......@@ -5587,4 +5612,4 @@ function createApp(rootComponent, rootProps = null) {
}
const createSSRApp = createApp;
export { EffectScope, Fragment, ReactiveEffect, Text, c, callWithAsyncErrorHandling, callWithErrorHandling, computed$1 as computed, createApp, createSSRApp, createVNode$1 as createVNode, createVueApp, customRef, d, defineAsyncComponent, defineComponent, defineEmits, defineExpose, defineProps, diff, e, effect, effectScope, f, findComponentPropsData, getCurrentInstance, getCurrentScope, getExposeProxy, guardReactiveProps, h, inject, injectHook, invalidateJob, isInSSRComponentSetup, isProxy, isReactive, isReadonly, isRef, logError, markRaw, mergeDefaults, mergeProps, n, nextTick, o, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, p, patch, provide, proxyRefs, pruneComponentPropsCache, queuePostFlushCb, r, reactive, readonly, ref, resolveComponent, resolveDirective, resolveFilter, s, setCurrentRenderingInstance, setTemplateRef, setupDevtoolsPlugin, shallowReactive, shallowReadonly, shallowRef, sr, stop, t, toHandlers, toRaw, toRef, toRefs, triggerRef, unref, updateProps, useAttrs, useCssModule, useCssVars, useSSRContext, useSlots, version, w, warn$1 as warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withModifiers, withScopeId };
export { EffectScope, Fragment, ReactiveEffect, Text, c, callWithAsyncErrorHandling, callWithErrorHandling, computed$1 as computed, createApp, createSSRApp, createVNode$1 as createVNode, createVueApp, customRef, d, defineAsyncComponent, defineComponent, defineEmits, defineExpose, defineProps, diff, e, effect, effectScope, f, findComponentPropsData, getCurrentInstance, getCurrentScope, getExposeProxy, guardReactiveProps, h, inject, injectHook, invalidateJob, isInSSRComponentSetup, isProxy, isReactive, isReadonly, isRef, logError, m, markRaw, mergeDefaults, mergeProps, n, nextTick, o, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, p, patch, provide, proxyRefs, pruneComponentPropsCache, queuePostFlushCb, r, reactive, readonly, ref, resolveComponent, resolveDirective, resolveFilter, s, setCurrentRenderingInstance, setTemplateRef, setupDevtoolsPlugin, shallowReactive, shallowReadonly, shallowRef, sr, stop, t, toHandlers, toRaw, toRef, toRefs, triggerRef, unref, updateProps, useAttrs, useCssModule, useCssVars, useSSRContext, useSlots, version, w, warn$1 as warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withModifiers, withScopeId };
......@@ -13,6 +13,7 @@ import { stringifyStyle } from './style'
import { dynamicSlot } from './dynamicSlot'
import { setRef } from './ref'
import { renderProps } from './renderProps'
import { withModelModifiers } from './withModelModifiers'
export { setupDevtoolsPlugin } from './devtools'
......@@ -38,3 +39,8 @@ export const n: typeof normalizeClass = (value) => normalizeClass(value)
export const t: typeof toDisplayString = (val) => toDisplayString(val)
export const p: typeof renderProps = (props) => renderProps(props)
export const sr: typeof setRef = (ref, id, opts) => setRef(ref, id, opts)
export const m: typeof withModelModifiers = (
fn,
modifiers,
isComponent = false
) => withModelModifiers(fn, modifiers, isComponent)
......@@ -56,7 +56,7 @@ export function vOn(value: EventValue | undefined, key?: number | string) {
return name
}
interface MPEvent extends WechatMiniprogram.BaseEvent {
export interface MPEvent extends WechatMiniprogram.BaseEvent {
detail: Record<string, any> & {
__args__?: unknown[]
}
......
import { toNumber } from '@vue/shared'
import type { MPEvent } from './vOn'
interface VModelFn {
(...args: unknown[]): unknown
modifiers?: VModelModifiers
}
interface VModelModifiers {
number?: boolean
trim?: boolean
}
export function withModelModifiers(
fn: VModelFn,
{ number, trim }: VModelModifiers,
isComponent = false
) {
if (isComponent) {
return (...args: any[]) => {
if (trim) {
args = args.map((a) => a.trim())
} else if (number) {
args = args.map(toNumber)
}
return fn(...args)
}
}
return (event: MPEvent) => {
const value: string = event.detail.value
if (trim) {
event.detail.value = value.trim()
} else if (number) {
event.detail.value = toNumber(value)
}
return fn(event)
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册