diff --git a/packages/compiler/src/runtimeHelpers.ts b/packages/compiler/src/runtimeHelpers.ts index 24192982..54634d88 100644 --- a/packages/compiler/src/runtimeHelpers.ts +++ b/packages/compiler/src/runtimeHelpers.ts @@ -1,10 +1,6 @@ import { registerRuntimeHelpers } from '@vue/compiler-core' -export const V_MODEL_RADIO = Symbol(__DEV__ ? `vModelRadio` : ``) -export const V_MODEL_CHECKBOX = Symbol(__DEV__ ? `vModelCheckbox` : ``) -export const V_MODEL_TEXT = Symbol(__DEV__ ? `vModelText` : ``) -export const V_MODEL_SELECT = Symbol(__DEV__ ? `vModelSelect` : ``) -export const V_MODEL_DYNAMIC = Symbol(__DEV__ ? `vModelDynamic` : ``) +export const V_MODEL = Symbol(__DEV__ ? `vModel` : ``) export const V_ON_WITH_MODIFIERS = Symbol(__DEV__ ? `vOnModifiersGuard` : ``) export const V_ON_WITH_KEYS = Symbol(__DEV__ ? `vOnKeysGuard` : ``) @@ -17,15 +13,11 @@ export const TRANSITION_GROUP = Symbol(__DEV__ ? `TransitionGroup` : ``) export const ACTION_BAR = Symbol(__DEV__ ? `ActionBar` : ``) registerRuntimeHelpers({ - [V_MODEL_RADIO]: `vModelRadio`, - [V_MODEL_CHECKBOX]: `vModelCheckbox`, - [V_MODEL_TEXT]: `vModelText`, - [V_MODEL_SELECT]: `vModelSelect`, - [V_MODEL_DYNAMIC]: `vModelDynamic`, + [V_MODEL]: `vModel`, [V_ON_WITH_MODIFIERS]: `withModifiers`, [V_ON_WITH_KEYS]: `withKeys`, [V_SHOW]: `vShow`, [TRANSITION]: `Transition`, [TRANSITION_GROUP]: `TransitionGroup`, - [ACTION_BAR]: `ActionBar` + [ACTION_BAR]: `ActionBar`, }) diff --git a/packages/runtime/src/directives/vModel.ts b/packages/runtime/src/directives/vModel.ts index 5738c8a7..b44ae24c 100644 --- a/packages/runtime/src/directives/vModel.ts +++ b/packages/runtime/src/directives/vModel.ts @@ -1 +1,48 @@ -// todo: if the element is a knownView - we should transform v-model to rely on the built-in events (valueChanged) and not require wrapping them in a Vue component. +import { ObjectDirective, VNode } from '@vue/runtime-core' +import { getViewMeta, NSModel } from '../registry' +import { addEventListener } from '../modules/events' +import { INSVElement } from '../nodes' +import { isArray, invokeArrayFns } from '@vue/shared' + +type AssignerFn = (value: any) => void + +const getModelAssigner = (vnode: VNode): AssignerFn => { + const fn = vnode.props!['onUpdate:modelValue'] + return isArray(fn) ? (value) => invokeArrayFns(fn, value) : fn +} + +function toNumber(val: string): number | string { + const n = parseFloat(val) + return isNaN(n) ? val : n +} + +type ModelDirective = ObjectDirective + +export const vModel: ModelDirective = { + beforeMount(el, { value, modifiers: { trim, number } }, vnode) { + el._assign = getModelAssigner(vnode) + const castToNumber = number + const { prop, event } = getViewMeta(el.tagName).model as NSModel + el._assign(value) + el.setAttribute(prop, value) + + addEventListener(el, event, () => { + let propValue: unknown = el.getAttribute(prop) + if (trim && typeof propValue === 'string') { + propValue = propValue.trim() + } else if (castToNumber && typeof propValue === 'string') { + propValue = toNumber(propValue) + } + el._assign(propValue) + }) + }, + + beforeUpdate(el, { value, oldValue }, vnode) { + const { prop } = getViewMeta(el.tagName).model as NSModel + el._assign = getModelAssigner(vnode) + if (value === oldValue) { + return + } + el.setAttribute(prop, value) + }, +} diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 362cab06..4f0775d1 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -4,7 +4,7 @@ import { RootRenderFunction, CreateAppFunction, ComponentPublicInstance, - VNode + VNode, } from '@vue/runtime-core' import { Application } from '@nativescript/core' import { debug } from '@nativescript-vue/shared' @@ -15,7 +15,7 @@ import './registry' const rendererOptions = { patchProp, - ...nodeOps + ...nodeOps, } let renderer: Renderer @@ -31,11 +31,11 @@ function runApp(root: ComponentPublicInstance): ComponentPublicInstance { `Root Node: ${JSON.stringify({ id: root.$el.nodeId, type: root.$el.nodeType, - tag: root.$el.tagName + tag: root.$el.tagName, })}` ) return root.$el.nativeView - } + }, }) return root @@ -63,6 +63,7 @@ export * from './registry' export { resolveComponent } from './resolveAssets' // runtime directive helpers +export { vModel } from './directives/vModel' export { vShow } from './directives/vShow' // Runtime components diff --git a/packages/runtime/src/modules/events.ts b/packages/runtime/src/modules/events.ts index eb06dd5d..b2af2f5e 100644 --- a/packages/runtime/src/modules/events.ts +++ b/packages/runtime/src/modules/events.ts @@ -16,7 +16,7 @@ type EventValueWithOptions = { invoker?: Invoker | null } -function addEventListener( +export function addEventListener( el: INSVElement, event: string, handler: EventListener, @@ -68,7 +68,7 @@ export function patchEvent( } function createInvoker(initialValue: EventValue) { - const invoker: Invoker = e => { + const invoker: Invoker = (e) => { callWithAsyncErrorHandling(invoker.value, null, 5, [e]) } invoker.value = initialValue diff --git a/packages/runtime/src/registry.ts b/packages/runtime/src/registry.ts index 39cc8dcc..06077f80 100644 --- a/packages/runtime/src/registry.ts +++ b/packages/runtime/src/registry.ts @@ -1,7 +1,7 @@ import { Frame as TNSFrame, Page as TNSPage, - ViewBase as TNSViewBase + ViewBase as TNSViewBase, } from '@nativescript/core' import { NSVElement, NSVViewFlags } from './nodes' import { actionBarNodeOps } from './components/ActionBar' @@ -9,12 +9,18 @@ import { warn } from '@vue/runtime-core' export type NSVElementResolver = () => TNSViewBase +export type NSModel = { + prop: string + event: string +} + export interface NSVViewMeta { viewFlags: NSVViewFlags nodeOps?: { insert(child: NSVElement, parent: NSVElement, atIndex?: number): void remove(child: NSVElement, parent: NSVElement): void } + model?: NSModel } export interface NSVElementDescriptor { @@ -23,7 +29,7 @@ export interface NSVElementDescriptor { } export let defaultViewMeta: NSVViewMeta = { - viewFlags: NSVViewFlags.NONE + viewFlags: NSVViewFlags.NONE, } let elementMap: Record = {} @@ -76,7 +82,7 @@ export function registerElement( elementMap[normalizedName] = { meta: mergedMeta, - resolver + resolver, } // console.log(`->registerElement(${elementName})`) }