@react95/core 9.5.2 → 9.6.0-alpha.0

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,4 +1,3 @@
1
1
  require('./GlobalStyle.css.ts.vanilla.css');
2
2
  "use strict";
3
- require("../themes/contract.css.ts.vanilla.css.cjs");
4
3
  /* empty css */
@@ -9,30 +9,34 @@ const TitleBar = require("../TitleBar/TitleBar.cjs");
9
9
  const Modal_css = require("./Modal.css.cjs");
10
10
  const cn = require("classnames");
11
11
  const usehooksTs = require("usehooks-ts");
12
- const events = require("../shared/events.cjs");
12
+ const useModal = require("../shared/use-modal.cjs");
13
+ const modalTypes = require("../shared/modal-types.cjs");
13
14
  const ModalContent = Frame.fixedForwardRef(
14
15
  (rest, ref) => /* @__PURE__ */ React.createElement(Frame.Frame, { ...rest, ref, className: cn(Modal_css.content, rest.className) })
15
16
  );
16
17
  const ModalMinimize = Frame.fixedForwardRef(
17
18
  (props, ref) => {
18
19
  const [id, setId] = React.useState("");
20
+ const { minimize, focus, subscribe } = useModal.useModal();
19
21
  React.useEffect(() => {
20
22
  const handleVisibilityChange = ({ id: activeId }) => {
21
23
  setId(activeId);
22
24
  };
23
- events.modals.on(events.ModalEvents.ModalVisibilityChanged, handleVisibilityChange);
24
- return () => {
25
- events.modals.off(events.ModalEvents.ModalVisibilityChanged, handleVisibilityChange);
26
- };
27
- }, []);
25
+ const unsubscribe = subscribe(
26
+ modalTypes.ModalEvents.ModalVisibilityChanged,
27
+ handleVisibilityChange
28
+ );
29
+ return unsubscribe;
30
+ }, [subscribe]);
28
31
  const handleMinimize = () => {
29
- events.modals.emit(events.ModalEvents.MinimizeModal, { id });
30
- events.modals.emit(events.ModalEvents.ModalVisibilityChanged, { id: "no id" });
32
+ minimize(id);
33
+ focus("no-id");
31
34
  };
32
35
  return /* @__PURE__ */ React.createElement(TitleBar.TitleBar.Minimize, { ...props, ref, onClick: handleMinimize });
33
36
  }
34
37
  );
