@linzjs/windows 3.0.0 → 3.1.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,10 +1,21 @@
|
|
|
1
1
|
import { ComponentType, LuiModalAsyncContext, PromiseWithResolve } from "./LuiModalAsyncContext";
|
|
2
2
|
import { LuiModalAsyncInstanceContext } from "./LuiModalAsyncInstanceContext";
|
|
3
|
-
import React, {
|
|
3
|
+
import React, {
|
|
4
|
+
createRef,
|
|
5
|
+
MutableRefObject,
|
|
6
|
+
PropsWithChildren,
|
|
7
|
+
ReactElement,
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from "react";
|
|
4
13
|
import { createPortal } from "react-dom";
|
|
5
14
|
import { useInterval } from "usehooks-ts";
|
|
6
15
|
import { v4 as makeUuid } from "uuid";
|
|
7
16
|
|
|
17
|
+
export const openModalOnAllWindows = createRef<HTMLDivElement>();
|
|
18
|
+
|
|
8
19
|
export interface LuiModalAsyncInstance {
|
|
9
20
|
uuid: string;
|
|
10
21
|
ownerRef: MutableRefObject<HTMLElement | null>;
|
|
@@ -58,6 +69,24 @@ export interface LuiModalAsyncInstance {
|
|
|
58
69
|
*/
|
|
59
70
|
export const LuiModalAsyncContextProvider = ({ children }: PropsWithChildren<unknown>): ReactElement => {
|
|
60
71
|
const [modals, setModals] = useState<LuiModalAsyncInstance[]>([]);
|
|
72
|
+
const openWindowsRef = useRef([window]);
|
|
73
|
+
|
|
74
|
+
// hook window open for opening dialogs across multiple windows
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const oldWindowOpen = window.open;
|
|
77
|
+
window.open = (...props) => {
|
|
78
|
+
const r = oldWindowOpen(...props);
|
|
79
|
+
// We don't want to track non app-windows, e.g. help screens
|
|
80
|
+
if (!props[0]) {
|
|
81
|
+
r && openWindowsRef.current.push(r as typeof window);
|
|
82
|
+
}
|
|
83
|
+
return r;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return () => {
|
|
87
|
+
window.open = oldWindowOpen;
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
61
90
|
|
|
62
91
|
/**
|
|
63
92
|
* Inserts the modal into the page, and removes once modal has a result.
|
|
@@ -104,18 +133,28 @@ export const LuiModalAsyncContextProvider = ({ children }: PropsWithChildren<unk
|
|
|
104
133
|
* Check modal is still attached to a window.
|
|
105
134
|
*/
|
|
106
135
|
const modalHasView = useCallback(
|
|
107
|
-
(modalInstance: LuiModalAsyncInstance): boolean =>
|
|
136
|
+
(modalInstance: LuiModalAsyncInstance): boolean =>
|
|
137
|
+
modalInstance.ownerRef === openModalOnAllWindows || !!modalInstance.ownerRef.current?.ownerDocument?.defaultView,
|
|
108
138
|
[],
|
|
109
139
|
);
|
|
110
140
|
|
|
141
|
+
const windowIsOpen = useCallback((w: typeof window): boolean => !w.closed, []);
|
|
142
|
+
|
|
111
143
|
/**
|
|
112
144
|
* Tidy up modals that have closed because of an external window closing
|
|
113
145
|
*/
|
|
114
146
|
useInterval(() => {
|
|
115
147
|
const newModals = modals.filter(modalHasView);
|
|
116
148
|
newModals.length !== modals.length && setModals(newModals);
|
|
149
|
+
// We could do this with window close listeners, but they are unreliable
|
|
150
|
+
openWindowsRef.current = openWindowsRef.current.filter((w) => !w.closed);
|
|
117
151
|
}, 500);
|
|
118
152
|
|
|
153
|
+
const portalOwners = (modalInstance: LuiModalAsyncInstance) =>
|
|
154
|
+
modalInstance.ownerRef == openModalOnAllWindows
|
|
155
|
+
? openWindowsRef.current.filter(windowIsOpen).map((frame) => frame.document.body)
|
|
156
|
+
: [(modalInstance.ownerRef.current?.ownerDocument ?? document).body];
|
|
157
|
+
|
|
119
158
|
return (
|
|
120
159
|
<LuiModalAsyncContext.Provider
|
|
121
160
|
value={{
|
|
@@ -123,20 +162,20 @@ export const LuiModalAsyncContextProvider = ({ children }: PropsWithChildren<unk
|
|
|
123
162
|
}}
|
|
124
163
|
>
|
|
125
164
|
<>
|
|
126
|
-
{modals.filter(modalHasView).map((modalInstance) =>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)}
|
|
165
|
+
{modals.filter(modalHasView).map((modalInstance) => (
|
|
166
|
+
<LuiModalAsyncInstanceContext.Provider
|
|
167
|
+
key={modalInstance.uuid}
|
|
168
|
+
value={{
|
|
169
|
+
ownerRef: modalInstance.ownerRef,
|
|
170
|
+
close: () => modalInstance.resolve(undefined),
|
|
171
|
+
resolve: modalInstance.resolve,
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
{portalOwners(modalInstance).map((element) =>
|
|
175
|
+
createPortal(<>{modalInstance.componentInstance}</>, element),
|
|
176
|
+
)}
|
|
177
|
+
</LuiModalAsyncInstanceContext.Provider>
|
|
178
|
+
))}
|
|
140
179
|
</>
|
|
141
180
|
<>{children}</>
|
|
142
181
|
</LuiModalAsyncContext.Provider>
|
|
@@ -23,6 +23,7 @@ export interface LuiModalAsyncPrefabButton<RT> {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface useLuiModalPrefabProps<RT extends any = string> extends LuiModalAsyncProps {
|
|
26
|
+
showOnAllWindows?: boolean;
|
|
26
27
|
level: PrefabType;
|
|
27
28
|
title: string;
|
|
28
29
|
helpLink?: string;
|
|
@@ -186,8 +187,7 @@ export const useLuiModalPrefab = () => {
|
|
|
186
187
|
if (props.dontShowAgainSessionKey && LuiModalDontShowSessionCheck(props.dontShowAgainSessionKey)) {
|
|
187
188
|
return Promise.resolve(undefined);
|
|
188
189
|
}
|
|
189
|
-
|
|
190
|
-
return showModal(LuiModalPrefab, props) as PromiseWithResolve<RT>;
|
|
190
|
+
return showModal(LuiModalPrefab, props, { showOnAllWindows: props.showOnAllWindows }) as PromiseWithResolve<RT>;
|
|
191
191
|
},
|
|
192
192
|
[showModal],
|
|
193
193
|
);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { ComponentType, LuiModalAsyncContext, PromiseWithResolve } from "./LuiModalAsyncContext";
|
|
2
2
|
import { ComponentProps, useCallback, useContext, useMemo, useRef } from "react";
|
|
3
|
+
import { openModalOnAllWindows } from "./LuiModalAsyncContextProvider";
|
|
4
|
+
|
|
5
|
+
export interface ShowModalProps {
|
|
6
|
+
showOnAllWindows?: boolean;
|
|
7
|
+
}
|
|
3
8
|
|
|
4
9
|
export const useShowAsyncModal = () => {
|
|
5
10
|
const { showModal } = useContext(LuiModalAsyncContext);
|
|
@@ -10,7 +15,9 @@ export const useShowAsyncModal = () => {
|
|
|
10
15
|
<CT extends ComponentType>(
|
|
11
16
|
component: CT,
|
|
12
17
|
args: Omit<ComponentProps<CT>, "resolve" | "close">,
|
|
13
|
-
|
|
18
|
+
props?: ShowModalProps,
|
|
19
|
+
): PromiseWithResolve<Parameters<ComponentProps<CT>["resolve"]>[0]> =>
|
|
20
|
+
showModal(props?.showOnAllWindows ? openModalOnAllWindows : modalOwnerRef, component, args),
|
|
14
21
|
[showModal],
|
|
15
22
|
);
|
|
16
23
|
|
|
@@ -29,9 +29,18 @@ interface OpenPanelIconProps {
|
|
|
29
29
|
component: () => ReactElement;
|
|
30
30
|
disabled?: boolean;
|
|
31
31
|
className?: string;
|
|
32
|
+
testId?: string;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export const OpenPanelIcon = ({
|
|
35
|
+
export const OpenPanelIcon = ({
|
|
36
|
+
iconTitle,
|
|
37
|
+
uniqueId,
|
|
38
|
+
icon,
|
|
39
|
+
component,
|
|
40
|
+
className,
|
|
41
|
+
disabled,
|
|
42
|
+
testId,
|
|
43
|
+
}: OpenPanelIconProps) => {
|
|
35
44
|
const { openPanel, openPanels } = useContext(PanelsContext);
|
|
36
45
|
const id = useRef(uniqueId ?? uuid());
|
|
37
46
|
|
|
@@ -47,6 +56,7 @@ export const OpenPanelIcon = ({ iconTitle, uniqueId, icon, component, className,
|
|
|
47
56
|
title={iconTitle}
|
|
48
57
|
onClick={() => openPanel(id.current, component)}
|
|
49
58
|
disabled={disabled}
|
|
59
|
+
data-testid={testId}
|
|
50
60
|
>
|
|
51
61
|
<LuiIcon name={icon} alt={iconTitle} size={"md"} />
|
|
52
62
|
</button>
|