mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-07 06:57:02 +08:00
chore(): sync next with master
chore(): sync next with master
This commit is contained in:
@ -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);
|
||||
|
||||
@ -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"
|
||||
});
|
||||
|
||||
@ -544,6 +562,7 @@ export const IonSearchbar = /*@__PURE__*/ defineContainer<JSX.IonSearchbar>('ion
|
||||
'placeholder',
|
||||
'searchIcon',
|
||||
'showCancelButton',
|
||||
'showClearButton',
|
||||
'spellcheck',
|
||||
'type',
|
||||
'value',
|
||||
@ -557,7 +576,10 @@ export const IonSearchbar = /*@__PURE__*/ defineContainer<JSX.IonSearchbar>('ion
|
||||
],
|
||||
{
|
||||
"modelProp": "value",
|
||||
"modelUpdateEvent": "v-ionChange",
|
||||
"modelUpdateEvent": [
|
||||
"v-ionChange",
|
||||
"v-ion-change"
|
||||
],
|
||||
"externalModelUpdateEvent": "ionChange"
|
||||
});
|
||||
|
||||
@ -574,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"
|
||||
});
|
||||
|
||||
@ -587,7 +612,10 @@ export const IonSegmentButton = /*@__PURE__*/ defineContainer<JSX.IonSegmentButt
|
||||
],
|
||||
{
|
||||
"modelProp": "value",
|
||||
"modelUpdateEvent": "v-ionChange",
|
||||
"modelUpdateEvent": [
|
||||
"v-ionChange",
|
||||
"v-ion-change"
|
||||
],
|
||||
"externalModelUpdateEvent": "ionChange"
|
||||
});
|
||||
|
||||
@ -612,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"
|
||||
});
|
||||
|
||||
@ -705,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"
|
||||
});
|
||||
|
||||
@ -733,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"
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { defineComponent, h, ref } from 'vue';
|
||||
import { defineComponent, h, ref, VNode } from 'vue';
|
||||
|
||||
export interface OverlayProps {
|
||||
isOpen?: boolean;
|
||||
@ -21,28 +21,61 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
|
||||
isOpen && (await present(props))
|
||||
}
|
||||
|
||||
const onVnodeUpdated = async () => {
|
||||
const isOpen = props.isOpen;
|
||||
const onVnodeUpdated = async (node: VNode, prevNode: VNode) => {
|
||||
const isOpen = node.props!.isOpen;
|
||||
const prevIsOpen = prevNode.props!.isOpen;
|
||||
|
||||
/**
|
||||
* Do not do anything if this prop
|
||||
* did not change.
|
||||
*/
|
||||
if (isOpen === prevIsOpen) return;
|
||||
|
||||
if (isOpen) {
|
||||
await overlay.value?.present() || present(props);
|
||||
await present(props);
|
||||
} else {
|
||||
await overlay.value?.dismiss();
|
||||
overlay.value = undefined;
|
||||
await dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
const onVnodeBeforeUnmount = async () => {
|
||||
await overlay.value?.dismiss();
|
||||
await dismiss();
|
||||
}
|
||||
|
||||
const dismiss = async () => {
|
||||
if (!overlay.value) return;
|
||||
|
||||
await overlay.value;
|
||||
|
||||
overlay.value = overlay.value.dismiss();
|
||||
|
||||
await overlay.value;
|
||||
|
||||
overlay.value = undefined;
|
||||
}
|
||||
|
||||
const present = async (props: Readonly<Props>) => {
|
||||
/**
|
||||
* Do not open another instance
|
||||
* if one is already opened.
|
||||
*/
|
||||
if (overlay.value) {
|
||||
await overlay.value;
|
||||
}
|
||||
|
||||
if (overlay.value?.present) {
|
||||
await overlay.value.present();
|
||||
return;
|
||||
}
|
||||
|
||||
const component = slots.default && slots.default()[0];
|
||||
overlay.value = await controller.create({
|
||||
overlay.value = controller.create({
|
||||
...props,
|
||||
component
|
||||
});
|
||||
|
||||
overlay.value = await overlay.value;
|
||||
|
||||
eventListeners.forEach(eventListener => {
|
||||
overlay.value.addEventListener(eventListener.componentEv, () => {
|
||||
emit(eventListener.frameworkEv);
|
||||
@ -59,7 +92,8 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
|
||||
style: { display: 'none' },
|
||||
onVnodeMounted,
|
||||
onVnodeUpdated,
|
||||
onVnodeBeforeUnmount
|
||||
onVnodeBeforeUnmount,
|
||||
isOpen: props.isOpen
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user