Skip to content

Commit

Permalink
fix(react,vue): backdrop for inline modal/popover overlay (#24453)
Browse files Browse the repository at this point in the history
Resolves #24449
  • Loading branch information
sean-perkins committed Jan 7, 2022
1 parent 388622f commit 77f8412
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 56 deletions.
38 changes: 20 additions & 18 deletions core/src/utils/overlays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,29 @@ export const prepareOverlay = <T extends HTMLIonOverlayElement>(el: T) => {
}
};

export const createOverlay = <T extends HTMLIonOverlayElement>(tagName: string, opts: object | undefined, customElement?: any, childrenCustomElements?: ChildCustomElementDefinition[]): Promise<T> => {
/* tslint:disable-next-line */
if (typeof window.customElements !== 'undefined') {
if (typeof (window as any) !== 'undefined' && window.customElements) {
if (!window.customElements.get(tagName)) {
window.customElements.define(tagName, customElement);
}
/**
* If the parent element has nested usage of custom elements,
* we need to manually define those custom elements.
*/
if (childrenCustomElements) {
for (const customElementDefinition of childrenCustomElements) {
if (!window.customElements.get(customElementDefinition.tagName)) {
window.customElements.define(customElementDefinition.tagName, customElementDefinition.customElement);
}
}
const registerOverlayComponents = (tagName: string, customElement: any, childrenCustomElements?: ChildCustomElementDefinition[]): Promise<any> => {
const { customElements } = window;
if (!customElements.get(tagName)) {
customElements.define(tagName, customElement);
}
/**
* If the parent element has nested usage of custom elements,
* we need to manually define those custom elements.
*/
if (childrenCustomElements) {
for (const customElementDefinition of childrenCustomElements) {
if (!customElements.get(customElementDefinition.tagName)) {
customElements.define(customElementDefinition.tagName, customElementDefinition.customElement);
}
}
}
return customElements.whenDefined(tagName);
}

return window.customElements.whenDefined(tagName).then(() => {
export const createOverlay = <T extends HTMLIonOverlayElement>(tagName: string, opts: object | undefined, customElement?: any, childrenCustomElements?: ChildCustomElementDefinition[]): Promise<T> => {
/* tslint:disable-next-line */
if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined') {
return registerOverlayComponents(tagName, customElement, childrenCustomElements).then(() => {
const element = document.createElement(tagName) as HTMLIonOverlayElement;
element.classList.add('overlay-hidden');

Expand Down
14 changes: 7 additions & 7 deletions packages/react/src/components/IonActionSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import {
ActionSheetOptions as ActionSheetOptionsCore,
actionSheetController as actionSheetControllerCore,
} from '@ionic/core/components';
import { IonActionSheet as IonActionSheetCmp } from '@ionic/core/components/ion-action-sheet.js';
import { defineCustomElement } from '@ionic/core/components/ion-action-sheet.js';

import { createOverlayComponent } from './createOverlayComponent';

export interface ActionSheetButton extends Omit<ActionSheetButtonCore, 'icon'> {
icon?:
| {
ios: string;
md: string;
}
| string;
| {
ios: string;
md: string;
}
| string;
}

export interface ActionSheetOptions extends Omit<ActionSheetOptionsCore, 'buttons'> {
Expand All @@ -30,4 +30,4 @@ const actionSheetController = {
export const IonActionSheet = /*@__PURE__*/ createOverlayComponent<
ActionSheetOptions,
HTMLIonActionSheetElement
>('ion-action-sheet', actionSheetController, IonActionSheetCmp);
>('ion-action-sheet', actionSheetController, defineCustomElement);
4 changes: 2 additions & 2 deletions packages/react/src/components/IonModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { JSX } from '@ionic/core/components';
import { IonModal as IonModalCmp } from '@ionic/core/components/ion-modal.js';
import { defineCustomElement } from '@ionic/core/components/ion-modal.js';

import { createInlineOverlayComponent } from './createInlineOverlayComponent'

export const IonModal = /*@__PURE__*/ createInlineOverlayComponent<
JSX.IonModal,
HTMLIonModalElement
>('ion-modal', IonModalCmp);
>('ion-modal', defineCustomElement);
4 changes: 2 additions & 2 deletions packages/react/src/components/IonPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { JSX } from '@ionic/core/components';
import { IonPopover as IonPopoverCmp } from '@ionic/core/components/ion-popover.js';
import { defineCustomElement } from '@ionic/core/components/ion-popover.js';

import { createInlineOverlayComponent } from './createInlineOverlayComponent'

export const IonPopover = /*@__PURE__*/ createInlineOverlayComponent<
JSX.IonPopover,
HTMLIonPopoverElement
>('ion-popover', IonPopoverCmp);
>('ion-popover', defineCustomElement);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
attachProps,
camelToDashCase,
dashToPascalCase,
defineCustomElement,
isCoveredByReact,
mergeRefs,
} from './react-component-lib/utils';
Expand All @@ -26,10 +25,11 @@ interface IonicReactInternalProps<ElementType> extends React.HTMLAttributes<Elem

export const createInlineOverlayComponent = <PropType, ElementType>(
tagName: string,
customElement?: any
defineCustomElement?: () => void
) => {
defineCustomElement(tagName, customElement);

if (defineCustomElement) {
defineCustomElement();
}
const displayName = dashToPascalCase(tagName);
const ReactComponent = class extends React.Component<IonicReactInternalProps<PropType>, InlineOverlayState> {
ref: React.RefObject<HTMLElement>;
Expand Down
8 changes: 5 additions & 3 deletions packages/react/src/components/createOverlayComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { OverlayEventDetail } from '@ionic/core/components';
import React from 'react';
import ReactDOM from 'react-dom';

import { attachProps, dashToPascalCase, defineCustomElement, setRef } from './react-component-lib/utils';
import { attachProps, dashToPascalCase, setRef } from './react-component-lib/utils';

interface OverlayElement extends HTMLElement {
present: () => Promise<void>;
Expand All @@ -24,9 +24,11 @@ export const createOverlayComponent = <
>(
tagName: string,
controller: { create: (options: any) => Promise<OverlayType> },
customElement?: any
defineCustomElement?: () => void
) => {
defineCustomElement(tagName, customElement);
if (defineCustomElement !== undefined) {
defineCustomElement();
}

const displayName = dashToPascalCase(tagName);
const didDismissEventName = `on${displayName}DidDismiss`;
Expand Down
6 changes: 4 additions & 2 deletions packages/vue/scripts/copy-overlays.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ function generateOverlays() {
const docsBlock = getDocsBlock(component.tag);
const props = getPropsFromDocsBlock(docsBlock);

componentImports.push(`import { ${component.name} as ${component.name}Cmp } from '@ionic/core/components/${component.tag}.js'`);
const defineCustomElementFn = `define${component.name}CustomElement`;

componentImports.push(`import { defineCustomElement as ${defineCustomElementFn} } from '@ionic/core/components/${component.tag}.js'`);

if (component.controller) {
controllerImports.push(component.controller);
Expand All @@ -55,7 +57,7 @@ function generateOverlays() {
const controllerParam = (component.controller) ? `, ${component.controller}` : '';

componentDefinitions.push(`
export const ${component.name} = /*@__PURE__*/ defineOverlayContainer<JSX.${component.name}>('${component.tag}', ${component.name}Cmp, [${props.join(', ')}]${controllerParam});
export const ${component.name} = /*@__PURE__*/ defineOverlayContainer<JSX.${component.name}>('${component.tag}', ${defineCustomElementFn}, [${props.join(', ')}]${controllerParam});
`);
});

Expand Down
28 changes: 14 additions & 14 deletions packages/vue/src/components/Overlays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ import {
toastController,
} from '@ionic/core/components';

import { IonActionSheet as IonActionSheetCmp } from '@ionic/core/components/ion-action-sheet.js'
import { IonAlert as IonAlertCmp } from '@ionic/core/components/ion-alert.js'
import { IonLoading as IonLoadingCmp } from '@ionic/core/components/ion-loading.js'
import { IonPicker as IonPickerCmp } from '@ionic/core/components/ion-picker.js'
import { IonToast as IonToastCmp } from '@ionic/core/components/ion-toast.js'
import { IonModal as IonModalCmp } from '@ionic/core/components/ion-modal.js'
import { IonPopover as IonPopoverCmp } from '@ionic/core/components/ion-popover.js'
import { defineCustomElement as defineIonActionSheetCustomElement } from '@ionic/core/components/ion-action-sheet.js'
import { defineCustomElement as defineIonAlertCustomElement } from '@ionic/core/components/ion-alert.js'
import { defineCustomElement as defineIonLoadingCustomElement } from '@ionic/core/components/ion-loading.js'
import { defineCustomElement as defineIonPickerCustomElement } from '@ionic/core/components/ion-picker.js'
import { defineCustomElement as defineIonToastCustomElement } from '@ionic/core/components/ion-toast.js'
import { defineCustomElement as defineIonModalCustomElement } from '@ionic/core/components/ion-modal.js'
import { defineCustomElement as defineIonPopoverCustomElement } from '@ionic/core/components/ion-popover.js'

import { defineOverlayContainer } from '../vue-component-lib/overlays';

export const IonActionSheet = /*@__PURE__*/ defineOverlayContainer<JSX.IonActionSheet>('ion-action-sheet', IonActionSheetCmp, ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'mode', 'subHeader', 'translucent'], actionSheetController);
export const IonActionSheet = /*@__PURE__*/ defineOverlayContainer<JSX.IonActionSheet>('ion-action-sheet', defineIonActionSheetCustomElement, ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'mode', 'subHeader', 'translucent'], actionSheetController);

export const IonAlert = /*@__PURE__*/ defineOverlayContainer<JSX.IonAlert>('ion-alert', IonAlertCmp, ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent'], alertController);
export const IonAlert = /*@__PURE__*/ defineOverlayContainer<JSX.IonAlert>('ion-alert', defineIonAlertCustomElement, ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent'], alertController);

export const IonLoading = /*@__PURE__*/ defineOverlayContainer<JSX.IonLoading>('ion-loading', IonLoadingCmp, ['animated', 'backdropDismiss', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'showBackdrop', 'spinner', 'translucent'], loadingController);
export const IonLoading = /*@__PURE__*/ defineOverlayContainer<JSX.IonLoading>('ion-loading', defineIonLoadingCustomElement, ['animated', 'backdropDismiss', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'showBackdrop', 'spinner', 'translucent'], loadingController);

export const IonPicker = /*@__PURE__*/ defineOverlayContainer<JSX.IonPicker>('ion-picker', IonPickerCmp, ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop'], pickerController);
export const IonPicker = /*@__PURE__*/ defineOverlayContainer<JSX.IonPicker>('ion-picker', defineIonPickerCustomElement, ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop'], pickerController);

export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', IonToastCmp, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController);
export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController);

export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', IonModalCmp, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'enterAnimation', 'handle', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']);
export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'enterAnimation', 'handle', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']);

export const IonPopover = /*@__PURE__*/ defineOverlayContainer<JSX.IonPopover>('ion-popover', IonPopoverCmp, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']);
export const IonPopover = /*@__PURE__*/ defineOverlayContainer<JSX.IonPopover>('ion-popover', defineIonPopoverCustomElement, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']);

11 changes: 7 additions & 4 deletions packages/vue/src/vue-component-lib/overlays.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { defineComponent, h, ref, VNode, onMounted } from 'vue';
import { defineCustomElement } from '../utils';

export interface OverlayProps {
isOpen?: boolean;
Expand All @@ -8,7 +7,7 @@ export interface OverlayProps {
const EMPTY_PROP = Symbol();
const DEFAULT_EMPTY_PROP = { default: EMPTY_PROP };

export const defineOverlayContainer = <Props extends object>(name: string, customElement: any, componentProps: string[] = [], controller?: any) => {
export const defineOverlayContainer = <Props extends object>(name: string, defineCustomElement: () => void, componentProps: string[] = [], controller?: any) => {

const createControllerComponent = () => {
return defineComponent<Props & OverlayProps>((props, { slots, emit }) => {
Expand All @@ -19,7 +18,9 @@ export const defineOverlayContainer = <Props extends object>(name: string, custo
{ componentEv: `${name}-did-dismiss`, frameworkEv: 'didDismiss' },
];

defineCustomElement(name, customElement);
if (defineCustomElement !== undefined) {
defineCustomElement();
}

const overlay = ref();
const onVnodeMounted = async () => {
Expand Down Expand Up @@ -131,7 +132,9 @@ export const defineOverlayContainer = <Props extends object>(name: string, custo
};
const createInlineComponent = () => {
return defineComponent((props, { slots }) => {
defineCustomElement(name, customElement);
if (defineCustomElement !== undefined) {
defineCustomElement();
}
const isOpen = ref(false);
const elementRef = ref();

Expand Down

0 comments on commit 77f8412

Please sign in to comment.