@4i/modal-manager 1.1.21 → 1.1.30

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.
@@ -1,16 +1,19 @@
1
1
  import React from "react";
2
2
  export type ModalList = {
3
- [key: string]: React.ComponentType;
3
+ [key: string]: React.ComponentType<any>;
4
4
  };
5
5
  interface ModalProviderProps {
6
- modalList: any;
6
+ modalList: ModalList;
7
7
  isOverflow?: boolean;
8
8
  className?: string;
9
9
  backdropClassName?: string;
10
- onModalStateChange?: (modalState: boolean, data: TData[], names: string[]) => void;
10
+ onModalStateChange?: (modalState: boolean, data: ModalData[], names: string[]) => void;
11
11
  }
12
- type TData = {
13
- [key: string]: any;
12
+ type ModalData = {
13
+ id: string;
14
+ name: string;
15
+ payload: any;
16
+ options?: any;
14
17
  };
15
- declare const ModalProvider: ({ modalList, isOverflow, className, backdropClassName, onModalStateChange, }: ModalProviderProps) => null;
18
+ declare const ModalProvider: React.FC<ModalProviderProps>;
16
19
  export default ModalProvider;
@@ -81,121 +81,122 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
81
81
  };
82
82
  Object.defineProperty(exports, "__esModule", { value: true });
83
83
  var react_1 = __importStar(require("react"));
84
- var ModalManager_1 = __importStar(require("../utils/ModalManager"));
85
- var react_dom_1 = require("react-dom");
84
+ var ModalManager_1 = require("../utils/ModalManager");
86
85
  var ModalProvider = function (_a) {
87
- var modalList = _a.modalList, isOverflow = _a.isOverflow, className = _a.className, backdropClassName = _a.backdropClassName, onModalStateChange = _a.onModalStateChange;
88
- var _b = (0, react_1.useState)([]), data = _b[0], setData = _b[1];
89
- var _c = (0, react_1.useState)([]), names = _c[0], setNames = _c[1];
90
- var modalRef = (0, react_1.useRef)([]);
91
- var applyCloseStyles = function (index) {
86
+ var modalList = _a.modalList, _b = _a.isOverflow, isOverflow = _b === void 0 ? true : _b, _c = _a.className, className = _c === void 0 ? "" : _c, _d = _a.backdropClassName, backdropClassName = _d === void 0 ? "" : _d, onModalStateChange = _a.onModalStateChange;
87
+ var _e = (0, react_1.useState)([]), modals = _e[0], setModals = _e[1];
88
+ var modalRefs = (0, react_1.useRef)(new Map());
89
+ var applyCloseStyles = function (modalIndex) {
92
90
  return new Promise(function (resolve) {
93
- var modal = document.querySelector("[data-index=\"".concat(index, "\"]"));
94
- if (!modal)
91
+ var modalElement = document.querySelector("[data-modal-index=\"".concat(modalIndex, "\"]"));
92
+ if (!modalElement) {
93
+ resolve(false);
95
94
  return;
96
- modal.classList.add("closing");
95
+ }
96
+ modalElement.classList.add("modal_closing");
97
97
  setTimeout(function () {
98
98
  resolve(true);
99
- }, 150);
99
+ }, 300);
100
100
  });
101
101
  };