35
38
  const ModalRenderer = ({
39
+ id: providedId,
36
40
  hasWindowButton: hasButton = true,
37
41
  buttons = [],
38
42
  buttonsAlignment = "flex-end",
@@ -45,10 +49,11 @@ const ModalRenderer = ({
45
49
  className,
46
50
  ...rest
47
51
  }, ref) => {
48
- const [id] = React.useState(nanoid.nanoid());
52
+ const [id] = React.useState(providedId || nanoid.nanoid());
49
53
  const [menuOpened, setMenuOpened] = React.useState("");
50
54
  const [isActive, setIsActive] = React.useState(false);
51
55
  const [isModalMinimized, setIsModalMinimized] = React.useState(false);
56
+ const { add, remove, focus, subscribe } = useModal.useModal();
52
57
  const draggableRef = React.useRef(null);
53
58
  react.useDraggable(draggableRef, {
54
59
  ...dragOptions,
@@ -59,38 +64,46 @@ const ModalRenderer = ({
59
64
  setMenuOpened("");
60
65
  });
61
66
  React.useEffect(() => {
62
- events.modals.emit(events.ModalEvents.AddModal, {
67
+ add({
63
68
  icon,
64
- title,
69
+ title: title || "",
65
70
  id,
66
71
  hasButton
67
72
  });
68
- events.modals.on(events.ModalEvents.ModalVisibilityChanged, ({ id: activeId }) => {
69
- setIsActive(activeId === id);
70
- });
71
- events.modals.emit(events.ModalEvents.ModalVisibilityChanged, { id });
73
+ const unsubscribeVisibility = subscribe(
74
+ modalTypes.ModalEvents.ModalVisibilityChanged,
75
+ ({ id: activeId }) => {
76
+ setIsActive(activeId === id);
77
+ }
78
+ );
79
+ focus(id);
72
80
  return () => {
73
- events.modals.emit(events.ModalEvents.RemoveModal, { id });
81
+ remove(id);
82
+ unsubscribeVisibility();
74
83
  };
75
- }, []);
84
+ }, [id, icon, title, hasButton, providedId, add, remove, focus, subscribe]);
76
85
  React.useEffect(() => {
77
- events.modals.on(events.ModalEvents.MinimizeModal, ({ id: activeId }) => {
78
- if (activeId === id) {
79
- setIsModalMinimized(true);
86
+ const unsubscribeMinimize = subscribe(
87
+ modalTypes.ModalEvents.MinimizeModal,
88
+ ({ id: activeId }) => {
89
+ if (activeId === id) {
90
+ setIsModalMinimized(true);
91
+ }
80
92
  }
81
- });
82
- events.modals.on(events.ModalEvents.RestoreModal, ({ id: activeId }) => {
83
- if (activeId === id) {
84
- setIsModalMinimized(false);
93
+ );
94
+ const unsubscribeRestore = subscribe(
95
+ modalTypes.ModalEvents.RestoreModal,
96
+ ({ id: activeId }) => {
97
+ if (activeId === id) {
98
+ setIsModalMinimized(false);
99
+ }
85
100
  }
86
- });
101
+ );
87
102
  return () => {
88
- events.modals.off(events.ModalEvents.MinimizeModal, () => {
89
- });
90
- events.modals.off(events.ModalEvents.RestoreModal, () => {
91
- });
103
+ unsubscribeMinimize();
104
+ unsubscribeRestore();
92
105
  };
93
- }, [id]);
106
+ }, [id, subscribe]);
94
107
  React.useImperativeHandle(ref, () => {
95
108
  return draggableRef.current;
96
109
  });
@@ -106,7 +119,7 @@ const ModalRenderer = ({
106
119
  "aria-hidden": isModalMinimized,
107
120
  ref: draggableRef,
108
121
  onMouseDown: () => {
109
- events.modals.emit(events.ModalEvents.ModalVisibilityChanged, { id });
122
+ focus(id);
110
123
  }
111
124
  },
112
125
  /* @__PURE__ */ React.createElement(
@@ -6,16 +6,27 @@ const Clock = require("./Clock.cjs");
6
6
  const WindowButton = require("./WindowButton.cjs");
7
7
  const icons = require("@react95/icons");
8
8
  const TaskBar_css = require("./TaskBar.css.cjs");
9
- const events = require("../shared/events.cjs");
9
+ const useModal = require("../shared/use-modal.cjs");
10
+ const modalTypes = require("../shared/modal-types.cjs");
10
11
  const TaskBar = React.forwardRef(
11
12
  ({ list, className }, ref) => {
12
13
  const [showList, toggleShowList] = React.useState(false);
13
14
  const [activeStart, toggleActiveStart] = React.useState(false);
14
15
  const [modalWindows, setModalWindows] = React.useState([]);
15
16
  const [activeWindow, setActiveWindow] = React.useState();
17
+ const { minimize, restore, focus, subscribe } = useModal.useModal();
16
18
  React.useEffect(() => {
17
19
  const addModal = (window) => {
18
- setModalWindows((prevModals) => [...prevModals, window]);
20
+ if (!window.id) {
21
+ console.warn("Modal added without ID");
22
+ return;
23
+ }
24
+ setModalWindows((prevModals) => {
25
+ if (prevModals.some((modal) => modal.id === window.id)) {
26
+ return prevModals;
27
+ }
28
+ return [...prevModals, window];
29
+ });
19
30
  };
20
31
  const removeModal = (data) => {
21
32
  setModalWindows((prevModals) => {
@@ -23,10 +34,8 @@ const TaskBar = React.forwardRef(
23
34
  (modal) => modal.id !== data.id
24
35
  );
25
36
  const lastModal = filteredModals.at(-1);
26
- if (!activeWindow && lastModal) {
27
- events.modals.emit(events.ModalEvents.ModalVisibilityChanged, {
28
- id: lastModal == null ? void 0 : lastModal.id
29
- });
37
+ if (activeWindow === data.id && lastModal) {
38
+ focus(lastModal.id);
30
39
  }
31
40
  return filteredModals;
32
41
  });
@@ -34,15 +43,18 @@ const TaskBar = React.forwardRef(
34
43
  const updateVisibleModal = ({ id }) => {
35
44
  setActiveWindow(id);
36
45
  };
37
- events.modals.on(events.ModalEvents.AddModal, addModal);
38
- events.modals.on(events.ModalEvents.RemoveModal, removeModal);
39
- events.modals.on(events.ModalEvents.ModalVisibilityChanged, updateVisibleModal);
46
+ const unsubscribeAdd = subscribe(modalTypes.ModalEvents.AddModal, addModal);
47
+ const unsubscribeRemove = subscribe(modalTypes.ModalEvents.RemoveModal, removeModal);
48
+ const unsubscribeVisibility = subscribe(
49
+ modalTypes.ModalEvents.ModalVisibilityChanged,
50
+ updateVisibleModal
51
+ );
40
52
  return () => {
41
- events.modals.off(events.ModalEvents.AddModal, addModal);
42
- events.modals.off(events.ModalEvents.RemoveModal, removeModal);
43
- events.modals.off(events.ModalEvents.ModalVisibilityChanged, updateVisibleModal);
53
+ unsubscribeAdd();
54
+ unsubscribeRemove();
55
+ unsubscribeVisibility();
44
56
  };
45
- }, []);
57
+ }, [activeWindow, subscribe, focus]);
46
58
  return /* @__PURE__ */ React.createElement(
47
59
  Frame.Frame,
48
60
  {
@@ -95,11 +107,11 @@ const TaskBar = React.forwardRef(
95
107
  active: id === activeWindow,
96
108
  onClick: () => {
97
109
  if (id === activeWindow) {
98
- events.modals.emit(events.ModalEvents.MinimizeModal, { id });
99
- setActiveWindow("Minimize");
110
+ minimize(id);
111
+ setActiveWindow(void 0);
100
112
  } else {
101
- events.modals.emit(events.ModalEvents.RestoreModal, { id });
102
- events.modals.emit(events.ModalEvents.ModalVisibilityChanged, { id });
113
+ restore(id);
114
+ focus(id);
103
115
  }
104
116
  },
105
117
  small: false
package/cjs/index.cjs CHANGED
@@ -24,6 +24,8 @@ const TitleBar = require("./TitleBar/TitleBar.cjs");
24
24
  const Video = require("./Video/Video.cjs");
25
25
  const contract_css = require("./themes/contract.css.cjs");
26
26
  const index = require("./themes/tokens/index.cjs");
27
+ const modalTypes = require("./shared/modal-types.cjs");
28
+ const useModal = require("./shared/use-modal.cjs");
27
29
  exports.Modal = Modal.Modal;
28
30
  exports.Tabs = Tabs.Tabs;
29
31
  exports.Tab = Tab.Tab;
@@ -48,3 +50,5 @@ exports.TitleBar = TitleBar.TitleBar;
48
50
  exports.Video = Video.Video;
49
51
  exports.contract = contract_css.contract;
50
52
  exports.tokens = index;
53
+ exports.ModalEvents = modalTypes.ModalEvents;
54
+ exports.useModal = useModal.useModal;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ class Emitter {
4
+ constructor() {
5
+ this.listeners = {};
6
+ }
7
+ on(eventName, callback) {
8
+ var _a;
9
+ if (!this.listeners[eventName]) {
10
+ this.listeners[eventName] = [];
11
+ }
12
+ (_a = this.listeners[eventName]) == null ? void 0 : _a.push(callback);
13
+ }
14
+ off(eventName, callback) {
15
+ var _a;
16
+ if (this.listeners[eventName]) {
17
+ this.listeners[eventName] = (_a = this.listeners[eventName]) == null ? void 0 : _a.filter(
18
+ (cb) => cb !== callback
19
+ );
20
+ }
21
+ }
22
+ emit(eventName, data) {
23
+ var _a;
24
+ if (this.listeners[eventName]) {
25
+ (_a = this.listeners[eventName]) == null ? void 0 : _a.forEach((callback) => {
26
+ try {
27
+ callback(data);
28
+ } catch (error) {
29
+ console.error(`Error in event listener for ${eventName}:`, error);
30
+ }
31
+ });
32
+ }
33
+ }
34
+ }
35
+ exports.Emitter = Emitter;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const emitter = require("./emitter.cjs");
4
+ const modals = new emitter.Emitter();
5
+ exports.modals = modals;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ var ModalEvents = /* @__PURE__ */ ((ModalEvents2) => {
4
+ ModalEvents2["AddModal"] = "add-modal";
5
+ ModalEvents2["RemoveModal"] = "remove-modal";
6
+ ModalEvents2["ModalVisibilityChanged"] = "modal-visibility-changed";
7
+ ModalEvents2["MinimizeModal"] = "minimize-modal";
8
+ ModalEvents2["RestoreModal"] = "restore-modal";
9
+ ModalEvents2["FocusModal"] = "focus-modal";
10
+ return ModalEvents2;
11
+ })(ModalEvents || {});
12
+ exports.ModalEvents = ModalEvents;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const React = require("react");
4
+ const modalController = require("./modal-controller.cjs");
5
+ const modalTypes = require("./modal-types.cjs");
6
+ const useModal = () => {
7
+ const add = React.useCallback((modal) => {
8
+ modalController.modals.emit(modalTypes.ModalEvents.AddModal, modal);
9
+ }, []);
10
+ const remove = React.useCallback((id) => {
11
+ modalController.modals.emit(modalTypes.ModalEvents.RemoveModal, { id });
12
+ }, []);
13
+ const minimize = React.useCallback((id) => {
14
+ modalController.modals.emit(modalTypes.ModalEvents.MinimizeModal, { id });
15
+ }, []);
16
+ const restore = React.useCallback((id) => {
17
+ modalController.modals.emit(modalTypes.ModalEvents.RestoreModal, { id });
18
+ }, []);
19
+ const focus = React.useCallback((id) => {
20
+ modalController.modals.emit(modalTypes.ModalEvents.ModalVisibilityChanged, { id });
21
+ }, []);
22
+ const toggle = React.useCallback((id, isActive) => {
23
+ if (isActive) {
24
+ modalController.modals.emit(modalTypes.ModalEvents.MinimizeModal, { id });
25
+ } else {
26
+ modalController.modals.emit(modalTypes.ModalEvents.RestoreModal, { id });
27
+ modalController.modals.emit(modalTypes.ModalEvents.ModalVisibilityChanged, { id });
28
+ }
29
+ }, []);
30
+ const subscribe = React.useCallback(
31
+ (event, callback) => {
32
+ modalController.modals.on(event, callback);
33
+ return () => modalController.modals.off(event, callback);
34
+ },
35
+ []
36
+ );
37
+ return { add, remove, minimize, restore, focus, toggle, subscribe };
38
+ };
39
+ exports.useModal = useModal;
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ require("./contract.css.ts.vanilla.css.cjs");
3
4
  var contract = { space: { "0": "var(--r95-space-0)", "1": "var(--r95-space-1)", "2": "var(--r95-space-2)", "3": "var(--r95-space-3)", "4": "var(--r95-space-4)", "5": "var(--r95-space-5)", "6": "var(--r95-space-6)", "7": "var(--r95-space-7)", "8": "var(--r95-space-8)", "9": "var(--r95-space-9)", "10": "var(--r95-space-10)", "11": "var(--r95-space-11)", "12": "var(--r95-space-12)", "13": "var(--r95-space-13)", "14": "var(--r95-space-14)", "15": "var(--r95-space-15)", "16": "var(--r95-space-16)", "17": "var(--r95-space-17)", "18": "var(--r95-space-18)", "19": "var(--r95-space-19)", "20": "var(--r95-space-20)" }, colors: { anchor: "var(--r95-color-anchor)", anchorVisited: "var(--r95-color-anchorVisited)", borderDark: "var(--r95-color-borderDark)", borderDarkest: "var(--r95-color-borderDarkest)", borderLight: "var(--r95-color-borderLight)", borderLighter: "var(--r95-color-borderLighter)", borderLightest: "var(--r95-color-borderLightest)", canvas: "var(--r95-color-canvas)", canvasText: "var(--r95-color-canvasText)", headerBackground: "var(--r95-color-headerBackground)", headerNotActiveBackground: "var(--r95-color-headerNotActiveBackground)", headerNotActiveText: "var(--r95-color-headerNotActiveText)", headerText: "var(--r95-color-headerText)", material: "var(--r95-color-material)", materialText: "var(--r95-color-materialText)", materialTextDisabled: "var(--r95-color-materialTextDisabled)", materialTextDisabledShadow: "var(--r95-color-materialTextDisabledShadow)", materialTextInvert: "var(--r95-color-materialTextInvert)", progress: "var(--r95-color-progress)", inputBackground: "var(--r95-color-inputBackground)", inputBackgroundDisabled: "var(--r95-color-inputBackgroundDisabled)" }, zIndices: { modal: "var(--r95-zIndex-modal)", tooltip: "var(--r95-zIndex-tooltip)", taskbar: "var(--r95-zIndex-taskbar)" }, shadows: { out: "var(--r95-shadow-out)", "in": "var(--r95-shadow-in)", input: "var(--r95-shadow-input)" } };
4
5
  exports.contract = contract;
@@ -1,3 +1,2 @@
1
1
  import './GlobalStyle.css.ts.vanilla.css';
2
- import "../themes/contract.css.ts.vanilla.css.mjs";
3
2
  /* empty css */
@@ -7,30 +7,34 @@ import { TitleBar } from "../TitleBar/TitleBar.mjs";
7
7
  import { content, modalWrapper, menuWrapper, menuItem, buttonWrapper } from "./Modal.css.mjs";
8
8
  import cn from "classnames";
9
9
  import { useOnClickOutside } from "usehooks-ts";
10
- import { modals, ModalEvents } from "../shared/events.mjs";
10
+ import { useModal } from "../shared/use-modal.mjs";
11
+ import { ModalEvents } from "../shared/modal-types.mjs";
11
12
  const ModalContent = fixedForwardRef(
12
13
  (rest, ref) => /* @__PURE__ */ React.createElement(Frame, { ...rest, ref, className: cn(content, rest.className) })
13
14
  );
14
15
  const ModalMinimize = fixedForwardRef(
15
16
  (props, ref) => {
16
17
  const [id, setId] = useState("");
18
+ const { minimize, focus, subscribe } = useModal();
17
19
  useEffect(() => {
18
20
  const handleVisibilityChange = ({ id: activeId }) => {
19
21
  setId(activeId);
20
22
  };
21
- modals.on(ModalEvents.ModalVisibilityChanged, handleVisibilityChange);
22
- return () => {
23
- modals.off(ModalEvents.ModalVisibilityChanged, handleVisibilityChange);
24
- };
25
- }, []);
23
+ const unsubscribe = subscribe(
24
+ ModalEvents.ModalVisibilityChanged,
25
+ handleVisibilityChange
26
+ );
27
+ return unsubscribe;
28
+ }, [subscribe]);
26
29
  const handleMinimize = () => {
27
- modals.emit(ModalEvents.MinimizeModal, { id });
28
- modals.emit(ModalEvents.ModalVisibilityChanged, { id: "no id" });
30
+ minimize(id);
31
+ focus("no-id");
29
32
  };
30
33
  return /* @__PURE__ */ React.createElement(TitleBar.Minimize, { ...props, ref, onClick: handleMinimize });
31
34
  }
32
35
  );
33
36
  const ModalRenderer = ({
37
+ id: providedId,
34
38
  hasWindowButton: hasButton = true,
35
39
  buttons = [],
36
40
  buttonsAlignment = "flex-end",
@@ -43,10 +47,11 @@ const ModalRenderer = ({
43
47
  className,
44
48
  ...rest
45
49
  }, ref) => {
46
- const [id] = useState(nanoid());
50
+ const [id] = useState(providedId || nanoid());
47
51
  const [menuOpened, setMenuOpened] = useState("");
48
52
  const [isActive, setIsActive] = useState(false);
49
53
  const [isModalMinimized, setIsModalMinimized] = useState(false);
54
+ const { add, remove, focus, subscribe } = useModal();
50
55
  const draggableRef = useRef(null);
51
56
  useDraggable(draggableRef, {
52
57
  ...dragOptions,
@@ -57,38 +62,46 @@ const ModalRenderer = ({
57
62
  setMenuOpened("");
58
63
  });
59
64
  useEffect(() => {
60
- modals.emit(ModalEvents.AddModal, {
65
+ add({
61
66
  icon,
62
- title,
67
+ title: title || "",
63
68
  id,
64
69
  hasButton
65
70
  });
66
- modals.on(ModalEvents.ModalVisibilityChanged, ({ id: activeId }) => {
67
- setIsActive(activeId === id);
68
- });
69
- modals.emit(ModalEvents.ModalVisibilityChanged, { id });
71
+ const unsubscribeVisibility = subscribe(
72
+ ModalEvents.ModalVisibilityChanged,
73
+ ({ id: activeId }) => {
74
+ setIsActive(activeId === id);
75
+ }
76
+ );
77
+ focus(id);
70
78
  return () => {
71
- modals.emit(ModalEvents.RemoveModal, { id });
79
+ remove(id);
80
+ unsubscribeVisibility();
72
81
  };
73
- }, []);
82
+ }, [id, icon, title, hasButton, providedId, add, remove, focus, subscribe]);
74
83
  useEffect(() => {
75
- modals.on(ModalEvents.MinimizeModal, ({ id: activeId }) => {
76
- if (activeId === id) {
77
- setIsModalMinimized(true);
84
+ const unsubscribeMinimize = subscribe(
85
+ ModalEvents.MinimizeModal,
86
+ ({ id: activeId }) => {
87
+ if (activeId === id) {
88
+ setIsModalMinimized(true);
89
+ }
78
90
  }
79
- });
80
- modals.on(ModalEvents.RestoreModal, ({ id: activeId }) => {
81
- if (activeId === id) {
82
- setIsModalMinimized(false);
91
+ );
92
+ const unsubscribeRestore = subscribe(
93
+ ModalEvents.RestoreModal,
94
+ ({ id: activeId }) => {
95
+ if (activeId === id) {
96
+ setIsModalMinimized(false);
97
+ }
83
98
  }
84
- });
99
+ );
85
100
  return () => {
86
- modals.off(ModalEvents.MinimizeModal, () => {
87
- });
88
- modals.off(ModalEvents.RestoreModal, () => {
89
- });
101
+ unsubscribeMinimize();
102
+ unsubscribeRestore();
90
103
  };
91
- }, [id]);
104
+ }, [id, subscribe]);
92
105
  useImperativeHandle(ref, () => {
93
106
  return draggableRef.current;
94
107
  });
@@ -104,7 +117,7 @@ const ModalRenderer = ({
104
117
  "aria-hidden": isModalMinimized,
105
118
  ref: draggableRef,
106
119
  onMouseDown: () => {
107
- modals.emit(ModalEvents.ModalVisibilityChanged, { id });
120
+ focus(id);
108
121
  }
109
122
  },
110
123
  /* @__PURE__ */ React.createElement(
@@ -4,16 +4,27 @@ import { Clock } from "./Clock.mjs";
4
4
  import { WindowButton } from "./WindowButton.mjs";
5
5
  import { Logo } from "@react95/icons";
6
6
  import { truncate } from "./TaskBar.css.mjs";
7
- import { modals, ModalEvents } from "../shared/events.mjs";
7
+ import { useModal } from "../shared/use-modal.mjs";
8
+ import { ModalEvents } from "../shared/modal-types.mjs";
8
9
  const TaskBar = forwardRef(
9
10
  ({ list, className }, ref) => {
10
11
  const [showList, toggleShowList] = useState(false);
11
12
  const [activeStart, toggleActiveStart] = useState(false);
12
13
  const [modalWindows, setModalWindows] = React.useState([]);
13
14
  const [activeWindow, setActiveWindow] = useState();
15
+ const { minimize, restore, focus, subscribe } = useModal();
14
16
  useEffect(() => {
15
17
  const addModal = (window) => {
16
- setModalWindows((prevModals) => [...prevModals, window]);
18
+ if (!window.id) {
19
+ console.warn("Modal added without ID");
20
+ return;
21
+ }
22
+ setModalWindows((prevModals) => {
23
+ if (prevModals.some((modal) => modal.id === window.id)) {
24
+ return prevModals;
25
+ }
26
+ return [...prevModals, window];
27
+ });
17
28
  };
18
29
  const removeModal = (data) => {
19
30
  setModalWindows((prevModals) => {
@@ -21,10 +32,8 @@ const TaskBar = forwardRef(
21
32
  (modal) => modal.id !== data.id
22
33
  );
23
34
  const lastModal = filteredModals.at(-1);
24
- if (!activeWindow && lastModal) {
25
- modals.emit(ModalEvents.ModalVisibilityChanged, {
26
- id: lastModal == null ? void 0 : lastModal.id
27
- });
35
+ if (activeWindow === data.id && lastModal) {
36
+ focus(lastModal.id);
28
37
  }
29
38
  return filteredModals;
30
39
  });
@@ -32,15 +41,18 @@ const TaskBar = forwardRef(
32
41
  const updateVisibleModal = ({ id }) => {
33
42
  setActiveWindow(id);
34
43
  };
35
- modals.on(ModalEvents.AddModal, addModal);
36
- modals.on(ModalEvents.RemoveModal, removeModal);
37
- modals.on(ModalEvents.ModalVisibilityChanged, updateVisibleModal);
44
+ const unsubscribeAdd = subscribe(ModalEvents.AddModal, addModal);
45
+ const unsubscribeRemove = subscribe(ModalEvents.RemoveModal, removeModal);
46
+ const unsubscribeVisibility = subscribe(
47
+ ModalEvents.ModalVisibilityChanged,
48
+ updateVisibleModal
49
+ );
38
50
  return () => {
39
- modals.off(ModalEvents.AddModal, addModal);
40
- modals.off(ModalEvents.RemoveModal, removeModal);
41
- modals.off(ModalEvents.ModalVisibilityChanged, updateVisibleModal);
51
+ unsubscribeAdd();
52
+ unsubscribeRemove();
53
+ unsubscribeVisibility();
42
54
  };
43
- }, []);
55
+ }, [activeWindow, subscribe, focus]);
44
56
  return /* @__PURE__ */ React.createElement(
45
57
  Frame,
46
58
  {
@@ -93,11 +105,11 @@ const TaskBar = forwardRef(
93
105
  active: id === activeWindow,
94
106
  onClick: () => {
95
107
  if (id === activeWindow) {
96
- modals.emit(ModalEvents.MinimizeModal, { id });
97
- setActiveWindow("Minimize");
108
+ minimize(id);
109
+ setActiveWindow(void 0);
98
110
  } else {
99
- modals.emit(ModalEvents.RestoreModal, { id });
100
- modals.emit(ModalEvents.ModalVisibilityChanged, { id });
111
+ restore(id);
112
+ focus(id);
101
113
  }
102
114
  },
103
115
  small: false
package/esm/index.mjs CHANGED
@@ -22,6 +22,8 @@ import { TitleBar } from "./TitleBar/TitleBar.mjs";
22
22
  import { Video } from "./Video/Video.mjs";
23
23
  import { contract } from "./themes/contract.css.mjs";
24
24
  import * as index from "./themes/tokens/index.mjs";
25
+ import { ModalEvents } from "./shared/modal-types.mjs";
26
+ import { useModal } from "./shared/use-modal.mjs";
25
27
  export {
26
28
  Alert,
27
29
  Avatar,
@@ -34,6 +36,7 @@ export {
34
36
  Input,
35
37
  List,
36
38
  Modal,
39
+ ModalEvents,
37
40
  ProgressBar,
38
41
  RadioButton,
39
42
  Range,
@@ -46,5 +49,6 @@ export {
46
49
  Tree,
47
50
  Video,
48
51
  contract,
49
- index as tokens
52
+ index as tokens,
53
+ useModal
50
54
  };
@@ -0,0 +1,35 @@
1
+ class Emitter {
2
+ constructor() {
3
+ this.listeners = {};
4
+ }
5
+ on(eventName, callback) {
6
+ var _a;
7
+ if (!this.listeners[eventName]) {
8
+ this.listeners[eventName] = [];
9
+ }
10
+ (_a = this.listeners[eventName]) == null ? void 0 : _a.push(callback);
11
+ }
12
+ off(eventName, callback) {
13
+ var _a;
14
+ if (this.listeners[eventName]) {
15
+ this.listeners[eventName] = (_a = this.listeners[eventName]) == null ? void 0 : _a.filter(
16
+ (cb) => cb !== callback
17
+ );
18
+ }
19
+ }
20
+ emit(eventName, data) {
21
+ var _a;
22
+ if (this.listeners[eventName]) {
23
+ (_a = this.listeners[eventName]) == null ? void 0 : _a.forEach((callback) => {
24
+ try {
25
+ callback(data);
26
+ } catch (error) {
27
+ console.error(`Error in event listener for ${eventName}:`, error);
28
+ }
29
+ });
30
+ }
31
+ }
32
+ }
33
+ export {
34
+ Emitter
35
+ };
@@ -0,0 +1,5 @@
1
+ import { Emitter } from "./emitter.mjs";
2
+ const modals = new Emitter();
3
+ export {
4
+ modals
5
+ };
@@ -0,0 +1,12 @@
1
+ var ModalEvents = /* @__PURE__ */ ((ModalEvents2) => {
2
+ ModalEvents2["AddModal"] = "add-modal";
3
+ ModalEvents2["RemoveModal"] = "remove-modal";
4
+ ModalEvents2["ModalVisibilityChanged"] = "modal-visibility-changed";
5
+ ModalEvents2["MinimizeModal"] = "minimize-modal";
6
+ ModalEvents2["RestoreModal"] = "restore-modal";
7
+ ModalEvents2["FocusModal"] = "focus-modal";
8
+ return ModalEvents2;
9
+ })(ModalEvents || {});
10
+ export {
11
+ ModalEvents
12
+ };
@@ -0,0 +1,39 @@
1
+ import { useCallback } from "react";
2
+ import { modals } from "./modal-controller.mjs";
3
+ import { ModalEvents } from "./modal-types.mjs";
4
+ const useModal = () => {
5
+ const add = useCallback((modal) => {
6
+ modals.emit(ModalEvents.AddModal, modal);
7
+ }, []);
8
+ const remove = useCallback((id) => {
9
+ modals.emit(ModalEvents.RemoveModal, { id });
10
+ }, []);
11
+ const minimize = useCallback((id) => {
12
+ modals.emit(ModalEvents.MinimizeModal, { id });
13
+ }, []);
14
+ const restore = useCallback((id) => {
15
+ modals.emit(ModalEvents.RestoreModal, { id });
16
+ }, []);
17
+ const focus = useCallback((id) => {
18
+ modals.emit(ModalEvents.ModalVisibilityChanged, { id });
19
+ }, []);
20
+ const toggle = useCallback((id, isActive) => {
21
+ if (isActive) {
22
+ modals.emit(ModalEvents.MinimizeModal, { id });
23
+ } else {
24
+ modals.emit(ModalEvents.RestoreModal, { id });
25
+ modals.emit(ModalEvents.ModalVisibilityChanged, { id });
26
+ }
27
+ }, []);
28
+ const subscribe = useCallback(
29
+ (event, callback) => {
30
+ modals.on(event, callback);
31
+ return () => modals.off(event, callback);
32
+ },
33
+ []
34
+ );
35
+ return { add, remove, minimize, restore, focus, toggle, subscribe };
36
+ };
37
+ export {
38
+ useModal
39
+ };
@@ -1,3 +1,4 @@
1
+ import "./contract.css.ts.vanilla.css.mjs";
1
2
  var contract = { space: { "0": "var(--r95-space-0)", "1": "var(--r95-space-1)", "2": "var(--r95-space-2)", "3": "var(--r95-space-3)", "4": "var(--r95-space-4)", "5": "var(--r95-space-5)", "6": "var(--r95-space-6)", "7": "var(--r95-space-7)", "8": "var(--r95-space-8)", "9": "var(--r95-space-9)", "10": "var(--r95-space-10)", "11": "var(--r95-space-11)", "12": "var(--r95-space-12)", "13": "var(--r95-space-13)", "14": "var(--r95-space-14)", "15": "var(--r95-space-15)", "16": "var(--r95-space-16)", "17": "var(--r95-space-17)", "18": "var(--r95-space-18)", "19": "var(--r95-space-19)", "20": "var(--r95-space-20)" }, colors: { anchor: "var(--r95-color-anchor)", anchorVisited: "var(--r95-color-anchorVisited)", borderDark: "var(--r95-color-borderDark)", borderDarkest: "var(--r95-color-borderDarkest)", borderLight: "var(--r95-color-borderLight)", borderLighter: "var(--r95-color-borderLighter)", borderLightest: "var(--r95-color-borderLightest)", canvas: "var(--r95-color-canvas)", canvasText: "var(--r95-color-canvasText)", headerBackground: "var(--r95-color-headerBackground)", headerNotActiveBackground: "var(--r95-color-headerNotActiveBackground)", headerNotActiveText: "var(--r95-color-headerNotActiveText)", headerText: "var(--r95-color-headerText)", material: "var(--r95-color-material)", materialText: "var(--r95-color-materialText)", materialTextDisabled: "var(--r95-color-materialTextDisabled)", materialTextDisabledShadow: "var(--r95-color-materialTextDisabledShadow)", materialTextInvert: "var(--r95-color-materialTextInvert)", progress: "var(--r95-color-progress)", inputBackground: "var(--r95-color-inputBackground)", inputBackgroundDisabled: "var(--r95-color-inputBackgroundDisabled)" }, zIndices: { modal: "var(--r95-zIndex-modal)", tooltip: "var(--r95-zIndex-tooltip)", taskbar: "var(--r95-zIndex-taskbar)" }, shadows: { out: "var(--r95-shadow-out)", "in": "var(--r95-shadow-in)", input: "var(--r95-shadow-input)" } };
2
3
  export {
3
4
  contract
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react95/core",
3
- "version": "9.5.2",
3
+ "version": "9.6.0-alpha.0",
4
4
  "description": "Windows 95 styleguide",
5
5
  "main": "cjs/index.cjs",
6
6
  "type": "module",
@@ -195,7 +195,7 @@
195
195
  "url": "https://github.com/React95/React95/issues"
196
196
  },
197
197
  "homepage": "https://github.com/React95/React95#readme",
198
- "gitHead": "8661cec1a88979b849d605a7d5533a8e1a0e5d1f",
198
+ "gitHead": "35643bfccf1cb19b59ed30bc1e91944b2aeb14b8",
199
199
  "module": "esm/index.mjs",
200
200
  "private": false,
201
201
  "types": "types/index.d.ts"
@@ -17,6 +17,7 @@ export type ModalDefaultPosition = {
17
17
  };
18
18
  type TitleBarOptions = typeof TitleBar.Close | typeof TitleBar.Help | typeof TitleBar.Maximize | typeof TitleBar.Minimize | typeof TitleBar.Restore;
19
19
  export type ModalProps = {
20
+ id?: string;
20
21
  buttons?: Array<ModalButtons>;
21
22
  menu?: Array<ModalMenu>;
22
23
  dragOptions?: Omit<DragOptions, 'handle'>;
@@ -2391,7 +2392,7 @@ declare const ModalContent: (props: {
2391
2392
  };
2392
2393
  }> & React.RefAttributes<HTMLDivElement>) => React.ReactNode;
2393
2394
  declare const ModalMinimize: OptionReturnType;
2394
- declare const ModalRenderer: ({ hasWindowButton: hasButton, buttons, buttonsAlignment, children, icon, menu, title, dragOptions, titleBarOptions, className, ...rest }: ModalProps, ref: Ref<HTMLDivElement | null>) => React.JSX.Element;
2395
+ declare const ModalRenderer: ({ id: providedId, hasWindowButton: hasButton, buttons, buttonsAlignment, children, icon, menu, title, dragOptions, titleBarOptions, className, ...rest }: ModalProps, ref: Ref<HTMLDivElement | null>) => React.JSX.Element;
2395
2396
  export declare const Modal: ModalProps & typeof ModalRenderer & {
2396
2397
  Content: typeof ModalContent;
2397
2398
  Minimize: typeof ModalMinimize;
package/types/index.d.ts CHANGED
@@ -22,4 +22,5 @@ import { TitleBar } from './TitleBar/TitleBar';
22
22
  import { Video } from './Video/Video';
23
23
  import { contract } from './themes/contract.css';
24
24
  import * as tokens from './themes/tokens';
25
- export { Alert, Avatar, Button, Checkbox, Cursor, Dropdown, Fieldset, Frame, Input, List, Modal, ProgressBar, RadioButton, Range, Tab, Tabs, TaskBar, TextArea, Tree, Tooltip, TitleBar, Video, contract, tokens, };
25
+ import { ModalEvents, useModal } from './shared/events';
26
+ export { Alert, Avatar, Button, Checkbox, Cursor, Dropdown, Fieldset, Frame, Input, List, Modal, ProgressBar, RadioButton, Range, Tab, Tabs, TaskBar, TextArea, Tree, Tooltip, TitleBar, Video, contract, tokens, ModalEvents, useModal, };
@@ -0,0 +1,6 @@
1
+ export declare class Emitter<U extends string, T> {
2
+ private listeners;
3
+ on(eventName: U, callback: (data: Partial<T>) => void): void;
4
+ off(eventName: U, callback: (data: Partial<T>) => void): void;
5
+ emit(eventName: U, data: Partial<T>): void;
6
+ }
@@ -1,21 +1,4 @@
1
- import { ReactElement } from 'react';
2
- export declare class Emitter<U extends string, T> {
3
- private listeners;
4
- on(eventName: U, callback: (data: T) => void): void;
5
- off(eventName: U, callback: (data: T) => void): void;
6
- emit(eventName: U, data: Partial<T>): void;
7
- }
8
- export declare enum ModalEvents {
9
- AddModal = "add-modal",
10
- RemoveModal = "remove-modal",
11
- ModalVisibilityChanged = "modal-visibility-changed",
12
- MinimizeModal = "minimize-modal",
13
- RestoreModal = "restore-modal"
14
- }
15
- export interface ModalWindow {
16
- icon?: ReactElement;
17
- title: string;
18
- hasButton: boolean;
19
- id: string;
20
- }
21
- export declare const modals: Emitter<ModalEvents, ModalWindow>;
1
+ export { Emitter } from './emitter';
2
+ export { ModalEvents, type ModalWindow } from './modal-types';
3
+ export { modals, modalController } from './modal-controller';
4
+ export { useModal } from './use-modal';
@@ -0,0 +1,11 @@
1
+ import { Emitter } from './emitter';
2
+ import { ModalEvents, ModalWindow } from './modal-types';
3
+ export declare const modals: Emitter<ModalEvents, ModalWindow>;
4
+ export declare const modalController: {
5
+ add: (modal: ModalWindow) => void;
6
+ remove: (id: string) => void;
7
+ minimize: (id: string) => void;
8
+ restore: (id: string) => void;
9
+ focus: (id: string) => void;
10
+ toggle: (id: string, isActive: boolean) => void;
11
+ };
@@ -0,0 +1,15 @@
1
+ import { ReactElement } from 'react';
2
+ export declare enum ModalEvents {
3
+ AddModal = "add-modal",
4
+ RemoveModal = "remove-modal",
5
+ ModalVisibilityChanged = "modal-visibility-changed",
6
+ MinimizeModal = "minimize-modal",
7
+ RestoreModal = "restore-modal",
8
+ FocusModal = "focus-modal"
9
+ }
10
+ export interface ModalWindow {
11
+ icon?: ReactElement;
12
+ title: string;
13
+ hasButton: boolean;
14
+ id: string;
15
+ }
@@ -0,0 +1,14 @@
1
+ import { ModalEvents, ModalWindow } from './modal-types';
2
+ /**
3
+ * Hook for controlling and listening to modal events
4
+ * @returns Object with methods to control modals and subscribe to events
5
+ */
6
+ export declare const useModal: () => {
7
+ add: (modal: ModalWindow) => void;
8
+ remove: (id: string) => void;
9
+ minimize: (id: string) => void;
10
+ restore: (id: string) => void;
11
+ focus: (id: string) => void;
12
+ toggle: (id: string, isActive: boolean) => void;
13
+ subscribe: (event: ModalEvents, callback: (data: Partial<ModalWindow>) => void) => () => void;
14
+ };
@@ -1,37 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- class Emitter {
4
- constructor() {
5
- this.listeners = {};
6
- }
7
- on(eventName, callback) {
8
- if (!this.listeners[eventName]) {
9
- this.listeners[eventName] = [];
10
- }
11
- this.listeners[eventName].push(callback);
12
- }
13
- off(eventName, callback) {
14
- if (this.listeners[eventName]) {
15
- this.listeners[eventName] = this.listeners[eventName].filter(
16
- (cb) => cb !== callback
17
- );
18
- }
19
- }
20
- emit(eventName, data) {
21
- if (this.listeners[eventName]) {
22
- this.listeners[eventName].forEach((callback) => callback(data));
23
- }
24
- }
25
- }
26
- var ModalEvents = /* @__PURE__ */ ((ModalEvents2) => {
27
- ModalEvents2["AddModal"] = "add-modal";
28
- ModalEvents2["RemoveModal"] = "remove-modal";
29
- ModalEvents2["ModalVisibilityChanged"] = "modal-visibility-changed";
30
- ModalEvents2["MinimizeModal"] = "minimize-modal";
31
- ModalEvents2["RestoreModal"] = "restore-modal";
32
- return ModalEvents2;
33
- })(ModalEvents || {});
34
- const modals = new Emitter();
35
- exports.Emitter = Emitter;
36
- exports.ModalEvents = ModalEvents;
37
- exports.modals = modals;
@@ -1,37 +0,0 @@
1
- class Emitter {
2
- constructor() {
3
- this.listeners = {};
4
- }
5
- on(eventName, callback) {
6
- if (!this.listeners[eventName]) {
7
- this.listeners[eventName] = [];
8
- }
9
- this.listeners[eventName].push(callback);
10
- }
11
- off(eventName, callback) {
12
- if (this.listeners[eventName]) {
13
- this.listeners[eventName] = this.listeners[eventName].filter(
14
- (cb) => cb !== callback
15
- );
16
- }
17
- }
18
- emit(eventName, data) {
19
- if (this.listeners[eventName]) {
20
- this.listeners[eventName].forEach((callback) => callback(data));
21
- }
22
- }
23
- }
24
- var ModalEvents = /* @__PURE__ */ ((ModalEvents2) => {
25
- ModalEvents2["AddModal"] = "add-modal";
26
- ModalEvents2["RemoveModal"] = "remove-modal";
27
- ModalEvents2["ModalVisibilityChanged"] = "modal-visibility-changed";
28
- ModalEvents2["MinimizeModal"] = "minimize-modal";
29
- ModalEvents2["RestoreModal"] = "restore-modal";
30
- return ModalEvents2;
31
- })(ModalEvents || {});
32
- const modals = new Emitter();
33
- export {
34
- Emitter,
35
- ModalEvents,
36
- modals
37
- };