@violice/rmu 0.2.7 → 0.2.8

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.
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { renderHook, act } from '@testing-library/react';
4
+ import { useRMUState } from './use-rmu-state';
5
+
6
+ describe('useRMUState', () => {
7
+ it('should initialize with empty outlets', () => {
8
+ const { result } = renderHook(() => useRMUState());
9
+ expect(result.current.outlets).toEqual({});
10
+ });
11
+
12
+ it('should add outlet', () => {
13
+ const { result } = renderHook(() => useRMUState());
14
+
15
+ act(() => {
16
+ result.current.addOutlet('test-outlet');
17
+ });
18
+
19
+ expect(result.current.outlets).toHaveProperty('test-outlet');
20
+ expect(result.current.outlets['test-outlet']).toEqual({});
21
+ });
22
+
23
+ it('should throw when adding duplicate outlet', () => {
24
+ const { result } = renderHook(() => useRMUState());
25
+
26
+ act(() => {
27
+ result.current.addOutlet('test-outlet');
28
+ });
29
+
30
+ expect(() => {
31
+ act(() => {
32
+ result.current.addOutlet('test-outlet');
33
+ });
34
+ }).toThrow('Outlet with id test-outlet already exists');
35
+ });
36
+
37
+ it('should remove outlet', () => {
38
+ const { result } = renderHook(() => useRMUState());
39
+
40
+ act(() => {
41
+ result.current.addOutlet('test-outlet');
42
+ result.current.removeOutlet('test-outlet');
43
+ });
44
+
45
+ expect(result.current.outlets).not.toHaveProperty('test-outlet');
46
+ });
47
+
48
+ it('should open modal in outlet', () => {
49
+ const { result } = renderHook(() => useRMUState());
50
+ const mockComponent = React.createElement('div', null, 'Test');
51
+
52
+ act(() => {
53
+ result.current.addOutlet('test-outlet');
54
+ result.current.openModal({
55
+ modalId: 'modal-1',
56
+ modalComponent: mockComponent,
57
+ outletId: 'test-outlet',
58
+ });
59
+ });
60
+
61
+ expect(result.current.outlets['test-outlet']).toHaveProperty('modal-1');
62
+ expect(result.current.outlets['test-outlet']['modal-1']).toBe(mockComponent);
63
+ });
64
+
65
+ it('should throw when opening modal in non-existent outlet', () => {
66
+ const { result } = renderHook(() => useRMUState());
67
+
68
+ expect(() => {
69
+ act(() => {
70
+ result.current.openModal({
71
+ modalId: 'modal-1',
72
+ modalComponent: React.createElement('div', null, 'Test'),
73
+ outletId: 'non-existent',
74
+ });
75
+ });
76
+ }).toThrow('Outlet with id non-existent not found');
77
+ });
78
+
79
+ it('should close modal', () => {
80
+ const { result } = renderHook(() => useRMUState());
81
+
82
+ act(() => {
83
+ result.current.addOutlet('test-outlet');
84
+ result.current.openModal({
85
+ modalId: 'modal-1',
86
+ modalComponent: React.createElement('div', null, 'Test'),
87
+ outletId: 'test-outlet',
88
+ });
89
+ result.current.closeModal({ modalId: 'modal-1', outletId: 'test-outlet' });
90
+ });
91
+
92
+ expect(result.current.outlets['test-outlet']).not.toHaveProperty('modal-1');
93
+ });
94
+
95
+ it('should throw when closing modal in non-existent outlet', () => {
96
+ const { result } = renderHook(() => useRMUState());
97
+
98
+ expect(() => {
99
+ act(() => {
100
+ result.current.closeModal({
101
+ modalId: 'modal-1',
102
+ outletId: 'non-existent',
103
+ });
104
+ });
105
+ }).toThrow('Outlet with id non-existent not found');
106
+ });
107
+
108
+ it('should handle multiple modals in same outlet', () => {
109
+ const { result } = renderHook(() => useRMUState());
110
+
111
+ act(() => {
112
+ result.current.addOutlet('test-outlet');
113
+ result.current.openModal({
114
+ modalId: 'modal-1',
115
+ modalComponent: React.createElement('div', null, 'Modal 1'),
116
+ outletId: 'test-outlet',
117
+ });
118
+ result.current.openModal({
119
+ modalId: 'modal-2',
120
+ modalComponent: React.createElement('div', null, 'Modal 2'),
121
+ outletId: 'test-outlet',
122
+ });
123
+ });
124
+
125
+ expect(Object.keys(result.current.outlets['test-outlet'])).toHaveLength(2);
126
+ expect(result.current.outlets['test-outlet']).toHaveProperty('modal-1');
127
+ expect(result.current.outlets['test-outlet']).toHaveProperty('modal-2');
128
+ });
129
+ });
package/dist/index.d.ts DELETED
@@ -1,49 +0,0 @@
1
- import React, { ReactNode } from "react";
2
-
3
- //#region src/rmu-provider.d.ts
4
- declare const RMUProvider: ({
5
- children
6
- }: {
7
- children: React.ReactNode;
8
- }) => React.JSX.Element;
9
- //#endregion
10
- //#region src/rmu-outlet.d.ts
11
- declare const RMUOutlet: ({
12
- outletId
13
- }: {
14
- outletId?: string | undefined;
15
- }) => React.JSX.Element;
16
- //#endregion
17
- //#region src/types.d.ts
18
- type RMUContextState = {
19
- outlets: Record<string, Record<string, ReactNode>>;
20
- openModal: ({
21
- modalId,
22
- modalComponent,
23
- outletId
24
- }: {
25
- modalId: string;
26
- modalComponent: ReactNode;
27
- outletId: string;
28
- }) => void;
29
- closeModal: ({
30
- modalId,
31
- outletId
32
- }: {
33
- modalId: string;
34
- outletId: string;
35
- }) => void;
36
- addOutlet: (outletId: string) => void;
37
- removeOutlet: (outletId: string) => void;
38
- };
39
- //#endregion
40
- //#region src/events.d.ts
41
- declare const openModal: (modalComponent: ReactNode, config?: {
42
- outletId?: string;
43
- }) => {
44
- modalId: string;
45
- outletId: string;
46
- };
47
- declare const closeModal: RMUContextState['closeModal'];
48
- //#endregion
49
- export { RMUOutlet, RMUProvider, closeModal, openModal };
package/dist/index.js DELETED
@@ -1,231 +0,0 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
24
- let react = require("react");
25
- react = __toESM(react);
26
-
27
- //#region src/rmu-context.ts
28
- const RMUContext = (0, react.createContext)(null);
29
-
30
- //#endregion
31
- //#region src/emitter.ts
32
- const listenersByType = {};
33
- const emitter = {
34
- subscribe(type, handler) {
35
- if (!listenersByType[type]) listenersByType[type] = /* @__PURE__ */ new Set();
36
- listenersByType[type].add(handler);
37
- },
38
- unsubscribe(type, handler) {
39
- const listeners = listenersByType[type];
40
- if (listeners) {
41
- listeners.delete(handler);
42
- if (listeners.size === 0) delete listenersByType[type];
43
- }
44
- },
45
- emit(type, payload) {
46
- const listeners = listenersByType[type];
47
- if (!listeners) return;
48
- [...listeners].forEach((handler) => {
49
- try {
50
- handler(payload);
51
- } catch {}
52
- });
53
- }
54
- };
55
-
56
- //#endregion
57
- //#region src/events.ts
58
- const RMU_EVENTS = {
59
- open: "rmu:open-modal",
60
- close: "rmu:close-modal"
61
- };
62
- const openModal = (modalComponent, config = {}) => {
63
- const modalId = `rmu-modal-${(/* @__PURE__ */ new Date()).getTime().toString()}`;
64
- const { outletId = "rmu-default-outlet" } = config;
65
- emitter.emit(RMU_EVENTS.open, {
66
- modalId,
67
- modalComponent,
68
- outletId
69
- });
70
- return {
71
- modalId,
72
- outletId
73
- };
74
- };
75
- const closeModal = ({ modalId, outletId }) => {
76
- emitter.emit(RMU_EVENTS.close, {
77
- modalId,
78
- outletId
79
- });
80
- };
81
-
82
- //#endregion
83
- //#region src/use-rmu-events.ts
84
- const useRMUEvents = (ctx) => {
85
- const events = {
86
- open: (payload) => ctx.openModal(payload),
87
- close: (payload) => ctx.closeModal(payload)
88
- };
89
- (0, react.useEffect)(() => {
90
- Object.keys(events).forEach((event) => {
91
- emitter.subscribe(RMU_EVENTS[event], events[event]);
92
- });
93
- return () => {
94
- Object.keys(events).forEach((event) => {
95
- emitter.unsubscribe(RMU_EVENTS[event], events[event]);
96
- });
97
- };
98
- }, []);
99
- };
100
-
101
- //#endregion
102
- //#region src/use-rmu-state.ts
103
- const ACTIONS = {
104
- openModal: "rmu:open-modal",
105
- closeModal: "rmu:close-modal",
106
- addOutlet: "rmu:add-modal",
107
- removeOutlet: "rmu:remove-outlet"
108
- };
109
- const reducer = (state, action) => {
110
- switch (action.type) {
111
- case ACTIONS.openModal: {
112
- const { modalId, modalComponent, outletId } = action.payload;
113
- const modalOutlet = state.outlets[outletId];
114
- if (!modalOutlet) throw new Error(`Outlet with id ${outletId} not found`);
115
- return {
116
- ...state,
117
- outlets: {
118
- ...state.outlets,
119
- [outletId]: {
120
- ...modalOutlet,
121
- [modalId]: modalComponent
122
- }
123
- }
124
- };
125
- }
126
- case ACTIONS.closeModal: {
127
- const { modalId, outletId } = action.payload;
128
- const modalOutlet = state.outlets[outletId];
129
- if (!modalOutlet) throw new Error(`Outlet with id ${outletId} not found`);
130
- const { [modalId]: _removed,...restModals } = modalOutlet;
131
- return {
132
- ...state,
133
- outlets: {
134
- ...state.outlets,
135
- [outletId]: restModals
136
- }
137
- };
138
- }
139
- case ACTIONS.addOutlet: {
140
- const { outletId } = action.payload;
141
- if (!!state.outlets[outletId]) throw new Error(`Outlet with id ${outletId} already exists`);
142
- return {
143
- ...state,
144
- outlets: {
145
- ...state.outlets,
146
- [outletId]: {}
147
- }
148
- };
149
- }
150
- case ACTIONS.removeOutlet: {
151
- const { outletId } = action.payload;
152
- const { [outletId]: _removed,...restOutlets } = state.outlets;
153
- return {
154
- ...state,
155
- outlets: restOutlets
156
- };
157
- }
158
- default: return state;
159
- }
160
- };
161
- const useRMUState = () => {
162
- const [state, dispatch] = (0, react.useReducer)(reducer, { outlets: {} });
163
- const openModal$1 = ({ modalId, modalComponent, outletId }) => {
164
- dispatch({
165
- type: ACTIONS.openModal,
166
- payload: {
167
- modalId,
168
- modalComponent,
169
- outletId
170
- }
171
- });
172
- };
173
- const closeModal$1 = ({ modalId, outletId }) => {
174
- dispatch({
175
- type: ACTIONS.closeModal,
176
- payload: {
177
- modalId,
178
- outletId
179
- }
180
- });
181
- };
182
- const addOutlet = (outletId) => {
183
- dispatch({
184
- type: ACTIONS.addOutlet,
185
- payload: { outletId }
186
- });
187
- };
188
- const removeOutlet = (outletId) => {
189
- dispatch({
190
- type: ACTIONS.removeOutlet,
191
- payload: { outletId }
192
- });
193
- };
194
- return {
195
- ...state,
196
- openModal: openModal$1,
197
- closeModal: closeModal$1,
198
- addOutlet,
199
- removeOutlet
200
- };
201
- };
202
-
203
- //#endregion
204
- //#region src/rmu-provider.tsx
205
- const RMUProvider = ({ children }) => {
206
- const state = useRMUState();
207
- useRMUEvents(state);
208
- return /* @__PURE__ */ react.default.createElement(RMUContext.Provider, { value: state }, children);
209
- };
210
-
211
- //#endregion
212
- //#region src/rmu-outlet.tsx
213
- const RMUOutlet = ({ outletId = "rmu-default-outlet" }) => {
214
- const ctx = (0, react.useContext)(RMUContext);
215
- if (!ctx) throw new Error("RMUProvider not found in component tree");
216
- const { outlets, addOutlet, removeOutlet } = ctx;
217
- (0, react.useEffect)(() => {
218
- addOutlet(outletId);
219
- return () => {
220
- removeOutlet(outletId);
221
- };
222
- }, []);
223
- const modals = outlets[outletId] ?? {};
224
- return /* @__PURE__ */ react.default.createElement(react.default.Fragment, null, Object.entries(modals).map(([modalId, modalComponent]) => /* @__PURE__ */ react.default.createElement(react.Fragment, { key: modalId }, modalComponent)));
225
- };
226
-
227
- //#endregion
228
- exports.RMUOutlet = RMUOutlet;
229
- exports.RMUProvider = RMUProvider;
230
- exports.closeModal = closeModal;
231
- exports.openModal = openModal;