react-imperial-modal 1.1.5 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -10
- package/dist/Modal.d.ts +7 -0
- package/dist/ModalProvider.d.ts +4 -0
- package/dist/constants.d.ts +7 -0
- package/dist/index.d.ts +5 -0
- package/dist/react-imperial-modal.d.ts +2 -0
- package/dist/react-imperial-modal.js +142 -0
- package/dist/react-imperial-modal.umd.cjs +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/useModal.d.ts +2 -0
- package/package.json +42 -27
- package/.babelrc +0 -8
- package/build/index.js +0 -2
- package/build/index.js.map +0 -1
- package/src/Modal.tsx +0 -50
- package/src/ModalContext.ts +0 -11
- package/src/ModalProvider.tsx +0 -116
- package/src/index.ts +0 -2
- package/src/types.ts +0 -21
- package/src/useModal.ts +0 -46
- package/tsconfig.json +0 -10
- package/webpack.config.js +0 -49
package/README.md
CHANGED
|
@@ -92,16 +92,6 @@ const Prompt = function(props) {
|
|
|
92
92
|
<button onClick={() => close(promptValue)}>ok</button>
|
|
93
93
|
</div>
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
const Alert = function(props) {
|
|
97
|
-
const { message, close } = props
|
|
98
|
-
|
|
99
|
-
return <div>
|
|
100
|
-
<h1>Alert</h1>
|
|
101
|
-
<p>{message}</p>
|
|
102
|
-
<button onClick={close}>ok</button>
|
|
103
|
-
</div>
|
|
104
|
-
}
|
|
105
95
|
```
|
|
106
96
|
|
|
107
97
|
you can:
|
package/dist/Modal.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ModalEntry } from './types';
|
|
2
|
+
type InternalModalProps<T, P> = {
|
|
3
|
+
entry: ModalEntry<T, P>;
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const Modal: <T, P>({ className, entry }: InternalModalProps<T, P>) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default Modal;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { ModalContextValue, ModalProviderProps } from './types';
|
|
3
|
+
export declare const ModalContext: React.Context<ModalContextValue>;
|
|
4
|
+
export declare const ModalProvider: ({ children, config }: ModalProviderProps) => import("react/jsx-runtime").JSX.Element;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { jsx as C, jsxs as O } from "react/jsx-runtime";
|
|
2
|
+
import I, { useRef as P, useContext as A, useLayoutEffect as K, useCallback as u, useState as L, useMemo as R } from "react";
|
|
3
|
+
const q = () => {
|
|
4
|
+
let s, t;
|
|
5
|
+
return { promise: new Promise((i, l) => {
|
|
6
|
+
s = i, t = l;
|
|
7
|
+
}), resolve: s, reject: t };
|
|
8
|
+
}, D = "Escape", N = [
|
|
9
|
+
"a[href]:not([tabindex='-1'])",
|
|
10
|
+
"area[href]:not([tabindex='-1'])",
|
|
11
|
+
"input:not([disabled]):not([tabindex='-1'])",
|
|
12
|
+
"select:not([disabled]):not([tabindex='-1'])",
|
|
13
|
+
"textarea:not([disabled]):not([tabindex='-1'])",
|
|
14
|
+
"button:not([disabled]):not([tabindex='-1'])",
|
|
15
|
+
"iframe:not([tabindex='-1'])",
|
|
16
|
+
"[tabindex]:not([tabindex='-1'])",
|
|
17
|
+
"[contentEditable=true]:not([tabindex='-1'])"
|
|
18
|
+
].join(", "), B = function({ className: s, entry: t }) {
|
|
19
|
+
const { role: a = "dialog", label: i, labelledby: l, componentProps: d, Component: M } = t, n = P(null), { removeModal: m } = A(S);
|
|
20
|
+
K(() => {
|
|
21
|
+
requestAnimationFrame(() => {
|
|
22
|
+
var r;
|
|
23
|
+
n.current && (n.current.showModal(), (r = n.current.querySelector(N)) == null || r.focus());
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
const f = u(
|
|
27
|
+
(r) => {
|
|
28
|
+
r.key === D && !t.ignoreEscape && m(t.instanceId);
|
|
29
|
+
},
|
|
30
|
+
[t, m]
|
|
31
|
+
);
|
|
32
|
+
return /* @__PURE__ */ C(
|
|
33
|
+
"dialog",
|
|
34
|
+
{
|
|
35
|
+
ref: n,
|
|
36
|
+
role: a,
|
|
37
|
+
"aria-label": i,
|
|
38
|
+
"aria-labelledby": l,
|
|
39
|
+
className: s,
|
|
40
|
+
onKeyDown: f,
|
|
41
|
+
children: /* @__PURE__ */ C(
|
|
42
|
+
M,
|
|
43
|
+
{
|
|
44
|
+
close: t.closeModal,
|
|
45
|
+
resolve: t.resolveModal,
|
|
46
|
+
reject: t.rejectModal,
|
|
47
|
+
...d
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}, g = () => {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Attempted to call useModal outside of modal context. Make sure your component is inside ModalProvider."
|
|
55
|
+
);
|
|
56
|
+
}, S = I.createContext({
|
|
57
|
+
addModal: g,
|
|
58
|
+
removeModal: g,
|
|
59
|
+
resolveModal: g,
|
|
60
|
+
rejectModal: g
|
|
61
|
+
}), F = {
|
|
62
|
+
bodyOpenClass: "modal-open",
|
|
63
|
+
modalContainerClass: "modals",
|
|
64
|
+
modalClass: "modal"
|
|
65
|
+
}, w = document.documentElement, k = document.body, Y = ({ children: s, config: t = {} }) => {
|
|
66
|
+
const [a, i] = L([]), l = R(() => ({ ...F, ...t }), [t]), d = P(null), M = P([]), n = a.at(-1), m = u(() => {
|
|
67
|
+
var e;
|
|
68
|
+
k.classList.add(l.bodyOpenClass), (e = d == null ? void 0 : d.current) == null || e.setAttribute("aria-hidden", "true"), w.style.overflow = "hidden";
|
|
69
|
+
}, [l]), f = u(() => {
|
|
70
|
+
var e;
|
|
71
|
+
k.classList.remove(l.bodyOpenClass), (e = d == null ? void 0 : d.current) == null || e.removeAttribute("aria-hidden"), w.style.overflow = "";
|
|
72
|
+
}, [l]), r = u(
|
|
73
|
+
(e) => {
|
|
74
|
+
i((o) => o.includes(e) ? o : (o.length === 0 && m(), M.current.push(document.activeElement), [...o, e]));
|
|
75
|
+
},
|
|
76
|
+
[m]
|
|
77
|
+
), p = u(
|
|
78
|
+
(e) => {
|
|
79
|
+
i((o) => {
|
|
80
|
+
const E = o.find((y) => y.instanceId === e), c = e === void 0 ? n : E, v = o.length === 1, j = M.current.pop();
|
|
81
|
+
return !c || !o.includes(c) ? o : (v && j && w.contains(j) && j.focus(), v && f(), o.filter((y) => c !== y));
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
[f, n]
|
|
85
|
+
), b = u(
|
|
86
|
+
(e, o) => {
|
|
87
|
+
const E = a.find((v) => v.instanceId === o), c = o === void 0 ? n : E;
|
|
88
|
+
c == null || c.resolveModal(e || null);
|
|
89
|
+
},
|
|
90
|
+
[a, n]
|
|
91
|
+
), x = u(
|
|
92
|
+
(e, o) => {
|
|
93
|
+
const E = a.find((v) => v.instanceId === o), c = o === void 0 ? n : E;
|
|
94
|
+
c == null || c.rejectModal(e || null);
|
|
95
|
+
},
|
|
96
|
+
[a, n]
|
|
97
|
+
), h = R(
|
|
98
|
+
() => ({ addModal: r, removeModal: p, resolveModal: b, rejectModal: x }),
|
|
99
|
+
[r, p, b, x]
|
|
100
|
+
);
|
|
101
|
+
return /* @__PURE__ */ O(S.Provider, { value: h, children: [
|
|
102
|
+
/* @__PURE__ */ C("div", { ref: d, children: s }),
|
|
103
|
+
/* @__PURE__ */ C("div", { className: l.modalContainerClass, children: a.map((e) => /* @__PURE__ */ C(
|
|
104
|
+
B,
|
|
105
|
+
{
|
|
106
|
+
className: l.modalClass,
|
|
107
|
+
entry: e
|
|
108
|
+
},
|
|
109
|
+
e.instanceId
|
|
110
|
+
)) })
|
|
111
|
+
] });
|
|
112
|
+
};
|
|
113
|
+
let T = 1;
|
|
114
|
+
const _ = () => {
|
|
115
|
+
const s = A(S);
|
|
116
|
+
return [u(
|
|
117
|
+
(a, i, l = !1, d, M, n = "dialog") => {
|
|
118
|
+
const { promise: m, resolve: f, reject: r } = q(), p = (T++ / 5).toString(32), b = () => s.removeModal(p), x = Object.assign(m, {
|
|
119
|
+
instanceId: p,
|
|
120
|
+
Component: a,
|
|
121
|
+
componentProps: i,
|
|
122
|
+
ignoreEscape: l,
|
|
123
|
+
labelledby: M,
|
|
124
|
+
label: d,
|
|
125
|
+
role: n,
|
|
126
|
+
resolveModal: (h) => {
|
|
127
|
+
f(h), b();
|
|
128
|
+
},
|
|
129
|
+
rejectModal: (h) => {
|
|
130
|
+
r(h), b();
|
|
131
|
+
},
|
|
132
|
+
closeModal: b
|
|
133
|
+
});
|
|
134
|
+
return s.addModal(x), x;
|
|
135
|
+
},
|
|
136
|
+
[s]
|
|
137
|
+
), s.resolveModal, s.rejectModal];
|
|
138
|
+
};
|
|
139
|
+
export {
|
|
140
|
+
Y as ModalProvider,
|
|
141
|
+
_ as useModal
|
|
142
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(u,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],c):(u=typeof globalThis<"u"?globalThis:u||self,c(u["react-imperial-modal"]={},u.jsxRuntime,u.React))})(this,(function(u,c,e){"use strict";const O=()=>{let l,n;return{promise:new Promise((f,d)=>{l=f,n=d}),resolve:l,reject:n}},q="Escape",A=["a[href]:not([tabindex='-1'])","area[href]:not([tabindex='-1'])","input:not([disabled]):not([tabindex='-1'])","select:not([disabled]):not([tabindex='-1'])","textarea:not([disabled]):not([tabindex='-1'])","button:not([disabled]):not([tabindex='-1'])","iframe:not([tabindex='-1'])","[tabindex]:not([tabindex='-1'])","[contentEditable=true]:not([tabindex='-1'])"].join(", "),T=function({className:l,entry:n}){const{role:a="dialog",label:f,labelledby:d,componentProps:r,Component:M}=n,s=e.useRef(null),{removeModal:b}=e.useContext(g);e.useLayoutEffect(()=>{requestAnimationFrame(()=>{var m;s.current&&(s.current.showModal(),(m=s.current.querySelector(A))==null||m.focus())})},[]);const v=e.useCallback(m=>{m.key===q&&!n.ignoreEscape&&b(n.instanceId)},[n,b]);return c.jsx("dialog",{ref:s,role:a,"aria-label":f,"aria-labelledby":d,className:l,onKeyDown:v,children:c.jsx(M,{close:n.closeModal,resolve:n.resolveModal,reject:n.rejectModal,...r})})},y=()=>{throw new Error("Attempted to call useModal outside of modal context. Make sure your component is inside ModalProvider.")},g=e.createContext({addModal:y,removeModal:y,resolveModal:y,rejectModal:y}),I={bodyOpenClass:"modal-open",modalContainerClass:"modals",modalClass:"modal"},k=document.documentElement,S=document.body,K=({children:l,config:n={}})=>{const[a,f]=e.useState([]),d=e.useMemo(()=>({...I,...n}),[n]),r=e.useRef(null),M=e.useRef([]),s=a.at(-1),b=e.useCallback(()=>{var o;S.classList.add(d.bodyOpenClass),(o=r==null?void 0:r.current)==null||o.setAttribute("aria-hidden","true"),k.style.overflow="hidden"},[d]),v=e.useCallback(()=>{var o;S.classList.remove(d.bodyOpenClass),(o=r==null?void 0:r.current)==null||o.removeAttribute("aria-hidden"),k.style.overflow=""},[d]),m=e.useCallback(o=>{f(t=>t.includes(o)?t:(t.length===0&&b(),M.current.push(document.activeElement),[...t,o]))},[b]),h=e.useCallback(o=>{f(t=>{const E=t.find(P=>P.instanceId===o),i=o===void 0?s:E,x=t.length===1,w=M.current.pop();return!i||!t.includes(i)?t:(x&&w&&k.contains(w)&&w.focus(),x&&v(),t.filter(P=>i!==P))})},[v,s]),p=e.useCallback((o,t)=>{const E=a.find(x=>x.instanceId===t),i=t===void 0?s:E;i==null||i.resolveModal(o||null)},[a,s]),C=e.useCallback((o,t)=>{const E=a.find(x=>x.instanceId===t),i=t===void 0?s:E;i==null||i.rejectModal(o||null)},[a,s]),j=e.useMemo(()=>({addModal:m,removeModal:h,resolveModal:p,rejectModal:C}),[m,h,p,C]);return c.jsxs(g.Provider,{value:j,children:[c.jsx("div",{ref:r,children:l}),c.jsx("div",{className:d.modalContainerClass,children:a.map(o=>c.jsx(T,{className:d.modalClass,entry:o},o.instanceId))})]})};let L=1;const D=()=>{const l=e.useContext(g);return[e.useCallback((a,f,d=!1,r,M,s="dialog")=>{const{promise:b,resolve:v,reject:m}=O(),h=(L++/5).toString(32),p=()=>l.removeModal(h),C=Object.assign(b,{instanceId:h,Component:a,componentProps:f,ignoreEscape:d,labelledby:M,label:r,role:s,resolveModal:j=>{v(j),p()},rejectModal:j=>{m(j),p()},closeModal:p});return l.addModal(C),C},[l]),l.resolveModal,l.rejectModal]};u.ModalProvider=K,u.useModal=D,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export interface ModalProps<T> {
|
|
3
|
+
resolve: (val: T | null) => void;
|
|
4
|
+
reject: (reason?: unknown) => void;
|
|
5
|
+
close: () => void;
|
|
6
|
+
}
|
|
7
|
+
export type ModalEntry<T, P> = Promise<T | null> & {
|
|
8
|
+
instanceId: string;
|
|
9
|
+
Component: React.ComponentType<P & ModalProps<T>>;
|
|
10
|
+
componentProps: P;
|
|
11
|
+
resolveModal: (val: T | null) => void;
|
|
12
|
+
rejectModal: (reason?: unknown) => void;
|
|
13
|
+
closeModal: () => void;
|
|
14
|
+
ignoreEscape?: boolean;
|
|
15
|
+
labelledby?: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
role?: string;
|
|
18
|
+
};
|
|
19
|
+
export type ModalProviderConfig = {
|
|
20
|
+
bodyOpenClass?: string;
|
|
21
|
+
modalContainerClass?: string;
|
|
22
|
+
modalClass?: string;
|
|
23
|
+
};
|
|
24
|
+
export type ModalProviderProps = {
|
|
25
|
+
children?: ReactNode;
|
|
26
|
+
config?: ModalProviderConfig;
|
|
27
|
+
};
|
|
28
|
+
export type ModalContextValue = {
|
|
29
|
+
addModal: (entry: ModalEntry<unknown, unknown>) => void;
|
|
30
|
+
removeModal: (instanceId?: string) => void;
|
|
31
|
+
resolveModal: (val: unknown | null, instanceId?: string) => void;
|
|
32
|
+
rejectModal: (val: unknown | null, instanceId?: string) => void;
|
|
33
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ModalEntry, ModalProps } from './types';
|
|
2
|
+
export declare const useModal: () => readonly [<T, P>(Component: React.ComponentType<P & ModalProps<T>>, componentProps: P, ignoreEscape?: boolean, label?: string, labelledby?: string, role?: string) => ModalEntry<T, P>, (val: unknown | null, instanceId?: string) => void, (val: unknown | null, instanceId?: string) => void];
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-imperial-modal",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "imperative modal api for react",
|
|
5
5
|
"author": "Greg Archer (greg.taff@gmail.com)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/nihlton/react-imperial-modal#readme",
|
|
8
|
-
"main": "build/index.js",
|
|
9
8
|
"repository": {
|
|
10
9
|
"type": "git",
|
|
11
10
|
"url": "git+https://github.com/nihlton/react-imperial-modal.git"
|
|
@@ -18,34 +17,50 @@
|
|
|
18
17
|
"modal",
|
|
19
18
|
"imperative"
|
|
20
19
|
],
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/react-imperial-modal.umd.cjs",
|
|
25
|
+
"module": "./dist/react-imperial-modal.js",
|
|
26
|
+
"types": "./dist/react-imperial-modal.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/react-imperial-modal.d.ts",
|
|
30
|
+
"import": "./dist/react-imperial-modal.js",
|
|
31
|
+
"require": "./dist/react-imperial-modal.umd.cjs"
|
|
32
|
+
}
|
|
24
33
|
},
|
|
25
34
|
"peerDependencies": {
|
|
26
|
-
"react": "
|
|
27
|
-
"react-dom": "
|
|
35
|
+
"react": ">=17.0.0",
|
|
36
|
+
"react-dom": ">=17.0.0"
|
|
28
37
|
},
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"@babel/preset-react": "^7.0.0",
|
|
37
|
-
"babel-cli": "^6.26.0",
|
|
38
|
-
"babel-loader": "^8.0.6",
|
|
39
|
-
"html-webpack-plugin": "^3.2.0",
|
|
40
|
-
"webpack": "^4.35.0",
|
|
41
|
-
"webpack-cli": "^3.0.4",
|
|
42
|
-
"ts-loader": "^7.0.0"
|
|
38
|
+
"dependencies": {},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"dev": "vite",
|
|
41
|
+
"build": "tsc && vite build",
|
|
42
|
+
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
|
43
|
+
"lint:fix": "eslint . --fix",
|
|
44
|
+
"typecheck": "tsc --noEmit"
|
|
43
45
|
},
|
|
44
|
-
"
|
|
45
|
-
"@types/
|
|
46
|
-
"@types/
|
|
47
|
-
"@types/react": "^
|
|
48
|
-
"@
|
|
49
|
-
"
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^25.0.3",
|
|
48
|
+
"@types/react": "^19.2.0",
|
|
49
|
+
"@types/react-dom": "^19.2.0",
|
|
50
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
51
|
+
"eslint": "^9.39.2",
|
|
52
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
53
|
+
"eslint-plugin-react": "^7.37.5",
|
|
54
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
55
|
+
"react": "^19.2.0",
|
|
56
|
+
"react-dom": "^19.2.0",
|
|
57
|
+
"sass": "^1.95.1",
|
|
58
|
+
"simpl-grid": "2.0.14",
|
|
59
|
+
"source-map-loader": "^0.2.4",
|
|
60
|
+
"ts-loader": "^7.0.0",
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"typescript-eslint": "^8.50.1",
|
|
63
|
+
"vite": "^6.4.1",
|
|
64
|
+
"vite-plugin-dts": "^4.5.4"
|
|
50
65
|
}
|
|
51
66
|
}
|
package/.babelrc
DELETED
package/build/index.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
module.exports=function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=2)}([function(e,t){e.exports=require("react")},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ModalContext=void 0;const o=n(0),r=e=>{throw new Error("Attempted to call useModal outside of modal context. Make sure your component is inside ModalProvider.")};t.ModalContext=o.createContext({addModal:r,removeModal:r})},function(e,t,n){"use strict";var o=this&&this.__createBinding||(Object.create?function(e,t,n,o){void 0===o&&(o=n),Object.defineProperty(e,o,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,o){void 0===o&&(o=n),e[o]=t[n]}),r=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||t.hasOwnProperty(n)||o(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),r(n(3),t),r(n(5),t)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ModalProvider=void 0;const o=n(0),r=n(1),l=n(4),{useRef:a,useState:d,useCallback:s,useMemo:i}=o,u={bodyOpenClass:"modal-open",modalShadeClass:"modal-shade",modalContainerClass:"modals",modalClass:"modal"},c=[];t.ModalProvider=({children:e,config:t={},appElement:n=(()=>{})})=>{const[f,m]=d([]),b=Object.assign(Object.assign({},u),t),v=a(),p=s(e=>{c.push(document.activeElement),0===f.length&&(()=>{const e=n()||(null==v?void 0:v.current);document.documentElement.style.overflow="hidden",document.body.classList.add(b.bodyOpenClass),e.setAttribute("aria-hidden","true")})(),f.includes(e)?console.warn("tried to open a modal that was already opened"):m(t=>[...t,e])},[f]),y=s(e=>{const t=c.pop();document.documentElement.contains(t)&&t.focus(),1===f.length&&(()=>{var e;const t=n()||(null==v?void 0:v.current);document.documentElement.style.overflow="",null===(e=null===document||void 0===document?void 0:document.body)||void 0===e||e.classList.remove(b.bodyOpenClass),t.removeAttribute("aria-hidden")})(),f.includes(e)?m(t=>{const n=[...t];return n.splice(n.indexOf(e),1),n}):console.warn("tried to close a modal that isn't open")},[f]),x=i(()=>({addModal:p,removeModal:y}),[f]),M=e=>{e.resolver(),y(e)};return o.createElement(r.ModalContext.Provider,{value:x},o.createElement(o.Fragment,null,o.createElement("div",{ref:v},e),f.length>0&&o.createElement("div",{className:b.modalContainerClass,onKeyDown:e=>{const t=f.slice(-1)[0];"Escape"===e.key&&t.userDismiss&&M(t)}},f.map((e,t)=>o.createElement(l.default,{key:"modal-"+t,className:b.modalClass,label:e.label,role:e.role},e.modal)),o.createElement("div",{className:b.modalShadeClass,onClick:()=>{f.slice(-1)[0].userDismiss&&M(f.slice(-1)[0])}}))))}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),{useRef:r,useEffect:l}=o,a=["a[href]:not([tabindex='-1'])","area[href]:not([tabindex='-1'])","input:not([disabled]):not([tabindex='-1'])","select:not([disabled]):not([tabindex='-1'])","textarea:not([disabled]):not([tabindex='-1'])","button:not([disabled]):not([tabindex='-1'])","iframe:not([tabindex='-1'])","[tabindex]:not([tabindex='-1'])","[contentEditable=true]:not([tabindex='-1'])"].join(", ");t.default=function(e){const{className:t,children:n,label:d,role:s}=e,i=r();l(()=>{u([0,1])},[i]);const u=e=>{var t;const n=(Array.from(null===(t=null==i?void 0:i.current)||void 0===t?void 0:t.querySelectorAll(a))||[]).slice(...e)[0];n&&n.focus()};return o.createElement(o.Fragment,null,o.createElement("div",{tabIndex:0,onFocus:()=>u([-1])}),o.createElement("div",{role:s,"aria-label":d,ref:i,className:t},n),o.createElement("div",{tabIndex:0,onFocus:()=>u([0,1])}))}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.useModal=t.usePrevious=void 0;const o=n(0),r=n(1);function l(e){const t=o.useRef();return o.useEffect(()=>{t.current=e},[e]),t.current}t.usePrevious=l,t.useModal=()=>{const[e,t]=o.useState([]),n=l(e)||[],a=o.useContext(r.ModalContext);o.useEffect(()=>{const t=e.filter(e=>!n.includes(e)),o=n.filter(t=>!e.includes(t));t.forEach(e=>{a.addModal(e)}),o.forEach(e=>{a.removeModal(e)})},[e]);return[(e,n="",o="dialog",r=!0)=>{let l;const a=new Promise(e=>{l=e}),d={modal:e,resolver:l,label:n,role:o,userDismiss:r};return t(e=>[...e,d]),a},(e,n)=>{t(t=>{const o=t.find(t=>t.modal===e),r=[...t];return r.splice(r.indexOf(o),1),o.resolver(n),r})}]}}]);
|
|
2
|
-
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///external \"react\"","webpack:///./src/ModalContext.ts","webpack:///./src/index.ts","webpack:///./src/ModalProvider.tsx","webpack:///./src/Modal.tsx","webpack:///./src/useModal.ts"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","require","useWithoutProvider","modalEntry","Error","ModalContext","React","createContext","addModal","removeModal","useRef","useState","useCallback","useMemo","defaultConfig","bodyOpenClass","modalShadeClass","modalContainerClass","modalClass","previouslyFocusedElements","ModalProvider","children","config","appElement","modalEntries","setModalEntries","configuration","appContainer","push","document","activeElement","length","ariaTarget","current","documentElement","style","overflow","body","classList","add","setAttribute","modalSetup","includes","console","warn","openModals","previouslyFocusedElement","pop","contains","focus","remove","removeAttribute","modalTakeDown","newModals","splice","indexOf","contextValue","internalClose","entry","resolver","Provider","Fragment","ref","className","onKeyDown","event","topModal","slice","userDismiss","map","label","role","modal","onClick","useEffect","focusableSelector","join","props","modalRef","handleTabBoundary","indexes","element","Array","from","querySelectorAll","tabIndex","onFocus","usePrevious","useModal","localModalEntries","setLocalModalEntries","prevEntries","context","useContext","addedModals","filter","removedModals","forEach","modalPromise","Promise","resolve","currentModalEntries","result","thisEntry","find","newModalEntries"],"mappings":"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","file":"../build/index.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 2);\n","module.exports = require(\"react\");","import * as React from 'react'\r\nimport {ModalEntry} from \"./types\";\r\n\r\nconst useWithoutProvider = (modalEntry : ModalEntry) : void => {\r\n throw new Error( 'Attempted to call useModal outside of modal context. Make sure your component is inside ModalProvider.' )\r\n}\r\n\r\nexport const ModalContext = React.createContext({\r\n addModal: useWithoutProvider,\r\n removeModal: useWithoutProvider\r\n})\r\n","export * from './ModalProvider'\r\nexport * from './useModal'\r\n","import * as React from 'react'\r\nimport { ReactElement } from 'react'\r\nimport { ModalContext } from './ModalContext'\r\nimport Modal from './Modal'\r\n\r\nimport { ModalEntry, ModalProviderProps } from './types'\r\n\r\nconst { useRef, useState, useCallback, useMemo } = React\r\n\r\nconst defaultConfig = {\r\n bodyOpenClass: 'modal-open',\r\n modalShadeClass: 'modal-shade',\r\n modalContainerClass: 'modals',\r\n modalClass: 'modal'\r\n}\r\n\r\nconst previouslyFocusedElements : HTMLElement[] = []\r\nconst ESC_KEY = 'Escape'\r\n\r\n\r\nexport const ModalProvider = ({ children, config = {}, appElement = () => {} }: ModalProviderProps ) : ReactElement => {\r\n const [ modalEntries, setModalEntries ] = useState([])\r\n const configuration = {...defaultConfig, ...config}\r\n const appContainer = useRef()\r\n \r\n const modalSetup = () : void => {\r\n // showing first modal\r\n const ariaTarget = appElement() || appContainer?.current\r\n document.documentElement.style.overflow = 'hidden'\r\n document.body.classList.add(configuration.bodyOpenClass)\r\n ariaTarget.setAttribute('aria-hidden', 'true');\r\n }\r\n \r\n const modalTakeDown = () : void => {\r\n // removing last modal\r\n const ariaTarget = appElement() || appContainer?.current\r\n document.documentElement.style.overflow = ''\r\n document?.body?.classList.remove(configuration.bodyOpenClass)\r\n ariaTarget.removeAttribute('aria-hidden');\r\n }\r\n \r\n const addModal = useCallback((modalEntry : ModalEntry) : void => {\r\n // remember where focus was\r\n previouslyFocusedElements.push(document.activeElement as HTMLInputElement)\r\n \r\n if (modalEntries.length === 0) {\r\n modalSetup()\r\n }\r\n \r\n if (modalEntries.includes(modalEntry)) {\r\n console.warn('tried to open a modal that was already opened')\r\n } else {\r\n setModalEntries(openModals => [...openModals, modalEntry ])\r\n }\r\n }, [modalEntries])\r\n \r\n const removeModal = useCallback((modalEntry : ModalEntry) : void => {\r\n // set focus back\r\n const previouslyFocusedElement:HTMLElement = previouslyFocusedElements.pop()\r\n if (document.documentElement.contains(previouslyFocusedElement)) {\r\n previouslyFocusedElement.focus()\r\n }\r\n \r\n if (modalEntries.length === 1) {\r\n modalTakeDown()\r\n }\r\n \r\n if (modalEntries.includes(modalEntry)) {\r\n setModalEntries(openModals => {\r\n const newModals = [...openModals]\r\n newModals.splice(newModals.indexOf(modalEntry), 1)\r\n return newModals\r\n })\r\n } else {\r\n console.warn(`tried to close a modal that isn't open`)\r\n }\r\n }, [modalEntries])\r\n \r\n const contextValue = useMemo(() => ({ addModal, removeModal }), [modalEntries]);\r\n\r\n const handleShade = () => {\r\n const topModal = modalEntries.slice(-1)[0]\r\n if (topModal.userDismiss) {\r\n internalClose(modalEntries.slice(-1)[0])\r\n }\r\n }\r\n\r\n const handleKey = (event : React.KeyboardEvent) : void => {\r\n const topModal = modalEntries.slice(-1)[0]\r\n if (event.key === ESC_KEY && topModal.userDismiss) {\r\n internalClose(topModal)\r\n }\r\n }\r\n\r\n const internalClose = (entry: ModalEntry) : void => {\r\n entry.resolver()\r\n removeModal(entry)\r\n }\r\n \r\n return <ModalContext.Provider value={contextValue}>\r\n <React.Fragment>\r\n <div ref={appContainer}>{children}</div>\r\n {modalEntries.length > 0 && <div className={configuration.modalContainerClass} onKeyDown={handleKey}>\r\n {modalEntries.map((modalEntry, i) => (\r\n <Modal\r\n key={`modal-${i}`}\r\n className={configuration.modalClass}\r\n label={modalEntry.label}\r\n role={modalEntry.role}\r\n >{modalEntry.modal}</Modal>\r\n ))}\r\n <div className={configuration.modalShadeClass} onClick={handleShade} />\r\n </div>}\r\n </React.Fragment>\r\n </ModalContext.Provider>\r\n}\r\n","import * as React from 'react'\r\nimport {ReactChildren, ReactElement} from \"react\";\r\nconst { useRef, useEffect } = React\r\n\r\nconst focusableSelector = [\r\n 'a[href]:not([tabindex=\\'-1\\'])',\r\n 'area[href]:not([tabindex=\\'-1\\'])',\r\n 'input:not([disabled]):not([tabindex=\\'-1\\'])',\r\n 'select:not([disabled]):not([tabindex=\\'-1\\'])',\r\n 'textarea:not([disabled]):not([tabindex=\\'-1\\'])',\r\n 'button:not([disabled]):not([tabindex=\\'-1\\'])',\r\n 'iframe:not([tabindex=\\'-1\\'])',\r\n '[tabindex]:not([tabindex=\\'-1\\'])',\r\n '[contentEditable=true]:not([tabindex=\\'-1\\'])'\r\n].join(', ')\r\n\r\ntype ModalProps = {\r\n className?: string,\r\n children?: ReactChildren,\r\n label?: string,\r\n role?: string,\r\n}\r\n\r\nconst Modal = function (props : ModalProps): ReactElement {\r\n const { className, children, label, role } = props\r\n const modalRef = useRef<HTMLDivElement>()\r\n \r\n useEffect(() => {\r\n handleTabBoundary([0, 1])\r\n }, [ modalRef ])\r\n \r\n const handleTabBoundary = (indexes : number[]) : void => {\r\n const focusableElements = Array.from(modalRef?.current?.querySelectorAll<HTMLElement>(focusableSelector)) || []\r\n const element = focusableElements.slice(...indexes)[0]\r\n \r\n if (element) { element.focus() }\r\n }\r\n \r\n return <React.Fragment>\r\n <div tabIndex={0} onFocus={() => handleTabBoundary([-1])}/>\r\n <div\r\n role={role}\r\n aria-label={label}\r\n ref={modalRef}\r\n className={className}>{children}</div>\r\n <div tabIndex={0} onFocus={() => handleTabBoundary([0, 1])}/>\r\n </React.Fragment>\r\n}\r\n\r\nexport default Modal\r\n","import {useRef, useState, useEffect, useContext, ReactElement} from 'react'\r\nimport { ModalContext } from './ModalContext'\r\nimport {ModalEntry} from \"./types\";\r\n\r\nexport function usePrevious(value : any) : any {\r\n const ref = useRef()\r\n useEffect(() => { ref.current = value }, [value])\r\n \r\n return ref.current\r\n}\r\n\r\nexport const useModal = () => {\r\n const [ localModalEntries, setLocalModalEntries ] = useState([])\r\n const prevEntries: ModalEntry[] = usePrevious(localModalEntries) || []\r\n \r\n const context = useContext(ModalContext);\r\n \r\n useEffect(() => {\r\n const addedModals = localModalEntries.filter(modalEntry => !prevEntries.includes(modalEntry))\r\n const removedModals = prevEntries.filter(modalEntry => !localModalEntries.includes(modalEntry))\r\n \r\n addedModals.forEach(modalEntry => { context.addModal(modalEntry) })\r\n removedModals.forEach(modalEntry => { context.removeModal(modalEntry) })\r\n }, [ localModalEntries ])\r\n \r\n const open = (modal : ReactElement, label: string = '', role: string = 'dialog', userDismiss: boolean = true): Promise<any> => {\r\n let resolver: () => any\r\n const modalPromise = new Promise((resolve) => { resolver = resolve })\r\n const modalEntry = {modal, resolver, label, role, userDismiss}\r\n setLocalModalEntries(currentModalEntries => [...currentModalEntries, modalEntry ])\r\n \r\n return modalPromise\r\n }\r\n \r\n const close = (modal: ReactElement, result: any) : void => {\r\n setLocalModalEntries(currentModalEntries => {\r\n const thisEntry = currentModalEntries.find(entry => entry.modal === modal)\r\n const newModalEntries = [...currentModalEntries]\r\n newModalEntries.splice(newModalEntries.indexOf(thisEntry), 1)\r\n thisEntry.resolver(result)\r\n return newModalEntries\r\n })\r\n }\r\n \r\n return [ open, close ]\r\n}\r\n"],"sourceRoot":""}
|
package/src/Modal.tsx
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import {ReactChildren, ReactElement} from "react";
|
|
3
|
-
const { useRef, useEffect } = React
|
|
4
|
-
|
|
5
|
-
const focusableSelector = [
|
|
6
|
-
'a[href]:not([tabindex=\'-1\'])',
|
|
7
|
-
'area[href]:not([tabindex=\'-1\'])',
|
|
8
|
-
'input:not([disabled]):not([tabindex=\'-1\'])',
|
|
9
|
-
'select:not([disabled]):not([tabindex=\'-1\'])',
|
|
10
|
-
'textarea:not([disabled]):not([tabindex=\'-1\'])',
|
|
11
|
-
'button:not([disabled]):not([tabindex=\'-1\'])',
|
|
12
|
-
'iframe:not([tabindex=\'-1\'])',
|
|
13
|
-
'[tabindex]:not([tabindex=\'-1\'])',
|
|
14
|
-
'[contentEditable=true]:not([tabindex=\'-1\'])'
|
|
15
|
-
].join(', ')
|
|
16
|
-
|
|
17
|
-
type ModalProps = {
|
|
18
|
-
className?: string,
|
|
19
|
-
children?: ReactChildren,
|
|
20
|
-
label?: string,
|
|
21
|
-
role?: string,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const Modal = function (props : ModalProps): ReactElement {
|
|
25
|
-
const { className, children, label, role } = props
|
|
26
|
-
const modalRef = useRef<HTMLDivElement>()
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
handleTabBoundary([0, 1])
|
|
30
|
-
}, [ modalRef ])
|
|
31
|
-
|
|
32
|
-
const handleTabBoundary = (indexes : number[]) : void => {
|
|
33
|
-
const focusableElements = Array.from(modalRef?.current?.querySelectorAll<HTMLElement>(focusableSelector)) || []
|
|
34
|
-
const element = focusableElements.slice(...indexes)[0]
|
|
35
|
-
|
|
36
|
-
if (element) { element.focus() }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return <React.Fragment>
|
|
40
|
-
<div tabIndex={0} onFocus={() => handleTabBoundary([-1])}/>
|
|
41
|
-
<div
|
|
42
|
-
role={role}
|
|
43
|
-
aria-label={label}
|
|
44
|
-
ref={modalRef}
|
|
45
|
-
className={className}>{children}</div>
|
|
46
|
-
<div tabIndex={0} onFocus={() => handleTabBoundary([0, 1])}/>
|
|
47
|
-
</React.Fragment>
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default Modal
|
package/src/ModalContext.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import {ModalEntry} from "./types";
|
|
3
|
-
|
|
4
|
-
const useWithoutProvider = (modalEntry : ModalEntry) : void => {
|
|
5
|
-
throw new Error( 'Attempted to call useModal outside of modal context. Make sure your component is inside ModalProvider.' )
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const ModalContext = React.createContext({
|
|
9
|
-
addModal: useWithoutProvider,
|
|
10
|
-
removeModal: useWithoutProvider
|
|
11
|
-
})
|
package/src/ModalProvider.tsx
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { ReactElement } from 'react'
|
|
3
|
-
import { ModalContext } from './ModalContext'
|
|
4
|
-
import Modal from './Modal'
|
|
5
|
-
|
|
6
|
-
import { ModalEntry, ModalProviderProps } from './types'
|
|
7
|
-
|
|
8
|
-
const { useRef, useState, useCallback, useMemo } = React
|
|
9
|
-
|
|
10
|
-
const defaultConfig = {
|
|
11
|
-
bodyOpenClass: 'modal-open',
|
|
12
|
-
modalShadeClass: 'modal-shade',
|
|
13
|
-
modalContainerClass: 'modals',
|
|
14
|
-
modalClass: 'modal'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const previouslyFocusedElements : HTMLElement[] = []
|
|
18
|
-
const ESC_KEY = 'Escape'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export const ModalProvider = ({ children, config = {}, appElement = () => {} }: ModalProviderProps ) : ReactElement => {
|
|
22
|
-
const [ modalEntries, setModalEntries ] = useState([])
|
|
23
|
-
const configuration = {...defaultConfig, ...config}
|
|
24
|
-
const appContainer = useRef()
|
|
25
|
-
|
|
26
|
-
const modalSetup = () : void => {
|
|
27
|
-
// showing first modal
|
|
28
|
-
const ariaTarget = appElement() || appContainer?.current
|
|
29
|
-
document.documentElement.style.overflow = 'hidden'
|
|
30
|
-
document.body.classList.add(configuration.bodyOpenClass)
|
|
31
|
-
ariaTarget.setAttribute('aria-hidden', 'true');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const modalTakeDown = () : void => {
|
|
35
|
-
// removing last modal
|
|
36
|
-
const ariaTarget = appElement() || appContainer?.current
|
|
37
|
-
document.documentElement.style.overflow = ''
|
|
38
|
-
document?.body?.classList.remove(configuration.bodyOpenClass)
|
|
39
|
-
ariaTarget.removeAttribute('aria-hidden');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const addModal = useCallback((modalEntry : ModalEntry) : void => {
|
|
43
|
-
// remember where focus was
|
|
44
|
-
previouslyFocusedElements.push(document.activeElement as HTMLInputElement)
|
|
45
|
-
|
|
46
|
-
if (modalEntries.length === 0) {
|
|
47
|
-
modalSetup()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (modalEntries.includes(modalEntry)) {
|
|
51
|
-
console.warn('tried to open a modal that was already opened')
|
|
52
|
-
} else {
|
|
53
|
-
setModalEntries(openModals => [...openModals, modalEntry ])
|
|
54
|
-
}
|
|
55
|
-
}, [modalEntries])
|
|
56
|
-
|
|
57
|
-
const removeModal = useCallback((modalEntry : ModalEntry) : void => {
|
|
58
|
-
// set focus back
|
|
59
|
-
const previouslyFocusedElement:HTMLElement = previouslyFocusedElements.pop()
|
|
60
|
-
if (document.documentElement.contains(previouslyFocusedElement)) {
|
|
61
|
-
previouslyFocusedElement.focus()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (modalEntries.length === 1) {
|
|
65
|
-
modalTakeDown()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (modalEntries.includes(modalEntry)) {
|
|
69
|
-
setModalEntries(openModals => {
|
|
70
|
-
const newModals = [...openModals]
|
|
71
|
-
newModals.splice(newModals.indexOf(modalEntry), 1)
|
|
72
|
-
return newModals
|
|
73
|
-
})
|
|
74
|
-
} else {
|
|
75
|
-
console.warn(`tried to close a modal that isn't open`)
|
|
76
|
-
}
|
|
77
|
-
}, [modalEntries])
|
|
78
|
-
|
|
79
|
-
const contextValue = useMemo(() => ({ addModal, removeModal }), [modalEntries]);
|
|
80
|
-
|
|
81
|
-
const handleShade = () => {
|
|
82
|
-
const topModal = modalEntries.slice(-1)[0]
|
|
83
|
-
if (topModal.userDismiss) {
|
|
84
|
-
internalClose(modalEntries.slice(-1)[0])
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const handleKey = (event : React.KeyboardEvent) : void => {
|
|
89
|
-
const topModal = modalEntries.slice(-1)[0]
|
|
90
|
-
if (event.key === ESC_KEY && topModal.userDismiss) {
|
|
91
|
-
internalClose(topModal)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const internalClose = (entry: ModalEntry) : void => {
|
|
96
|
-
entry.resolver()
|
|
97
|
-
removeModal(entry)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return <ModalContext.Provider value={contextValue}>
|
|
101
|
-
<React.Fragment>
|
|
102
|
-
<div ref={appContainer}>{children}</div>
|
|
103
|
-
{modalEntries.length > 0 && <div className={configuration.modalContainerClass} onKeyDown={handleKey}>
|
|
104
|
-
{modalEntries.map((modalEntry, i) => (
|
|
105
|
-
<Modal
|
|
106
|
-
key={`modal-${i}`}
|
|
107
|
-
className={configuration.modalClass}
|
|
108
|
-
label={modalEntry.label}
|
|
109
|
-
role={modalEntry.role}
|
|
110
|
-
>{modalEntry.modal}</Modal>
|
|
111
|
-
))}
|
|
112
|
-
<div className={configuration.modalShadeClass} onClick={handleShade} />
|
|
113
|
-
</div>}
|
|
114
|
-
</React.Fragment>
|
|
115
|
-
</ModalContext.Provider>
|
|
116
|
-
}
|
package/src/index.ts
DELETED
package/src/types.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import {ReactChildren, ReactElement} from 'react'
|
|
2
|
-
|
|
3
|
-
export type ModalEntry = {
|
|
4
|
-
modal: ReactElement,
|
|
5
|
-
resolver: () => Promise<any>,
|
|
6
|
-
label?: string,
|
|
7
|
-
role?: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type ModalProviderConfig = {
|
|
11
|
-
bodyOpenClass?: string
|
|
12
|
-
modalShadeClass?: string
|
|
13
|
-
modalContainerClass?: string
|
|
14
|
-
modalClass?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type ModalProviderProps = {
|
|
18
|
-
children?: ReactChildren
|
|
19
|
-
config?: ModalProviderConfig
|
|
20
|
-
appElement?: () => HTMLElement | void
|
|
21
|
-
}
|
package/src/useModal.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {useRef, useState, useEffect, useContext, ReactElement} from 'react'
|
|
2
|
-
import { ModalContext } from './ModalContext'
|
|
3
|
-
import {ModalEntry} from "./types";
|
|
4
|
-
|
|
5
|
-
export function usePrevious(value : any) : any {
|
|
6
|
-
const ref = useRef()
|
|
7
|
-
useEffect(() => { ref.current = value }, [value])
|
|
8
|
-
|
|
9
|
-
return ref.current
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const useModal = () => {
|
|
13
|
-
const [ localModalEntries, setLocalModalEntries ] = useState([])
|
|
14
|
-
const prevEntries: ModalEntry[] = usePrevious(localModalEntries) || []
|
|
15
|
-
|
|
16
|
-
const context = useContext(ModalContext);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
const addedModals = localModalEntries.filter(modalEntry => !prevEntries.includes(modalEntry))
|
|
20
|
-
const removedModals = prevEntries.filter(modalEntry => !localModalEntries.includes(modalEntry))
|
|
21
|
-
|
|
22
|
-
addedModals.forEach(modalEntry => { context.addModal(modalEntry) })
|
|
23
|
-
removedModals.forEach(modalEntry => { context.removeModal(modalEntry) })
|
|
24
|
-
}, [ localModalEntries ])
|
|
25
|
-
|
|
26
|
-
const open = (modal : ReactElement, label: string = '', role: string = 'dialog', userDismiss: boolean = true): Promise<any> => {
|
|
27
|
-
let resolver: () => any
|
|
28
|
-
const modalPromise = new Promise((resolve) => { resolver = resolve })
|
|
29
|
-
const modalEntry = {modal, resolver, label, role, userDismiss}
|
|
30
|
-
setLocalModalEntries(currentModalEntries => [...currentModalEntries, modalEntry ])
|
|
31
|
-
|
|
32
|
-
return modalPromise
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const close = (modal: ReactElement, result: any) : void => {
|
|
36
|
-
setLocalModalEntries(currentModalEntries => {
|
|
37
|
-
const thisEntry = currentModalEntries.find(entry => entry.modal === modal)
|
|
38
|
-
const newModalEntries = [...currentModalEntries]
|
|
39
|
-
newModalEntries.splice(newModalEntries.indexOf(thisEntry), 1)
|
|
40
|
-
thisEntry.resolver(result)
|
|
41
|
-
return newModalEntries
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return [ open, close ]
|
|
46
|
-
}
|
package/tsconfig.json
DELETED
package/webpack.config.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
mode: 'production',
|
|
5
|
-
entry: './src/index.ts',
|
|
6
|
-
output: {
|
|
7
|
-
path: path.resolve(__dirname, 'build'),
|
|
8
|
-
filename: '../build/index.js',
|
|
9
|
-
libraryTarget: 'commonjs2'
|
|
10
|
-
},
|
|
11
|
-
devtool: 'source-map',
|
|
12
|
-
resolve: {
|
|
13
|
-
// Add '.ts' and '.tsx' as resolvable extensions.
|
|
14
|
-
extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js'],
|
|
15
|
-
},
|
|
16
|
-
module: {
|
|
17
|
-
rules: [
|
|
18
|
-
{
|
|
19
|
-
test: /\.ts(x?)$/,
|
|
20
|
-
exclude: /node_modules/,
|
|
21
|
-
use: [
|
|
22
|
-
{
|
|
23
|
-
loader: "ts-loader"
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
enforce: "pre",
|
|
29
|
-
test: /\.js$/,
|
|
30
|
-
loader: "source-map-loader"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
test: /\.js$/,
|
|
34
|
-
include: path.resolve(__dirname, 'src'),
|
|
35
|
-
exclude: /(node_modules|bower_components|build)/,
|
|
36
|
-
use: {
|
|
37
|
-
loader: 'babel-loader',
|
|
38
|
-
options: {
|
|
39
|
-
presets: ["@babel/env"],
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
]
|
|
44
|
-
},
|
|
45
|
-
externals: {
|
|
46
|
-
'react': 'commonjs react',
|
|
47
|
-
'react-dom': 'commonjs react-dom'
|
|
48
|
-
}
|
|
49
|
-
};
|