From ed954bcd33518ac1208cab17c7a55de9f6c869a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sun, 4 Feb 2024 22:27:24 +0800 Subject: [PATCH] feat(runtime-vapor): support v-model w/ select tag --- packages/runtime-vapor/src/directive.ts | 3 +- .../runtime-vapor/src/directives/vModel.ts | 132 +++++++++++++++++- 2 files changed, 127 insertions(+), 8 deletions(-) diff --git a/packages/runtime-vapor/src/directive.ts b/packages/runtime-vapor/src/directive.ts index 68e2c43af..54c4500c6 100644 --- a/packages/runtime-vapor/src/directive.ts +++ b/packages/runtime-vapor/src/directive.ts @@ -36,6 +36,7 @@ export type DirectiveHookName = export type ObjectDirective = { [K in DirectiveHookName]?: DirectiveHook | undefined } & { + /** Watch value deeply */ deep?: boolean } @@ -100,7 +101,7 @@ export function withDirectives( // register source if (source) { // callback will be overridden by middleware - renderWatch(source, NOOP) + renderWatch(source, NOOP, { deep: dir.deep }) } } diff --git a/packages/runtime-vapor/src/directives/vModel.ts b/packages/runtime-vapor/src/directives/vModel.ts index 7138989e8..a6bdbb8ff 100644 --- a/packages/runtime-vapor/src/directives/vModel.ts +++ b/packages/runtime-vapor/src/directives/vModel.ts @@ -1,7 +1,16 @@ +import { + invokeArrayFns, + isArray, + isSet, + looseEqual, + looseIndexOf, + looseToNumber, +} from '@vue/shared' import type { ComponentInternalInstance } from '../component' import type { ObjectDirective } from '../directive' import { on } from '../dom/on' -import { invokeArrayFns, isArray, looseToNumber } from '@vue/shared' +import { nextTick } from '../scheduler' +import { warn } from '../warning' type AssignerFn = (value: any) => void @@ -26,16 +35,19 @@ function onCompositionEnd(e: Event) { } } -const assignKeyMap = new WeakMap() +const assignFnMap = new WeakMap() +const assigningMap = new WeakMap() // 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: ObjectDirective< - HTMLInputElement | HTMLTextAreaElement + HTMLInputElement | HTMLTextAreaElement, + any, + 'lazy' | 'trim' | 'number' > = { beforeMount(el, { instance, modifiers: { lazy, trim, number } = {} }) { const assigner = getModelAssigner(el, instance) - assignKeyMap.set(el, assigner) + assignFnMap.set(el, assigner) const castToNumber = number // || (vnode.props && vnode.props.type === 'number') on(el, lazy ? 'change' : 'input', e => { @@ -72,7 +84,7 @@ export const vModelText: ObjectDirective< el, { instance, value, modifiers: { lazy, trim, number } = {} }, ) { - assignKeyMap.set(el, getModelAssigner(el, instance)) + assignFnMap.set(el, getModelAssigner(el, instance)) // avoid clearing unresolved text. #2302 if ((el as any).composing) return @@ -100,7 +112,113 @@ export const vModelText: ObjectDirective< } // TODO -export const vModelDynamic = {} export const vModelRadio = {} + +export const vModelSelect: ObjectDirective = { + // expects an Array or Set value for its binding, ` + + `but got ${Object.prototype.toString.call(value).slice(8, -1)}.`, + ) + return + } + + // Disable fast path due to https://github.com/vuejs/core/issues/10267 + // fast path for updates triggered by other changes + // if (isArrayValue && looseEqual(value, oldValue)) { + // return + // } + + for (let i = 0, l = el.options.length; i < l; i++) { + const option = el.options[i] + const optionValue = getValue(option, instance) + if (isMultiple) { + if (isArrayValue) { + const optionType = typeof optionValue + // fast path for string / number values + if (optionType === 'string' || optionType === 'number') { + option.selected = value.includes( + number ? looseToNumber(optionValue) : optionValue, + ) + } else { + option.selected = looseIndexOf(value, optionValue) > -1 + } + } else { + option.selected = value.has(optionValue) + } + } else { + if (looseEqual(getValue(option, instance), value)) { + if (el.selectedIndex !== i) el.selectedIndex = i + return + } + } + } + if (!isMultiple && el.selectedIndex !== -1) { + el.selectedIndex = -1 + } +} + +// retrieve raw value set via :value bindings +function getValue( + el: HTMLOptionElement | HTMLInputElement, + instance: ComponentInternalInstance, +) { + const metadata = instance.metadata.get(el) + return metadata ? metadata.props.value : el.value +} + export const vModelCheckbox = {} -export const vModelSelect = {} + +export const vModelDynamic = {}