]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(vapor): v-model text reuse from runtime-dom
authorEvan You <evan@vuejs.org>
Sat, 1 Feb 2025 13:06:40 +0000 (21:06 +0800)
committerEvan You <evan@vuejs.org>
Sat, 1 Feb 2025 13:06:40 +0000 (21:06 +0800)
packages/compiler-vapor/src/generators/vModel.ts
packages/runtime-dom/src/directives/vModel.ts
packages/runtime-dom/src/index.ts
packages/runtime-vapor/src/directives/vModel.ts [new file with mode: 0644]
packages/runtime-vapor/src/index.ts

index aab780488c1d2689ba818d81f8233ed85f716493..8ebd365762ba5dc19228da3f07b871f8a14d9358 100644 (file)
@@ -1,48 +1,44 @@
 import type { CodegenContext } from '../generate'
 import type { DirectiveIRNode } from '../ir'
-import type { CodeFragment } from './utils'
-
-export function genVModel(
-  oper: DirectiveIRNode,
-  context: CodegenContext,
-): CodeFragment[] {
-  return []
-}
-
-import { camelize } from '@vue/shared'
+import { type CodeFragment, NEWLINE, genCall } from './utils'
 import { genExpression } from './expression'
-import type { SetModelValueIRNode } from '../ir'
-import { NEWLINE, genCall } from './utils'
-import type { SimpleExpressionNode } from '@vue/compiler-dom'
-
-export function genSetModelValue(
-  oper: SetModelValueIRNode,
-  context: CodegenContext,
-): CodeFragment[] {
-  const { helper } = context
-  const name = oper.key.isStatic
-    ? [JSON.stringify(`update:${camelize(oper.key.content)}`)]
-    : ['`update:${', ...genExpression(oper.key, context), '}`']
 
-  const handler = genModelHandler(oper.value, context)
+const helperMap = {
+  text: 'applyTextModel',
+  radio: 'applyRadioModel',
+  checkbox: 'applyCheckboxModel',
+  select: 'applySelectModel',
+  dynamic: 'applyDynamicModel',
+} as const
 
-  return [
-    NEWLINE,
-    ...genCall(helper('delegate'), `n${oper.element}`, name, handler),
-  ]
-}
-
-export function genModelHandler(
-  value: SimpleExpressionNode,
+// This is only for built-in v-model on native elements.
+export function genVModel(
+  oper: DirectiveIRNode,
   context: CodegenContext,
 ): CodeFragment[] {
   const {
-    options: { isTS },
-  } = context
+    modelType,
+    element,
+    dir: { exp, modifiers },
+  } = oper
 
   return [
-    `() => ${isTS ? `($event: any)` : `$event`} => (`,
-    ...genExpression(value, context, '$event'),
-    ')',
+    NEWLINE,
+    ...genCall(
+      context.helper(helperMap[modelType!]),
+      `n${element}`,
+      // getter
+      [`() => (`, ...genExpression(exp!, context), `)`],
+      // setter
+      [
+        `${context.options.isTS ? `($event: any)` : `$event`} => (`,
+        ...genExpression(exp!, context, '$event'),
+        ')',
+      ],
+      // modifiers
+      modifiers.length
+        ? `{ ${modifiers.map(e => e.content + ': true').join(',')} }`
+        : undefined,
+    ),
   ]
 }
index 5057e16d472bc59304b2bb386e96b53934e6cf92..41e33a68afcc21324b38a32856fce25b0a07d655 100644 (file)
@@ -52,34 +52,13 @@ export const vModelText: ModelDirective<
   'trim' | 'number' | 'lazy'
 > = {
   created(el, { modifiers: { lazy, trim, number } }, vnode) {
-    el[assignKey] = getModelAssigner(vnode)
-    const castToNumber =
-      number || (vnode.props && vnode.props.type === 'number')
-    addEventListener(el, lazy ? 'change' : 'input', e => {
-      if ((e.target as any).composing) return
-      let domValue: string | number = el.value
-      if (trim) {
-        domValue = domValue.trim()
-      }
-      if (castToNumber) {
-        domValue = looseToNumber(domValue)
-      }
-      el[assignKey](domValue)
-    })
-    if (trim) {
-      addEventListener(el, 'change', () => {
-        el.value = el.value.trim()
-      })
-    }
-    if (!lazy) {
-      addEventListener(el, 'compositionstart', onCompositionStart)
-      addEventListener(el, 'compositionend', onCompositionEnd)
-      // Safari < 10.2 & UIWebView doesn't fire compositionend when
-      // switching focus before confirming composition choice
-      // this also fixes the issue where some browsers e.g. iOS Chrome
-      // fires "change" instead of "input" on autocomplete.
-      addEventListener(el, 'change', onCompositionEnd)
-    }
+    vModelTextInit(
+      el,
+      (el[assignKey] = getModelAssigner(vnode)),
+      trim,
+      number || !!(vnode.props && vnode.props.type === 'number'),
+      lazy,
+    )
   },
   // set value on mounted so it's after min/max for type="range"
   mounted(el, { value }) {
@@ -91,30 +70,81 @@ export const vModelText: ModelDirective<
     vnode,
   ) {
     el[assignKey] = getModelAssigner(vnode)
-    // avoid clearing unresolved text. #2302
-    if ((el as any).composing) return
-    const elValue =
-      (number || el.type === 'number') && !/^0\d/.test(el.value)
-        ? looseToNumber(el.value)
-        : el.value
-    const newValue = value == null ? '' : value
+    vModelTextUpdate(el, value, oldValue, trim, number, lazy)
+  },
+}
 
