fix(vue): account for event name changes in vue 3.0.6+

This commit is contained in:
Liam DeBeasi
2021-02-26 13:03:45 -05:00
committed by Liam DeBeasi
parent cd8ffd82a0
commit 06d4c8e6f1
8 changed files with 239 additions and 119 deletions

View File

@ -2,22 +2,39 @@ import { App, Plugin } from 'vue';
import { IonicConfig, setupConfig } from '@ionic/core';
import { applyPolyfills, defineCustomElements } from '@ionic/core/loader';
const needsKebabCase = (version: string) => !['3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.0.4', '3.0.5'].includes(version);
/**
* We need to make sure that the web component fires an event
* that will not conflict with the user's @ionChange binding,
* otherwise the binding's callback will fire before any
* v-model values have been updated.
* We need to make sure that the web component fires an event
* that will not conflict with the user's @ionChange binding,
* otherwise the binding's callback will fire before any
* v-model values have been updated.
*/
const toLowerCase = (eventName: string) => eventName === 'ionChange' ? 'v-ionchange' : eventName.toLowerCase();
const toKebabCase = (eventName: string) => eventName === 'ionChange' ? 'v-ion-change' : eventName.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
/**
* Vue 3.0.6 fixed a bug where events on custom elements
* were always converted to lower case, so "ionRefresh"
* became "ionRefresh". We need to account for the old
* issue as well as the new behavior where "ionRefresh"
* is converted to "ion-refresh".
* See https://github.com/vuejs/vue-next/pull/2847
*/
const transformEventName = (eventName: string) => {
return eventName === 'ionChange' ? 'v-ionchange' : eventName.toLowerCase();
}
const ael = (el: any, eventName: string, cb: any, opts: any) => el.addEventListener(transformEventName(eventName), cb, opts);
const rel = (el: any, eventName: string, cb: any, opts: any) => el.removeEventListener(transformEventName(eventName), cb, opts);
const getHelperFunctions = (needsKebabCase: boolean = true) => {
const conversionFn = (needsKebabCase) ? toKebabCase : toLowerCase;
return {
ael: (el: any, eventName: string, cb: any, opts: any) => el.addEventListener(conversionFn(eventName), cb, opts),
rel: (el: any, eventName: string, cb: any, opts: any) => el.removeEventListener(conversionFn(eventName), cb, opts),
ce: (eventName: string, opts: any) => new CustomEvent(conversionFn(eventName), opts)
};
};
export const IonicVue: Plugin = {
async install(_app: App, config: IonicConfig = {}) {
async install(app: App, config: IonicConfig = {}) {
if (typeof (window as any) !== 'undefined') {
const { ael, rel, ce } = getHelperFunctions(needsKebabCase(app.version));
setupConfig({
...config,
_ael: ael,
@ -26,7 +43,7 @@ export const IonicVue: Plugin = {
await applyPolyfills();
await defineCustomElements(window, {
exclude: ['ion-tabs'],
ce: (eventName: string, opts: any) => new CustomEvent(transformEventName(eventName), opts),
ce,
ael,
rel
} as any);

View File

@ -97,7 +97,10 @@ export const IonCheckbox = /*@__PURE__*/ defineContainer<JSX.IonCheckbox>('ion-c
],
{
"modelProp": "checked",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -181,7 +184,10 @@ export const IonDatetime = /*@__PURE__*/ defineContainer<JSX.IonDatetime>('ion-d
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -296,7 +302,10 @@ export const IonInput = /*@__PURE__*/ defineContainer<JSX.IonInput>('ion-input',
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -448,7 +457,10 @@ export const IonRadio = /*@__PURE__*/ defineContainer<JSX.IonRadio>('ion-radio',
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -461,7 +473,10 @@ export const IonRadioGroup = /*@__PURE__*/ defineContainer<JSX.IonRadioGroup>('i
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -486,7 +501,10 @@ export const IonRange = /*@__PURE__*/ defineContainer<JSX.IonRange>('ion-range',
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -558,7 +576,10 @@ export const IonSearchbar = /*@__PURE__*/ defineContainer<JSX.IonSearchbar>('ion
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -575,7 +596,10 @@ export const IonSegment = /*@__PURE__*/ defineContainer<JSX.IonSegment>('ion-seg
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -588,7 +612,10 @@ export const IonSegmentButton = /*@__PURE__*/ defineContainer<JSX.IonSegmentButt
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -613,7 +640,10 @@ export const IonSelect = /*@__PURE__*/ defineContainer<JSX.IonSelect>('ion-selec
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -706,7 +736,10 @@ export const IonTextarea = /*@__PURE__*/ defineContainer<JSX.IonTextarea>('ion-t
],
{
"modelProp": "value",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});
@ -734,7 +767,10 @@ export const IonToggle = /*@__PURE__*/ defineContainer<JSX.IonToggle>('ion-toggl
],
{
"modelProp": "checked",
"modelUpdateEvent": "v-ionChange",
"modelUpdateEvent": [
"v-ionChange",
"v-ion-change"
],
"externalModelUpdateEvent": "ionChange"
});

View File

@ -16,7 +16,7 @@ interface NavManager<T = any> {
interface ComponentOptions {
modelProp?: string;
modelUpdateEvent?: string;
modelUpdateEvent?: string | string[];
externalModelUpdateEvent?: string;
}
@ -55,19 +55,22 @@ export const defineContainer = <Props>(name: string, componentProps: string[] =
const onVnodeBeforeMount = (vnode: VNode) => {
// Add a listener to tell Vue to update the v-model
if (vnode.el) {
vnode.el.addEventListener(modelUpdateEvent.toLowerCase(), (e: Event) => {
modelPropValue = (e?.target as any)[modelProp];
emit(UPDATE_VALUE_EVENT, modelPropValue);
const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent];
eventsNames.forEach((eventName: string) => {
vnode.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.
*/
emit(externalModelUpdateEvent, e);
/**
* 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.
*/
emit(externalModelUpdateEvent, e);
});
});
}
};