react-material-overlay
Version:
A fully featured Material UI implementation of overlays like modals, alert dialogs, lightboxes, and bottom sheets featuring easy stack management and browser history integration
133 lines (102 loc) • 3.53 kB
text/typescript
import { isNull, omitBy } from 'lodash';
import type { Id, Notify } from '../../types';
import enhancedMerge from '../../utils/enhancedMerge';
import mergeClasses from '../../utils/mergeClasses';
import { isFn } from '../../utils/propValidator';
import RmoStack from '../RmoStack';
import { IAlertDialogDefaultOptions, IAlertDialogProps, INotValidatedAlertDialogProps } from './types';
export type ContainerObserver = ReturnType<typeof createContainerObserver>;
export function createContainerObserver(id: Id, containerDefaultOptions: IAlertDialogDefaultOptions) {
let alertDialogCount = 0;
let activeAlertDialogs: Id[] = [];
let snapshot: IAlertDialogProps[] = [];
let defaultOptions = containerDefaultOptions;
const alertDialogs = new Map<Id, IAlertDialogProps>();
const listeners = new Set<Notify>();
const observe = (notify: Notify) => {
listeners.add(notify);
return () => listeners.delete(notify);
};
const notify = () => {
snapshot = Array.from(alertDialogs.values());
listeners.forEach((cb) => cb());
};
function shouldIgnorePop(alertDialogId: Id) {
const lastActiveAlertDialogId = activeAlertDialogs.at(-1);
return lastActiveAlertDialogId !== alertDialogId;
}
const popAlertDialog = () => {
activeAlertDialogs = activeAlertDialogs.slice(0, -1);
notify();
};
const pushAlertDialog = async (alertDialog: IAlertDialogProps) => {
const { alertDialogId, onOpen } = alertDialog;
alertDialogs.set(alertDialogId, alertDialog);
activeAlertDialogs = [...activeAlertDialogs, alertDialogId];
notify();
if (isFn(onOpen)) {
onOpen();
}
};
const buildAlertDialog = (options: INotValidatedAlertDialogProps) => {
if (alertDialogs.has(options.alertDialogId)) {
throw new Error('alert dialog is duplicated!');
}
const { alertDialogId } = options;
const closeAlertDialog = async () => {
if (shouldIgnorePop(alertDialogId)) {
return;
}
popAlertDialog();
await RmoStack.pop({ id: alertDialogId, preventEventTriggering: true });
};
alertDialogCount++;
const { classes: defaultClasses, ...containerProps } = defaultOptions;
const { classes, ...alertDialogOptions } = options;
const alertDialogProps = {
...enhancedMerge(containerProps, omitBy(alertDialogOptions, isNull)),
alertDialogId,
containerId: id,
closeAlertDialog,
deleteAlertDialog() {
const alertDialogToRemove = alertDialogs.get(alertDialogId)!;
const { onClose } = alertDialogToRemove;
if (isFn(onClose)) {
onClose();
}
alertDialogs.delete(alertDialogId);
alertDialogCount--;
if (alertDialogCount < 0) {
alertDialogCount = 0;
}
notify();
}
} as IAlertDialogProps;
if (isFn(classes)) {
alertDialogProps.classes = classes(defaultClasses);
} else if (defaultClasses && classes) {
alertDialogProps.classes = mergeClasses(defaultClasses, classes);
} else if (defaultClasses) {
alertDialogProps.classes = defaultClasses;
} else {
alertDialogProps.classes = classes;
}
return alertDialogProps;
};
return {
id,
defaultOptions,
observe,
pushAlertDialog,
popAlertDialog,
get alertDialogCount() {
return alertDialogCount;
},
buildAlertDialog,
setDefaultOptions(d: IAlertDialogDefaultOptions) {
defaultOptions = d;
},
isAlertDialogActive: (id: Id) => activeAlertDialogs.some((v) => v === id),
getSnapshot: () => snapshot
};
}