-    if (elValue === newValue) {
-      return
+/**
+ * @internal
+ */
+export const vModelTextInit = (
+  el: HTMLInputElement | HTMLTextAreaElement,
+  set: (v: any) => void,
+  trim: boolean | undefined,
+  number: boolean | undefined,
+  lazy: boolean | undefined,
+): void => {
+  addEventListener(el, lazy ? 'change' : 'input', e => {
+    if ((e.target as any).composing) return
+    let domValue: string | number = el.value
+    if (trim) {
+      domValue = domValue.trim()
+    }
+    if (number) {
+      domValue = looseToNumber(domValue)
     }
+    set(domValue)
+  })
+  if (trim) {
+    addEventListener(el, 'change', () => {
+      el.value = el.value.trim()
+    })
+  }
+  if (!lazy) {
+    addEventListener(el, 'compositionstart', onCompositionStart)
+    addEventListener(el, 'compositionend', onCompositionEnd)
+    // Safari < 10.2 & UIWebView doesn't fire compositionend when
+    // switching focus before confirming composition choice
+    // this also fixes the issue where some browsers e.g. iOS Chrome
+    // fires "change" instead of "input" on autocomplete.
+    addEventListener(el, 'change', onCompositionEnd)
+  }
+}
 
-    if (document.activeElement === el && el.type !== 'range') {
-      // #8546
-      if (lazy && value === oldValue) {
-        return
-      }
-      if (trim && el.value.trim() === newValue) {
-        return
-      }
+/**
+ * @internal
+ */
+export const vModelTextUpdate = (
+  el: HTMLInputElement | HTMLTextAreaElement,
+  value: any,
+  oldValue: any,
+  trim: boolean | undefined,
+  number: boolean | undefined,
+  lazy: boolean | undefined,
+): void => {
+  // avoid clearing unresolved text. #2302
+  if ((el as any).composing) return
+  const elValue =
+    (number || el.type === 'number') && !/^0\d/.test(el.value)
+      ? looseToNumber(el.value)
+      : el.value
+  const newValue = value == null ? '' : value
+
+  if (elValue === newValue) {
+    return
+  }
+
+  if (document.activeElement === el && el.type !== 'range') {
+    // #8546
+    if (lazy && value === oldValue) {
+      return
     }
+    if (trim && el.value.trim() === newValue) {
+      return
+    }
+  }
 
-    el.value = newValue
-  },
+  el.value = newValue
 }
 
 export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
index a23d0cb588bbc0fb1a5b9918f0be50086b30bad8..2ba88006f97f9dddc3241fc67f00458bb8f98c09 100644 (file)
@@ -330,3 +330,7 @@ export {
   vShowHidden,
   type VShowElement,
 } from './directives/vShow'
+/**
+ * @internal
+ */
+export { vModelTextInit, vModelTextUpdate } from './directives/vModel'
diff --git a/packages/runtime-vapor/src/directives/vModel.ts b/packages/runtime-vapor/src/directives/vModel.ts
new file mode 100644 (file)
index 0000000..48880f6
--- /dev/null
@@ -0,0 +1,28 @@
+import { onMounted, vModelTextInit, vModelTextUpdate } from '@vue/runtime-dom'
+import { renderEffect } from '../renderEffect'
+
+type VaporModelDirective<T = Element> = (
+  el: T,
+  get: () => any,
+  set: (v: any) => void,
+  modifiers?: { number?: true; trim?: true; lazy?: true },
+) => void
+
+export const applyTextModel: VaporModelDirective<
+  HTMLInputElement | HTMLTextAreaElement
+> = (el, get, set, { trim, number, lazy } = {}) => {
+  vModelTextInit(el, set, trim, number, lazy)
+  onMounted(() => {
+    let oldValue: any
+    renderEffect(() => {
+      const value = get()
+      vModelTextUpdate(el, value, oldValue, trim, number, lazy)
+      oldValue = value
+    })
+  })
+}
+
+export const applyRadioModel: VaporModelDirective = (el, get, set) => {}
+export const applyCheckboxModel: VaporModelDirective = (el, get, set) => {}
+export const applySelectModel: VaporModelDirective = (el, get, set) => {}
+export const applyDynamicModel: VaporModelDirective = (el, get, set) => {}
index f69d61444bd7c32dcba849ca99fa47dc2bc57bce..b228217efa621af2c3088239a3b20362ddca9958 100644 (file)
@@ -31,3 +31,10 @@ export {
 export { createTemplateRefSetter } from './apiTemplateRef'
 export { createDynamicComponent } from './apiCreateDynamicComponent'
 export { applyVShow } from './directives/vShow'
+export {
+  applyTextModel,
+  applyRadioModel,
+  applyCheckboxModel,
+  applySelectModel,
+  applyDynamicModel,
+} from './directives/vModel'