react-modal-global
Advanced tools
Comparing version 2.22.2023-alpha-2 to 2023.3.25-experimental-1
import EventEmitter from 'eventemitter3'; | ||
import { ReactElement, ReactNode, ComponentLifecycle } from 'react'; | ||
import { ReactElement, Component } from 'react'; | ||
import { HasRequiredKeys } from 'type-fest'; | ||
type ModalComponent<P = unknown> = ((props: P) => ReactElement | null) | (() => ReactElement | null); | ||
interface ModalState { | ||
isOpen: boolean; | ||
windows: ModalWindow[]; | ||
} | ||
/** | ||
* A modal component can be either a function component or a class component. | ||
*/ | ||
type ModalComponent<P = unknown> = ((props: P) => ReactElement | null) | (() => ReactElement | null) | (new (props: P) => Component<P>) | (new () => Component); | ||
interface ModalParams { | ||
@@ -10,3 +17,3 @@ /** | ||
* | ||
* @default 0 | ||
* @default nanoid() | ||
*/ | ||
@@ -33,14 +40,2 @@ id: string | number; | ||
} | ||
interface ModalWindow<P = unknown> { | ||
component: ModalComponent<ModalParams & P>; | ||
params: ModalParams & P; | ||
/** | ||
* Removes the modal from the queue. If | ||
*/ | ||
close: () => void; | ||
/** | ||
* Indicates that the `close` method has been called and the modal window is going to be removed. | ||
*/ | ||
isClosed: boolean; | ||
} | ||
/** | ||
@@ -64,6 +59,22 @@ * Gets either a tuple with required or optional parameters depending on whether `P` has any required keys. | ||
interface ModalState { | ||
isOpen: boolean; | ||
windows: ModalWindow[]; | ||
declare class ModalWindow<CustomParams = unknown> { | ||
component: ModalComponent<CustomParams>; | ||
params: ModalParams & CustomParams; | ||
closed: boolean; | ||
controller?: ModalController; | ||
private deffered; | ||
constructor(component: ModalComponent<CustomParams>, ...[params]: ModalWindowParams<CustomParams>); | ||
close(): void; | ||
/** | ||
* Can be used to wait for the modal to be closed before performing some action. | ||
* | ||
* @example | ||
* const modal = await modalController.open(PopupHello, { title: "Hello" }) | ||
* doAnyAction() | ||
*/ | ||
then(onfulfilled?: ((value: void) => void | PromiseLike<void>) | undefined | null, onrejected?: ((reason: unknown) => void | PromiseLike<void>) | undefined | null): PromiseLike<void>; | ||
serialize(): string; | ||
static deserialize(serialized: string): ModalWindow; | ||
} | ||
interface Events { | ||
@@ -74,76 +85,17 @@ add: [ModalWindow]; | ||
} | ||
/** | ||
* Controller for opening and closing modal windows. | ||
* | ||
* Can be used with `ModalContainer` or with custom implementation. | ||
*/ | ||
declare class ModalController { | ||
#private; | ||
static Instance: ModalController; | ||
protected windows: Set<ModalWindow>; | ||
protected events: EventEmitter<Events>; | ||
protected set isOpen(value: boolean); | ||
/** | ||
* Whether any modals are shown. | ||
*/ | ||
get isOpen(): boolean; | ||
protected windows: Set<ModalWindow>; | ||
protected events: EventEmitter<Events>; | ||
/** | ||
* Opens a modal window. Infers props from the component. | ||
* | ||
* - If the same modal window is already in the queue, it will be ignored. | ||
* - If the modal window is weak, it will be unmounted after closing. | ||
* - If the modal window is forked, it will be opened over all other modals. | ||
* - If the modal window is closable (be default `true`), it will be closed by clicking on the overlay. | ||
* - If the modal window is not closable, it will be closed only by calling internal `close` method. | ||
* | ||
* @param component Modal component. | ||
* @param params Modal params. | ||
* @returns Modal window and `PromiseLike`. | ||
* | ||
* @example | ||
* const modal = modalController.open(MyModal, { id: 1 }) | ||
* modal.closable // `true` by default | ||
* | ||
* modal.then(() => console.log("Modal was closed")) | ||
* modal.close() | ||
*/ | ||
open<P>(component: ModalComponent<P>, ...[modalParams]: ModalWindowParams<P>): ModalWindow<P> & PromiseLike<void>; | ||
/** | ||
* Replaces the last modal window in the queue with a new one. | ||
* | ||
* If the queue is empty, it will be added to the queue. | ||
*/ | ||
replace<P>(component: ModalComponent<P>, ...[params]: ModalWindowParams<P>): ModalWindow<P> & PromiseLike<void>; | ||
/** | ||
* Adds a modal window to the queue. | ||
* | ||
* - Controls whether the modal is open. | ||
* - Controls whether the order of windows. | ||
*/ | ||
private add; | ||
/** | ||
* Removes a modal window from the queue. | ||
*/ | ||
private remove; | ||
private findByComponent; | ||
private findById; | ||
/** | ||
* Closes all modals by its component (including forked) starting from the last one. | ||
*/ | ||
closeByComponent<Params>(component: ModalComponent<Params>, params?: ModalParams & Params): void; | ||
/** | ||
* Closes all modals by its id (including forked) starting from the last one. | ||
*/ | ||
closeById(id: ModalParams["id"]): void; | ||
/** | ||
* Closes all modals (including forked). | ||
*/ | ||
hide(): void; | ||
show(): void; | ||
open<P>(component: ModalComponent<P>, ...modalParams: ModalWindowParams<P>): ModalWindow<P>; | ||
close(modalWindow: ModalWindow): void; | ||
closeAll(): void; | ||
/** | ||
* Subscribes on event. | ||
* | ||
* @returns `unsubscribe` method | ||
*/ | ||
on<T extends keyof Events>(event: T, listener: (...args: Events[T]) => void): () => void; | ||
observe(callback: (state: ModalState) => void): () => void; | ||
} | ||
declare const Modal: ModalController; | ||
@@ -154,5 +106,3 @@ interface ModalContainerProps { | ||
*/ | ||
template?: (props: { | ||
children: ReactNode; | ||
}) => ReactElement; | ||
template?: ModalComponent; | ||
/** | ||
@@ -169,3 +119,2 @@ * Modal container class name. It will be used as a base for modifiers (will replace defaulted `"modal"`). | ||
} | ||
declare function ModalContainer(props: ModalContainerProps): JSX.Element; | ||
@@ -178,8 +127,8 @@ | ||
* It has 3 overloads: | ||
* 1. `useModalContext<ModalComponent>()` - infers the props from the class component type. | ||
* 2. `useModalContext<typeof ModalComponent>()` - infers the props from the function component type. | ||
* 3. `useModalContext<unknown>()` - infers any type besides the above. | ||
* 1. `useModalWindow<ModalComponent>()` - infers the props from the class component type. | ||
* 2. `useModalWindow<typeof ModalComponent>()` - infers the props from the function component type. | ||
* 3. `useModalWindow<unknown>()` - infers any type besides the above. | ||
*/ | ||
declare function useModalContext<T>(): ModalWindow<T extends ComponentLifecycle<infer P, unknown> | ((props: infer P) => ReactNode) ? P : T>; | ||
declare function useModalWindow<T>(): ModalWindow<T extends ModalComponent<infer Props> ? Props : T>; | ||
export { Modal, ModalContainer, ModalController, useModalContext }; | ||
export { ModalContainer, ModalController, ModalWindow, useModalWindow }; |
@@ -1,13 +0,11 @@ | ||
'use strict';Object.defineProperty(exports,"__esModule",{value:!0});var EventEmitter=require("eventemitter3"),jsxRuntime=require("react/jsx-runtime"),react=require("react");function _interopDefaultLegacy(a){return a&&"object"===typeof a&&"default"in a?a:{"default":a}}var EventEmitter__default=_interopDefaultLegacy(EventEmitter); | ||
'use strict';Object.defineProperty(exports,"__esModule",{value:!0});var EventEmitter=require("eventemitter3"),nanoid=require("nanoid"),jsxRuntime=require("react/jsx-runtime"),react=require("react");function _interopDefaultLegacy(a){return a&&"object"===typeof a&&"default"in a?a:{"default":a}}var EventEmitter__default=_interopDefaultLegacy(EventEmitter); | ||
function __classPrivateFieldGet(a,b,c,d){if("a"===c&&!d)throw new TypeError("Private accessor was defined without a getter");if("function"===typeof b?a!==b||!d:!b.has(a))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===c?d:"a"===c?d.call(a):d?d.value:b.get(a)} | ||
function __classPrivateFieldSet(a,b,c,d,e){if("m"===d)throw new TypeError("Private method is not writable");if("a"===d&&!e)throw new TypeError("Private accessor was defined without a setter");if("function"===typeof b?a!==b||!e:!b.has(a))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===d?e.call(a,c):e?e.value=c:b.set(a,c),c} | ||
function classWithModifiers(a,...b){b=b.filter(Boolean);if(!b.length)return a;b=b.map(c=>a+"--"+c);return a+" "+b.join(" ")}function serialize(a){function b(){let c=new WeakSet;return(d,e)=>{if("object"===typeof e&&null!==e){if(c.has(e))return;c.add(e)}return e}}return null==a?String(a):JSON.stringify(a,function(c,d){d=b()(c,d);c=d instanceof Function?d.name===c?d.toString():d.name:d;return c})} | ||
function stopPropagation(a){return({target:b,currentTarget:c})=>{b instanceof Element&&c instanceof Element&&b!==c||(null===a||void 0===a?void 0:a())}}var _ModalController_isOpen;let DEFAULT_PARAMS={id:0,closable:!0,weak:!1,fork:!1}; | ||
class ModalController{constructor(){_ModalController_isOpen.set(this,!1);this.windows=new Set;this.events=new EventEmitter__default["default"]}set isOpen(a){__classPrivateFieldSet(this,_ModalController_isOpen,a,"f")}get isOpen(){return __classPrivateFieldGet(this,_ModalController_isOpen,"f")}open(a,...[b]){let c=()=>{},d=new Promise(g=>c=g);b=Object.assign(Object.assign({},DEFAULT_PARAMS),b);let e={component:a,params:b,close:()=>{this.remove(e);c()},isClosed:!1};this.add(e);return Object.assign(Object.assign({}, | ||
e),{then(g,k){return d.then(g,k)}})}replace(a,...[b]){let c=[...this.windows].at(-1);null!=c&&this.windows.delete(c);return this.open(a,b)}add(a){!1===this.isOpen&&0<this.windows.size&&this.windows.clear();this.isOpen=!0;this.windows.add(a);this.events.emit("add",a)}remove(a){if(this.windows.has(a)){if(1===this.windows.size&&(this.isOpen=!1,!a.params.weak)){this.events.emit("update");return}this.windows.delete(a);this.events.emit("remove",a)}}findByComponent(a,b){return[...this.windows].filter(c=> | ||
c.component!==a?!1:null!=b?serialize(b)===serialize(c.params):!0)}findById(a){return[...this.windows].filter(b=>b.params.id===a)}closeByComponent(a,b){this.findByComponent(a,b).forEach(c=>c.close())}closeById(a){this.findById(a).forEach(b=>b.close())}closeAll(){this.isOpen=!1;this.events.emit("update")}observe(a){let b=()=>{const c={isOpen:this.isOpen,windows:[...this.windows]};a(c)};this.events.on("add",b);this.events.on("remove",b);this.events.on("update",b);return()=>{this.events.off("add",b); | ||
this.events.off("remove",b);this.events.off("update",b)}}}_ModalController_isOpen=new WeakMap;let Modal=new ModalController,modalContext=react.createContext(null),DEFAULT_STATE={isOpen:!1,windows:[]}; | ||
function ModalContainer(a){var b,c;let [d,e]=react.useState(DEFAULT_STATE);react.useEffect(()=>(a.controller||Modal).observe(e),[a.controller]);let g=a.className||"modal",k=a.template||react.Fragment,f=d.windows.at(-1),l=(null===(b=null===f||void 0===f?void 0:f.params)||void 0===b?0:b.closable)?stopPropagation(f.close):void 0;b=f?Object.assign(Object.assign({},f),{isClosed:!d.isOpen}):null;return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("div",Object.assign({className:classWithModifiers(g, | ||
d.isOpen&&"active"),"aria-modal":!0,"aria-hidden":!d.isOpen},{children:jsxRuntime.jsx("div",Object.assign({className:g+"__container",onClick:l},{children:jsxRuntime.jsx(k,{children:jsxRuntime.jsx(modalContext.Provider,Object.assign({value:b},{children:(null===f||void 0===f?void 0:f.component)&&react.createElement(f.component,Object.assign({},f.params,{key:null===(c=null===f||void 0===f?void 0:f.params)||void 0===c?void 0:c.id}))}))})}))})),[...d.windows].reverse().filter(h=>h.params.fork).map(h=> | ||
jsxRuntime.jsx("div",Object.assign({className:classWithModifiers(g,"active"),"aria-modal":!0},{children:jsxRuntime.jsx("div",Object.assign({className:g+"__container",onClick:h.params.closable?stopPropagation(h.close):void 0},{children:jsxRuntime.jsx(modalContext.Provider,Object.assign({value:h},{children:jsxRuntime.jsx(h.component,Object.assign({},h.params))}))}))}),h.params.id))]})} | ||
function useModalContext(){let a=react.useContext(modalContext);if(!a)throw Error("ModalError: useModalContext must be used within a modalContext");return a}exports.Modal=Modal;exports.ModalContainer=ModalContainer;exports.ModalController=ModalController;exports.useModalContext=useModalContext | ||
class Deffered{constructor(){this.resolve=()=>{throw Error("Deffered.resolve is not defined");};this.reject=()=>{throw Error("Deffered.reject is not defined");};this.promise=new Promise((a,b)=>{this.resolve=a;this.reject=b})}}function classWithModifiers(a,...b){b=b.filter(Boolean);if(!b.length)return a;b=b.map(c=>a+"--"+c);return a+" "+b.join(" ")} | ||
function serialize(a){function b(){let c=new WeakSet;return(d,e)=>{if("object"===typeof e&&null!==e){if(c.has(e))return;c.add(e)}return e}}return null==a?String(a):JSON.stringify(a,function(c,d){d=b()(c,d);c=d instanceof Function?d.name===c?d.toString():d.name:d;return c})}function stopPropagation(a){return({target:b,currentTarget:c})=>{b instanceof Element&&c instanceof Element&&b!==c||(null===a||void 0===a?void 0:a())}}let DEFAULT_PARAMS={id:-1,closable:!0,weak:!1,fork:!1}; | ||
class ModalWindow{constructor(a,...[b]){this.component=a;this.params=Object.assign(Object.assign(Object.assign({},DEFAULT_PARAMS),{id:nanoid.nanoid()}),b);this.closed=!1;this.deffered=new Deffered}close(){this.closed=!0;this.deffered.resolve()}then(a,b){return this.deffered.promise.then(a,b)}serialize(){return serialize([this.component,this.params,this.closed])}static deserialize(a){let [b,c,d]=JSON.parse(a);a=new ModalWindow(b,c);a.closed=d;return a}}var _ModalController_isOpen; | ||
class ModalController{constructor(){this.windows=new Set;this.events=new EventEmitter__default["default"];_ModalController_isOpen.set(this,!1)}set isOpen(a){__classPrivateFieldSet(this,_ModalController_isOpen,a,"f")}get isOpen(){return __classPrivateFieldGet(this,_ModalController_isOpen,"f")}hide(){this.isOpen=!1;this.events.emit("update")}show(){this.isOpen=!0;this.events.emit("update")}open(a,...b){a=new ModalWindow(a,...b);a.controller=this;this.windows.add(a);this.events.emit("add",a);return a}close(a){this.windows.delete(a); | ||
this.events.emit("remove",a)}closeAll(){this.windows.forEach(a=>this.close(a))}on(a,b){this.events.on(a,b);return()=>{this.events.off(a,b)}}observe(a){let b=()=>{const c={isOpen:this.isOpen,windows:[...this.windows]};a(c)};b();this.events.on("add",b);this.events.on("remove",b);this.events.on("update",b);return()=>{this.events.off("add",b);this.events.off("remove",b);this.events.off("update",b)}}}_ModalController_isOpen=new WeakMap;ModalController.Instance=new ModalController; | ||
let modalContext=react.createContext(null),DEFAULT_STATE={isOpen:!1,windows:[]}; | ||
function ModalContainer(a){let [b,c]=react.useState(DEFAULT_STATE);react.useEffect(()=>(a.controller||ModalController.Instance).observe(c),[a.controller]);let d=a.className||"modal",e=a.template||react.Fragment;return jsxRuntime.jsx("div",Object.assign({className:classWithModifiers(d,b.isOpen&&"active"),"aria-modal":!0,"aria-hidden":!b.isOpen},{children:b.windows.map(f=>jsxRuntime.jsx("div",Object.assign({className:d+"__container",onClick:f.params.closable?stopPropagation(f.close):void 0},{children:jsxRuntime.jsx(e, | ||
{children:jsxRuntime.jsx(modalContext.Provider,Object.assign({value:f},{children:jsxRuntime.jsx(f.component,Object.assign({},f.params))}))})}),f.params.id))}))}function useModalWindow(){let a=react.useContext(modalContext);if(!a)throw Error("ModalError: useModalWindow must be used within a modal context");return a}exports.ModalContainer=ModalContainer;exports.ModalController=ModalController;exports.ModalWindow=ModalWindow;exports.useModalWindow=useModalWindow |
{ | ||
"name": "react-modal-global", | ||
"version": "2.22.2023-alpha-2", | ||
"version": "2023.3.25-experimental-1", | ||
"description": "Highly reusable React Modal that can be run from useEffect.", | ||
@@ -15,3 +15,4 @@ "main": "dist/index.ts", | ||
"dependencies": { | ||
"eventemitter3": "^5.0.0" | ||
"eventemitter3": "^5.0.0", | ||
"nanoid": "^4.0.1" | ||
}, | ||
@@ -18,0 +19,0 @@ "devDependencies": { |
Sorry, the diff of this file is not supported yet
22980
3
144
+ Addednanoid@^4.0.1
+ Addednanoid@4.0.2(transitive)