From 4ecd9f2a1fb58183e15c2471a95ade8cfa501b6a Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 27 Oct 2023 15:17:41 -0400 Subject: [PATCH] fix(vue): ensure v-model is updated before event callback --- packages/vue-output-target/README.md | 14 +-------- .../__tests__/generate-vue-components.spec.ts | 10 +++---- .../src/generate-vue-component.ts | 4 --- packages/vue-output-target/src/types.ts | 1 - .../vue-component-lib/utils.ts | 29 +++++-------------- 5 files changed, 12 insertions(+), 46 deletions(-) diff --git a/packages/vue-output-target/README.md b/packages/vue-output-target/README.md index c75ec5a9..66225850 100644 --- a/packages/vue-output-target/README.md +++ b/packages/vue-output-target/README.md @@ -72,17 +72,6 @@ export interface ComponentModelConfig { * of the `v-model` reference is based off. */ targetAttr: string; - - /** - * (optional) The event to emit from the Vue component - * wrapper. When listening directly to the `event` emitted - * from the Web Component, the `v-model` reference has not - * yet had a chance to update. By setting `externalEvent`, - * your Web Component can emit `event`, the Vue output target - * can update the `v-model` reference, and then emit `externalEvent`, - * notifying the end user that `v-model` has changed. Defaults to `event`. - */ - externalEvent?: string; } ``` @@ -94,8 +83,7 @@ vueOutputTarget({ componentModels: [ { elements: ['my-input', 'my-textarea'], - event: 'v-on-change', - externalEvent: 'on-change', + event: 'on-change', targetAttr: 'value' } ] diff --git a/packages/vue-output-target/__tests__/generate-vue-components.spec.ts b/packages/vue-output-target/__tests__/generate-vue-components.spec.ts index ce22cfef..ad05246d 100644 --- a/packages/vue-output-target/__tests__/generate-vue-components.spec.ts +++ b/packages/vue-output-target/__tests__/generate-vue-components.spec.ts @@ -30,8 +30,7 @@ export const MyComponent = /*@__PURE__*/ defineContainer const generateComponentDefinition = createComponentDefinition('Components', [ { elements: ['my-component'], - event: 'v-ionChange', - externalEvent: 'ionChange', + event: 'ionChange', targetAttr: 'value', }, ]); @@ -83,7 +82,7 @@ export const MyComponent = /*@__PURE__*/ defineContainer { modelValue?: T; @@ -53,8 +53,6 @@ const getElementClasses = ( * to customElements.define. Only set if `includeImportCustomElements: true` in your config. * @prop modelProp - The prop that v-model binds to (i.e. value) * @prop modelUpdateEvent - The event that is fired from your Web Component when the value changes (i.e. ionChange) - * @prop externalModelUpdateEvent - The external event to fire from your Vue component when modelUpdateEvent fires. This is used for ensuring that v-model references have been - * correctly updated when a user's event callback fires. */ export const defineContainer = ( name: string, @@ -62,7 +60,6 @@ export const defineContainer = ( componentProps: string[] = [], modelProp?: string, modelUpdateEvent?: string, - externalModelUpdateEvent?: string ) => { /** * Create a Vue component wrapper around a Web Component. @@ -78,30 +75,17 @@ export const defineContainer = ( let modelPropValue = props[modelProp]; const containerRef = ref(); const classes = new Set(getComponentClasses(attrs.class)); - const onVnodeBeforeMount = (vnode: VNode) => { - // Add a listener to tell Vue to update the v-model - if (vnode.el) { + const vModelDirective = { + created(el: HTMLElement) { const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent]; eventsNames.forEach((eventName: string) => { - vnode.el!.addEventListener(eventName.toLowerCase(), (e: Event) => { + el.addEventListener(eventName.toLowerCase(), (e: Event) => { modelPropValue = (e?.target as any)[modelProp]; emit(UPDATE_VALUE_EVENT, modelPropValue); - - /** - * We need to emit the change event here - * rather than on the web component to ensure - * that any v-model bindings have been updated. - * Otherwise, the developer will listen on the - * native web component, but the v-model will - * not have been updated yet. - */ - if (externalModelUpdateEvent) { - emit(externalModelUpdateEvent, e); - } }); }); } - }; + } const currentInstance = getCurrentInstance(); const hasRouter = currentInstance?.appContext?.provides[NAV_MANAGER]; @@ -182,7 +166,8 @@ export const defineContainer = ( } } - return h(name, propsToAdd, slots.default && slots.default()); + const node = h(name, propsToAdd, slots.default && slots.default()); + return modelProp === undefined ? node : withDirectives(node, [vModelDirective]); }; });