102
102
  (0, react_1.useEffect)(function () {
103
- if (!onModalStateChange)
104
- return;
105
- var modalState = data.length !== 0;
106
- onModalStateChange(modalState, data, names);
107
- // eslint-disable-next-line react-hooks/exhaustive-deps
108
- }, [data, names]);
103
+ if (onModalStateChange && modals.length >= 0) {
104
+ var data = modals.map(function (modal) { return modal.payload; });
105
+ var names = modals.map(function (modal) { return modal.name; });
106
+ onModalStateChange(modals.length > 0, data, names);
107
+ }
108
+ }, [modals, onModalStateChange]);
109
109
  (0, react_1.useEffect)(function () {
110
- var handleOpenModal = function (name, data) {
111
- setData(function (prev) { return __spreadArray(__spreadArray([], prev, true), [data], false); });
112
- setNames(function (prev) { return __spreadArray(__spreadArray([], prev, true), [name], false); });
113
- if (isOverflow) {
114
- if (typeof document === "undefined")
115
- return;
116
- document.body.style.overflow = "hidden";
117
- }
110
+ // Toggle body overflow
111
+ if (typeof document !== "undefined") {
112
+ document.body.style.overflow =
113
+ isOverflow && modals.length > 0 ? "hidden" : "";
114
+ }
115
+ }, [modals.length, isOverflow]);
116
+ (0, react_1.useEffect)(function () {
117
+ var handleOpenModal = function (name, payload, options) {
118
+ var id = "modal-".concat(payload.modalId || Date.now(), "-").concat(Math.random()
119
+ .toString(36)
120
+ .substring(2, 9));
121
+ setModals(function (prevModals) { return __spreadArray(__spreadArray([], prevModals, true), [
122
+ { id: id, name: name, payload: payload, options: options },
123
+ ], false); });
118
124
  };
119
125
  var handleClose = function (position) { return __awaiter(void 0, void 0, void 0, function () {
126
+ var i, indexToRemove_1;
120
127
  return __generator(this, function (_a) {
121
128
  switch (_a.label) {
122
129
  case 0:
123
- console.log("position", position);
124
- return [4 /*yield*/, applyCloseStyles(position)];
130
+ console.log("POS", position);
131
+ if (!(position === "all")) return [3 /*break*/, 5];
132
+ i = modals.length - 1;
133
+ _a.label = 1;
125
134
  case 1:
135
+ if (!(i >= 0)) return [3 /*break*/, 4];
136
+ return [4 /*yield*/, applyCloseStyles(i)];
137
+ case 2:
126
138
  _a.sent();
127
- if (isOverflow) {
128
- if (typeof document !== "undefined") {
129
- document.body.style.overflow = "";
130
- }
131
- }
132
- if (position === "all") {
133
- setData([]);
134
- setNames([]);
135
- return [2 /*return*/];
136
- }
137
- if (position === -1) {
138
- // remove last
139
- setData(function (prev) {
140
- return prev.filter(function (_, index) { return index !== prev.length - 1; });
141
- });
142
- setNames(function (prev) {
143
- return prev.filter(function (_, index) { return index !== prev.length - 1; });
144
- });
145
- return [2 /*return*/];
146
- }
147
- if (position === 0) {
148
- // remove first
149
- setData(function (prev) { return prev.filter(function (_, index) { return index !== 0; }); });
150
- setNames(function (prev) { return prev.filter(function (_, index) { return index !== 0; }); });
151
- return [2 /*return*/];
139
+ _a.label = 3;
140
+ case 3:
141
+ i--;
142
+ return [3 /*break*/, 1];
143
+ case 4:
144
+ setModals([]);
145
+ return [2 /*return*/];
146
+ case 5:
147
+ if (!(typeof position === "number")) return [3 /*break*/, 7];
148
+ indexToRemove_1 = position;
149
+ if (indexToRemove_1 < 0 || indexToRemove_1 >= modals.length) {
150
+ // Если индекс невалидный, закрываем последний
151
+ indexToRemove_1 = modals.length - 1;
152
152
  }
153
- // remove position index
154
- setData(function (prev) {
155
- return prev.filter(function (_, index) { return index !== prev.length - 1; });
156
- });
157
- setNames(function (prev) {
158
- return prev.filter(function (_, index) { return index !== prev.length - 1; });
153
+ if (!(indexToRemove_1 >= 0 && indexToRemove_1 < modals.length)) return [3 /*break*/, 7];
154
+ return [4 /*yield*/, applyCloseStyles(indexToRemove_1)];
155
+ case 6:
156
+ _a.sent();
157
+ setModals(function (prevModals) {
158
+ return prevModals.filter(function (_, index) { return index !== indexToRemove_1; });
159
159
  });
160
- return [2 /*return*/];
160
+ _a.label = 7;
161
+ case 7: return [2 /*return*/];
161
162
  }
162
163
  });
163
164
  }); };
164
- ModalManager_1.default.addEventListener(ModalManager_1.constants.CHANGE, handleOpenModal);
165
- ModalManager_1.default.addEventListener(ModalManager_1.constants.CLOSE, handleClose);
165
+ // Добавляем обработчики событий
166
+ ModalManager_1.modal.emitter.on(ModalManager_1.constants.CHANGE, handleOpenModal);
167
+ ModalManager_1.modal.emitter.on(ModalManager_1.constants.CLOSE, handleClose);
166
168
  return function () {
167
- ModalManager_1.default.removeEventListener(ModalManager_1.constants.CHANGE, handleOpenModal);
168
- ModalManager_1.default.removeEventListener(ModalManager_1.constants.CLOSE, handleClose);
169
+ // Удаляем обработчики при размонтировании
170
+ ModalManager_1.modal.emitter.off(ModalManager_1.constants.CHANGE, handleOpenModal);
171
+ ModalManager_1.modal.emitter.off(ModalManager_1.constants.CLOSE, handleClose);
169
172
  };
170
- // eslint-disable-next-line react-hooks/exhaustive-deps
171
- }, []);
172
- var activeModals = names.map(function (name) {
173
- var Component = modalList[name] || (function () { return react_1.default.createElement(react_1.default.Fragment, null); });
174
- return Component;
175
- });
173
+ }, [modals]);
176
174
  var handleCloseModal = function (index) {
177
- ModalManager_1.default.close(index);
175
+ ModalManager_1.modal.close(index);
176
+ };
177
+ var saveModalRef = function (id, ref) {
178
+ if (ref) {
179
+ modalRefs.current.set(id, ref);
180
+ }
181
+ else {
182
+ modalRefs.current.delete(id);
183
+ }
178
184
  };
179
- var refReducer = function (index, value) {
180
- modalRef.current[index] = value;
185
+ // Предотвратить всплытие клика для модального контента
186
+ var stopPropagation = function (e) {
187
+ e.stopPropagation();
181
188
  };
182
- if (typeof window === "undefined")
183
- return null;
184
- var body = document.body;
185
- (0, react_dom_1.createPortal)(react_1.default.createElement(react_1.default.Fragment, null, data.length !== 0 &&
186
- data.map(function (item, i) {
187
- var Modal = activeModals[i] || (function () { return react_1.default.createElement(react_1.default.Fragment, null); });
188
- return (react_1.default.createElement("div", { "data-index": i, key: item.modalId, className: "modal-manager backdrop_modal_manager ".concat(backdropClassName) },
189
- react_1.default.createElement("div", { onClick: function (e) {
190
- e.stopPropagation();
191
- handleCloseModal(i);
192
- }, className: "backdrop" }),
193
- react_1.default.createElement("div", { className: "".concat(className, " modal_paper") },
194
- react_1.default.createElement("div", { ref: function (ref) {
195
- refReducer(i, ref);
196
- } },
197
- react_1.default.createElement(Modal, __assign({}, item.data))))));
198
- })), body);
199
- return null;
189
+ return (react_1.default.createElement(react_1.default.Fragment, null, modals.map(function (modalItem, index) {
190
+ var name = modalItem.name, payload = modalItem.payload, options = modalItem.options, id = modalItem.id;
191
+ var Modal = modalList[name] || (function () { return react_1.default.createElement("div", null,
192
+ "Modal not found: ",
193
+ name); });
194
+ var hideBackdrop = (options === null || options === void 0 ? void 0 : options.hideBackdrop) || false;
195
+ var extraClass = (options === null || options === void 0 ? void 0 : options.extraClass) || "";
196
+ return (react_1.default.createElement("div", { key: id, "data-modal-id": id, "data-modal-index": index, className: "modal_container ".concat(extraClass) },
197
+ !hideBackdrop && (react_1.default.createElement("div", { onClick: function () { return handleCloseModal(index); }, className: "modal_backdrop" })),
198
+ react_1.default.createElement("div", { className: "".concat(className, " modal_paper"), onClick: stopPropagation, ref: function (ref) { return saveModalRef(id, ref); } },
199
+ react_1.default.createElement(Modal, __assign({}, (payload.data || {}), { modalIndex: index })))));
200
+ })));
200
201
  };
201
202
  exports.default = ModalProvider;
@@ -1,171 +1,177 @@
1
- "use client";
2
-
3
- import React, { useEffect, useRef, useState } from "react";
4
- import modal, { constants } from "../utils/ModalManager";
5
- import { createPortal } from "react-dom";
6
-
7
- export type ModalList = { [key: string]: React.ComponentType };
8
-
9
- interface ModalProviderProps {
10
- modalList: any;
11
- isOverflow?: boolean;
12
- className?: string;
13
- backdropClassName?: string;
14
- onModalStateChange?: (
15
- modalState: boolean,
16
- data: TData[],
17
- names: string[]
18
- ) => void;
19
- }
20
-
21
- type TData = { [key: string]: any };
22
-
23
- const ModalProvider = ({
24
- modalList,
25
- isOverflow,
26
- className,
27
- backdropClassName,
28
- onModalStateChange,
29
- }: ModalProviderProps) => {
30
- const [data, setData] = useState<TData[]>([]);
31
- const [names, setNames] = useState<string[]>([]);
32
- const modalRef = useRef<any[]>([]);
33
-
34
- const applyCloseStyles = (index: number) => {
35
- return new Promise((resolve) => {
36
- const modal = document.querySelector(
37
- `[data-index="${index}"]`
38
- ) as HTMLElement;
39
- if (!modal) return;
40
- modal.classList.add("closing");
41
- setTimeout(() => {
42
- resolve(true);
43
- }, 150);
44
- });
45
- };
46
-
47
- useEffect(() => {
48
- if (!onModalStateChange) return;
49
- const modalState = data.length !== 0;
50
- onModalStateChange(modalState, data, names);
51
- // eslint-disable-next-line react-hooks/exhaustive-deps
52
- }, [data, names]);
53
-
54
- useEffect(() => {
55
- const handleOpenModal = (name: string, data: TData) => {
56
- setData((prev: TData[]) => [...prev, data]);
57
- setNames((prev: string[]) => [...prev, name]);
58
-
59
- if (isOverflow) {
60
- if (typeof document === "undefined") return;
61
- document.body.style.overflow = "hidden";
62
- }
63
- };
64
-
65
- const handleClose = async (position: number | string) => {
66
- console.log("position", position);
67
- await applyCloseStyles(position as number);
68
- if (isOverflow) {
69
- if (typeof document !== "undefined") {
70
- document.body.style.overflow = "";
71
- }
72
- }
73
-
74
- if (position === "all") {
75
- setData([]);
76
- setNames([]);
77
- return;
78
- }
79
-
80
- if (position === -1) {
81
- // remove last
82
- setData((prev: TData[]) =>
83
- prev.filter((_, index) => index !== prev.length - 1)
84
- );
85
- setNames((prev: string[]) =>
86
- prev.filter((_, index) => index !== prev.length - 1)
87
- );
88
- return;
89
- }
90
-
91
- if (position === 0) {
92
- // remove first
93
- setData((prev: TData[]) => prev.filter((_, index) => index !== 0));
94
- setNames((prev: string[]) => prev.filter((_, index) => index !== 0));
95
- return;
96
- }
97
-
98
- // remove position index
99
- setData((prev: TData[]) =>
100
- prev.filter((_, index) => index !== prev.length - 1)
101
- );
102
- setNames((prev: string[]) =>
103
- prev.filter((_, index) => index !== prev.length - 1)
104
- );
105
- };
106
-
107
- modal.addEventListener(constants.CHANGE, handleOpenModal);
108
- modal.addEventListener(constants.CLOSE, handleClose);
109
- return () => {
110
- modal.removeEventListener(constants.CHANGE, handleOpenModal);
111
- modal.removeEventListener(constants.CLOSE, handleClose);
112
- };
113
- // eslint-disable-next-line react-hooks/exhaustive-deps
114
- }, []);
115
-
116
- const activeModals = names.map((name: string) => {
117
- const Component = modalList[name] || (() => <></>);
118
- return Component;
119
- });
120
-
121
- const handleCloseModal = (index: number) => {
122
- modal.close(index);
123
- };
124
-
125
- const refReducer = (index: number, value: any) => {
126
- modalRef.current[index] = value;
127
- };
128
-
129
- if (typeof window === "undefined") return null;
130
-
131
- const body = document.body;
132
- createPortal(
133
- <>
134
- {data.length !== 0 &&
135
- data.map((item, i) => {
136
- const Modal = activeModals[i] || (() => <></>);
137
-
138
- return (
139
- <div
140
- data-index={i}
141
- key={item.modalId}
142
- className={`modal-manager backdrop_modal_manager ${backdropClassName}`}
143
- >
144
- <div
145
- onClick={(e) => {
146
- e.stopPropagation();
147
- handleCloseModal(i);
148
- }}
149
- className="backdrop"
150
- />
151
- {/* // h-full modal not close */}
152
- <div className={`${className} modal_paper`}>
153
- <div
154
- ref={(ref) => {
155
- refReducer(i, ref);
156
- }}
157
- >
158
- <Modal {...item.data} />
159
- </div>
160
- </div>
161
- </div>
162
- );
163
- })}
164
- </>,
165
- body
166
- );
167
-
168
- return null;
169
- };
170
-
171
- export default ModalProvider;
1
+ "use client";
2
+
3
+ import React, { useEffect, useRef, useState } from "react";
4
+ import { modal, constants } from "../utils/ModalManager";
5
+
6
+ export type ModalList = { [key: string]: React.ComponentType<any> };
7
+
8
+ interface ModalProviderProps {
9
+ modalList: ModalList;
10
+ isOverflow?: boolean;
11
+ className?: string;
12
+ backdropClassName?: string;
13
+ onModalStateChange?: (
14
+ modalState: boolean,
15
+ data: ModalData[],
16
+ names: string[]
17
+ ) => void;
18
+ }
19
+
20
+ type ModalData = {
21
+ id: string;
22
+ name: string;
23
+ payload: any;
24
+ options?: any;
25
+ };
26
+
27
+ const ModalProvider: React.FC<ModalProviderProps> = ({
28
+ modalList,
29
+ isOverflow = true,
30
+ className = "",
31
+ backdropClassName = "",
32
+ onModalStateChange,
33
+ }) => {
34
+ const [modals, setModals] = useState<ModalData[]>([]);
35
+ const modalRefs = useRef<Map<string, HTMLDivElement | null>>(new Map());
36
+
37
+ const applyCloseStyles = (modalIndex: number): Promise<boolean> => {
38
+ return new Promise((resolve) => {
39
+ const modalElement = document.querySelector(
40
+ `[data-modal-index="${modalIndex}"]`
41
+ ) as HTMLElement;
42
+
43
+ if (!modalElement) {
44
+ resolve(false);
45
+ return;
46
+ }
47
+
48
+ modalElement.classList.add("modal_closing");
49
+ setTimeout(() => {
50
+ resolve(true);
51
+ }, 300);
52
+ });
53
+ };
54
+
55
+ useEffect(() => {
56
+ if (onModalStateChange && modals.length >= 0) {
57
+ const data = modals.map((modal) => modal.payload);
58
+ const names = modals.map((modal) => modal.name);
59
+ onModalStateChange(modals.length > 0, data, names);
60
+ }
61
+ }, [modals, onModalStateChange]);
62
+
63
+ useEffect(() => {
64
+ // Toggle body overflow
65
+ if (typeof document !== "undefined") {
66
+ document.body.style.overflow =
67
+ isOverflow && modals.length > 0 ? "hidden" : "";
68
+ }
69
+ }, [modals.length, isOverflow]);
70
+
71
+ useEffect(() => {
72
+ const handleOpenModal = (name: string, payload: any, options?: any) => {
73
+ const id = `modal-${payload.modalId || Date.now()}-${Math.random()
74
+ .toString(36)
75
+ .substring(2, 9)}`;
76
+
77
+ setModals((prevModals) => [
78
+ ...prevModals,
79
+ { id, name, payload, options },
80
+ ]);
81
+ };
82
+
83
+ const handleClose = async (position: number | string) => {
84
+ console.log("POS", position);
85
+ if (position === "all") {
86
+ // Закрыть все модальные окна с анимацией
87
+ for (let i = modals.length - 1; i >= 0; i--) {
88
+ await applyCloseStyles(i);
89
+ }
90
+ setModals([]);
91
+ return;
92
+ }
93
+
94
+ if (typeof position === "number") {
95
+ // Обработка числовых позиций
96
+ let indexToRemove: number = position;
97
+
98
+ if (indexToRemove < 0 || indexToRemove >= modals.length) {
99
+ // Если индекс невалидный, закрываем последний
100
+ indexToRemove = modals.length - 1;
101
+ }
102
+
103
+ if (indexToRemove >= 0 && indexToRemove < modals.length) {
104
+ await applyCloseStyles(indexToRemove);
105
+
106
+ setModals((prevModals) =>
107
+ prevModals.filter((_, index) => index !== indexToRemove)
108
+ );
109
+ }
110
+ }
111
+ };
112
+
113
+ // Добавляем обработчики событий
114
+ modal.emitter.on(constants.CHANGE, handleOpenModal);
115
+ modal.emitter.on(constants.CLOSE, handleClose);
116
+
117
+ return () => {
118
+ // Удаляем обработчики при размонтировании
119
+ modal.emitter.off(constants.CHANGE, handleOpenModal);
120
+ modal.emitter.off(constants.CLOSE, handleClose);
121
+ };
122
+ }, [modals]);
123
+
124
+ const handleCloseModal = (index: number) => {
125
+ modal.close(index);
126
+ };
127
+
128
+ const saveModalRef = (id: string, ref: HTMLDivElement | null) => {
129
+ if (ref) {
130
+ modalRefs.current.set(id, ref);
131
+ } else {
132
+ modalRefs.current.delete(id);
133
+ }
134
+ };
135
+
136
+ // Предотвратить всплытие клика для модального контента
137
+ const stopPropagation = (e: React.MouseEvent) => {
138
+ e.stopPropagation();
139
+ };
140
+
141
+ return (
142
+ <>
143
+ {modals.map((modalItem, index) => {
144
+ const { name, payload, options, id } = modalItem;
145
+ const Modal =
146
+ modalList[name] || (() => <div>Modal not found: {name}</div>);
147
+ const hideBackdrop = options?.hideBackdrop || false;
148
+ const extraClass = options?.extraClass || "";
149
+
150
+ return (
151
+ <div
152
+ key={id}
153
+ data-modal-id={id}
154
+ data-modal-index={index}
155
+ className={`modal_container ${extraClass}`}
156
+ >
157
+ {!hideBackdrop && (
158
+ <div
159
+ onClick={() => handleCloseModal(index)}
160
+ className="modal_backdrop"
161
+ />
162
+ )}
163
+ <div
164
+ className={`${className} modal_paper`}
165
+ onClick={stopPropagation}
166
+ ref={(ref) => saveModalRef(id, ref)}
167
+ >
168
+ <Modal {...(payload.data || {})} modalIndex={index} />
169
+ </div>
170
+ </div>
171
+ );
172
+ })}
173
+ </>
174
+ );
175
+ };
176
+
177
+ export default ModalProvider;
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import Manager from "./utils/Manager";
2
- import modal from "./utils/ModalManager";
3
- import ModalProvider from "./components/modal-provider";
4
-
5
- export { Manager, modal, ModalProvider };
1
+ import Manager from "./utils/Manager";
2
+ import modal from "./utils/ModalManager";
3
+ import ModalProvider from "./components/modal-provider";
4
+
5
+ export { Manager, modal, ModalProvider };