react-snappy-modal
Version:
Flexible and easy-to-use modal library for React, supporting customizable dialogs with promise-based interactions.
181 lines (178 loc) • 5.29 kB
JavaScript
// src/context/SnappyModalContext.tsx
import { createContext, useEffect, useMemo } from "react";
// src/SnappyModal.tsx
import React from "react";
import "../SnappyModal-JGLWFQD3.css";
import { jsx } from "react/jsx-runtime";
var currentComponents = [];
var SnappyModal = class {
static isShow() {
return currentComponents.length > 0;
}
static getCurrentComponent(layer) {
return currentComponents.find((c) => c.options.layer === layer);
}
static removeModalProcess(layer) {
const index = currentComponents.findIndex((c) => c.options.layer === layer);
if (index === -1)
return;
currentComponents.splice(index, 1);
SnappyModalExternalStore.emitChange();
}
static getModalProcess() {
return currentComponents;
}
static close(value, layer = 0) {
const currentComponent = this.getCurrentComponent(layer);
currentComponent?.resolve(value);
this.removeModalProcess(layer);
}
static throw(thrower, layer = 0) {
const currentComponent = this.getCurrentComponent(layer);
currentComponent?.throw(thrower);
this.removeModalProcess(layer);
currentComponent?.throw(thrower);
}
static show(component, options) {
const dialogOptions = {
...defaultDialogOptions,
...options
};
return new Promise((resolve, reject) => {
currentComponents.push({
component: () => /* @__PURE__ */ jsx(React.Fragment, { children: component }),
options: dialogOptions,
resolve: (value) => {
resolve(value);
SnappyModalExternalStore.emitChange();
},
throw: (thrower) => {
reject(thrower);
SnappyModalExternalStore.emitChange();
}
});
currentComponents.sort((a, b) => a.options.layer - b.options.layer);
SnappyModalExternalStore.emitChange();
});
}
};
var defaultDialogOptions = {
allowOutsideClick: true,
allowScroll: false,
backdrop: true,
position: "center",
layer: 0
};
// src/context/useSnappyModalState.tsx
import { useSyncExternalStore } from "react";
var snappyModalListeners = [];
var snappyModalState = {
isShow: false,
modalProgress: []
};
function emitChange() {
for (const listener of snappyModalListeners) {
listener();
}
}
var SnappyModalExternalStore = {
emitChange: () => {
snappyModalState = {
isShow: SnappyModal.isShow(),
modalProgress: [...SnappyModal.getModalProcess()]
};
emitChange();
},
subscribe(listener) {
snappyModalListeners = [...snappyModalListeners, listener];
return () => {
snappyModalListeners = snappyModalListeners.filter((l) => l !== listener);
};
},
getSnappyModalState() {
return snappyModalState;
}
};
var useSnappyModalState = () => {
return useSyncExternalStore(
SnappyModalExternalStore.subscribe,
SnappyModalExternalStore.getSnappyModalState
);
};
// src/context/SnappyModalContext.tsx
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
var SnappyModalContext = createContext({});
var SnappyModalProvider = ({ children }) => {
const snappyModal = useSnappyModalState();
useEffect(() => {
if (!snappyModal.isShow)
return;
const htmlElement = document.getElementsByTagName("html")[0];
const pageX = window.scrollX;
const pageY = window.scrollY;
htmlElement.style.top = `-${pageY}px`;
htmlElement.style.left = `-${pageX}px`;
htmlElement.classList.add("not-scroll");
return () => {
htmlElement.style.top = "";
htmlElement.style.left = "";
htmlElement.classList.remove("not-scroll");
window.scrollTo({
left: pageX,
top: pageY
});
};
}, [snappyModal.isShow]);
const modalRendered = useMemo(() => {
const { modalProgress } = snappyModal;
return modalProgress.map((modal) => /* @__PURE__ */ jsx2("div", { ...assignModalOptions(modal.options), children: /* @__PURE__ */ jsx2(
"div",
{
className: `snappy-modal-content`,
onClick: (e) => e.stopPropagation(),
children: /* @__PURE__ */ jsx2(modal.component, {})
}
) }, modal.options.layer));
}, [snappyModal.modalProgress]);
return /* @__PURE__ */ jsxs(SnappyModalContext.Provider, { value: {}, children: [
children,
modalRendered
] });
};
function assignModalOptions(options) {
const domOptions = {
classList: ["snappy-modal-area"],
styleProperty: {},
onClick: (e) => {
}
};
if (options.backdrop) {
domOptions.classList.push("backdrop");
if (typeof options.backdrop === "string") {
domOptions.styleProperty["--snappy-modal-backdrop"] = options.backdrop;
}
}
if (options.position) {
domOptions.styleProperty["--snappy-modal-content-position"] = options.position;
}
if (options?.allowOutsideClick) {
domOptions.onClick = (e) => {
e.stopPropagation();
e.preventDefault();
SnappyModal.close(void 0, options.layer);
};
}
if (options.zIndex !== void 0) {
domOptions.styleProperty["--snappy-modal-z-index"] = options.zIndex.toString();
}
return {
className: domOptions.classList.join(" "),
style: domOptions.styleProperty,
onClick: domOptions.onClick
};
}
export {
SnappyModalProvider,
assignModalOptions
};
//# sourceMappingURL=SnappyModalContext.mjs.map