diff --git a/packages/uni-mp-compiler/__tests__/vModel.spec.ts b/packages/uni-mp-compiler/__tests__/vModel.spec.ts
index ea3870ca2e6b2c296afa1fb94aa48e5926c0e67c..b55e50ad91e2cc62633fa0414210769408386b8a 100644
--- a/packages/uni-mp-compiler/__tests__/vModel.spec.ts
+++ b/packages/uni-mp-compiler/__tests__/vModel.spec.ts
@@ -22,6 +22,39 @@ describe('compiler: transform v-model', () => {
}
)
})
+ test(`component v-model with number`, () => {
+ assert(
+ ``,
+ ``,
+ `(_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(
+ ``,
+ ``,
+ `(_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(
+ ``,
+ ``,
+ `(_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(
``,
@@ -44,6 +77,33 @@ describe('compiler: transform v-model', () => {
``,
`(_ctx, _cache) => {
return { a: _o([$event => _ctx.model = $event.detail.value, _ctx.input]), b: _ctx.model }
+}`
+ )
+ })
+ test(`input v-model with number`, () => {
+ assert(
+ ``,
+ ``,
+ `(_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(
+ ``,
+ ``,
+ `(_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(
+ ``,
+ ``,
+ `(_ctx, _cache) => {
+ return { a: _o([_m($event => _ctx.model = $event.detail.value, { number: true }), _ctx.input]), b: _ctx.model }
}`
)
})
diff --git a/packages/uni-mp-compiler/src/runtimeHelpers.ts b/packages/uni-mp-compiler/src/runtimeHelpers.ts
index 7600dbfac8162bf6c46ceb6365e48eefa62d656b..cf6dd7d3b4750eaae51dd8798953762d4e7b3455 100644
--- a/packages/uni-mp-compiler/src/runtimeHelpers.ts
+++ b/packages/uni-mp-compiler/src/runtimeHelpers.ts
@@ -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',
})
diff --git a/packages/uni-mp-compiler/src/transforms/vModel.ts b/packages/uni-mp-compiler/src/transforms/vModel.ts
index c33812086dc8418e7f39a653fa10e27d5a7b7e6a..5d67b7f863aeafea222792d0c376477846a5baab 100644
--- a/packages/uni-mp-compiler/src/transforms/vModel.ts
+++ b/packages/uni-mp-compiler/src/transforms/vModel.ts
@@ -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` : ``}`,
+ `)`,
+ ])
+}
diff --git a/packages/uni-mp-vue/dist/vue.runtime.esm.js b/packages/uni-mp-vue/dist/vue.runtime.esm.js
index 99b91e76fc30aca2595f25c246b03b6b57b4a756..8d94c8e73d531f7f64a19c9d770bcb0cf9524b3c 100644
--- a/packages/uni-mp-vue/dist/vue.runtime.esm.js
+++ b/packages/uni-mp-vue/dist/vue.runtime.esm.js
@@ -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 };
diff --git a/packages/uni-mp-vue/src/helpers/index.ts b/packages/uni-mp-vue/src/helpers/index.ts
index 542af4fbe6992b70a25820ab0a3287968649dd58..0f489aac2da03795a51c80a4f1323550a1960962 100644
--- a/packages/uni-mp-vue/src/helpers/index.ts
+++ b/packages/uni-mp-vue/src/helpers/index.ts
@@ -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)
diff --git a/packages/uni-mp-vue/src/helpers/vOn.ts b/packages/uni-mp-vue/src/helpers/vOn.ts
index 0039c928fabbb41f4d7a9885937f767e158c3e19..589d0a86e9b363047affd57f37b10cf382125832 100644
--- a/packages/uni-mp-vue/src/helpers/vOn.ts
+++ b/packages/uni-mp-vue/src/helpers/vOn.ts
@@ -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 & {
__args__?: unknown[]
}
diff --git a/packages/uni-mp-vue/src/helpers/withModelModifiers.ts b/packages/uni-mp-vue/src/helpers/withModelModifiers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4dd3af1df8cf209be73ca71fa2d9635bdac67eb6
--- /dev/null
+++ b/packages/uni-mp-vue/src/helpers/withModelModifiers.ts
@@ -0,0 +1,36 @@
+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)
+ }
+}