@linzjs/windows 2.3.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, { MutableRefObject, PropsWithChildren, ReactElement, useCallback, useState } from "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 => !!modalInstance.ownerRef.current?.ownerDocument?.defaultView,
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
- createPortal(
128
- <LuiModalAsyncInstanceContext.Provider
129
- value={{
130
- ownerRef: modalInstance.ownerRef,
131
- close: () => modalInstance.resolve(undefined),
132
- resolve: modalInstance.resolve,
133
- }}
134
- >
135
- {modalInstance.componentInstance}
136
- </LuiModalAsyncInstanceContext.Provider>,
137
- (modalInstance.ownerRef.current?.ownerDocument ?? document).body,
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
- ): PromiseWithResolve<Parameters<ComponentProps<CT>["resolve"]>[0]> => showModal(modalOwnerRef, component, args),
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 = ({ iconTitle, uniqueId, icon, component, className, disabled }: OpenPanelIconProps) => {
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>
package/package.json CHANGED
@@ -13,12 +13,12 @@
13
13
  "popout"
14
14
  ],
15
15
  "main": "./dist/index.ts",
16
- "version": "2.3.0",
16
+ "version": "3.1.0",
17
17
  "peerDependencies": {
18
18
  "@linzjs/lui": "^21",
19
19
  "lodash-es": ">=4",
20
- "react": ">=17",
21
- "react-dom": ">=17"
20
+ "react": ">=18",
21
+ "react-dom": ">=18"
22
22
  },
23
23
  "files": [
24
24
  "dist"
@@ -47,84 +47,85 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@emotion/cache": "^11.11.0",
50
- "@emotion/react": "^11.11.1",
51
- "@emotion/styled": "^11.11.0",
52
- "@linzjs/lui": "^21",
50
+ "@emotion/react": "^11.11.4",
51
+ "@emotion/styled": "^11.11.5",
53
52
  "lodash-es": ">=4",
54
- "react": ">=18",
55
- "react-dom": ">=18",
56
- "react-rnd": "^10.4.1",
57
- "usehooks-ts": "^2.9.1",
53
+ "react-rnd": "^10.4.11",
54
+ "usehooks-ts": "^3.1.0",
58
55
  "uuid": "^9.0.1"
59
56
  },
60
57
  "devDependencies": {
58
+ "@chromatic-com/storybook": "^1.5.0",
61
59
  "@esbuild-plugins/node-globals-polyfill": "^0.2.3",
62
60
  "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
63
- "@linzjs/step-ag-grid": "^19.1.0",
64
- "@rollup/plugin-commonjs": "^25.0.4",
65
- "@rollup/plugin-json": "^6.0.0",
66
- "@rollup/plugin-node-resolve": "^15.2.1",
67
- "@storybook/addon-docs": "^7.6.4",
68
- "@storybook/addon-essentials": "^7.6.4",
69
- "@storybook/addon-interactions": "^7.6.4",
70
- "@storybook/addon-links": "^7.6.4",
71
- "@storybook/addon-mdx-gfm": "^7.6.4",
72
- "@storybook/blocks": "^7.6.4",
73
- "@storybook/jest": "^0.2.3",
74
- "@storybook/preset-create-react-app": "^7.6.4",
75
- "@storybook/react": "^7.6.4",
76
- "@storybook/react-vite": "^7.6.4",
77
- "@storybook/test-runner": "^0.16.0",
78
- "@storybook/testing-library": "^0.2.2",
79
- "@testing-library/jest-dom": "^6.1.3",
80
- "@testing-library/react": "^14.0.0",
61
+ "@linzjs/lui": "^21.33.0",
62
+ "@linzjs/step-ag-grid": "^21.1.2",
63
+ "@rollup/plugin-commonjs": "^25.0.8",
64
+ "@rollup/plugin-json": "^6.1.0",
65
+ "@rollup/plugin-node-resolve": "^15.2.3",
66
+ "@storybook/addon-docs": "^8.1.5",
67
+ "@storybook/addon-essentials": "^8.1.5",
68
+ "@storybook/addon-interactions": "^8.1.5",
69
+ "@storybook/addon-links": "^8.1.5",
70
+ "@storybook/addon-mdx-gfm": "^8.1.5",
71
+ "@storybook/blocks": "^8.1.5",
72
+ "@storybook/react": "^8.1.5",
73
+ "@storybook/react-vite": "^8.1.5",
74
+ "@storybook/test": "^8.1.5",
75
+ "@storybook/test-runner": "^0.18.2",
76
+ "@testing-library/jest-dom": "^6.1.6",
77
+ "@testing-library/react": "^16.0.0",
81
78
  "@testing-library/user-event": "^14.5.1",
82
- "@trivago/prettier-plugin-sort-imports": "^4.2.0",
83
- "@types/jest": "^29.5.5",
84
- "@types/lodash-es": "^4.17.9",
85
- "@types/node": "^20.7.1",
86
- "@types/react": "^18.2.23",
87
- "@types/react-dom": "^18.2.8",
88
- "@types/uuid": "^9.0.4",
89
- "ag-grid-community": "^29.3.5",
90
- "ag-grid-react": "^29.3.5",
91
- "esbuild": "^0.19.9",
92
- "eslint": "^8.50.0",
93
- "eslint-config-prettier": "^8.10.0",
79
+ "@trivago/prettier-plugin-sort-imports": "^4.3.0",
80
+ "@types/jest": "^29.5.12",
81
+ "@types/lodash-es": "^4.17.12",
82
+ "@types/node": "^20.14.1",
83
+ "@types/react": "^18.3.3",
84
+ "@types/react-dom": "^18.3.0",
85
+ "@types/uuid": "^9.0.8",
86
+ "@typescript-eslint/eslint-plugin": "^7.12.0",
87
+ "@typescript-eslint/parser": "^7.12.0",
88
+ "ag-grid-community": "^31.3.2",
89
+ "ag-grid-react": "^31.3.2",
90
+ "esbuild": "^0.21.4",
91
+ "eslint": "^8.57.0",
92
+ "eslint-config-prettier": "^9.1.0",
94
93
  "eslint-config-react-app": "^7.0.1",
95
- "eslint-plugin-deprecation": "^1.6.0",
96
- "eslint-plugin-import": "^2.28.1",
97
- "eslint-plugin-jest": "^27.4.0",
98
- "eslint-plugin-jsx-a11y": "^6.7.1",
99
- "eslint-plugin-prettier": "^4.2.1",
100
- "eslint-plugin-react": "^7.33.2",
101
- "eslint-plugin-react-hooks": "^4.6.0",
102
- "eslint-plugin-storybook": "^0.6.15",
103
- "eslint-plugin-testing-library": "^5.11.1",
94
+ "eslint-plugin-deprecation": "^3.0.0",
95
+ "eslint-plugin-import": "^2.29.1",
96
+ "eslint-plugin-jest": "^28.5.0",
97
+ "eslint-plugin-jsx-a11y": "^6.8.0",
98
+ "eslint-plugin-prettier": "^5.1.3",
99
+ "eslint-plugin-react": "^7.34.2",
100
+ "eslint-plugin-react-hooks": "^4.6.2",
101
+ "eslint-plugin-storybook": "^0.8.0",
102
+ "eslint-plugin-testing-library": "^6.2.2",
104
103
  "jest": "^29.7.0",
105
104
  "jest-canvas-mock": "^2.5.2",
106
105
  "jest-environment-jsdom": "^29.7.0",
107
106
  "jest-expect-message": "^1.1.3",
108
107
  "mkdirp": "^3.0.1",
109
108
  "npm-run-all": "^4.1.5",
110
- "prettier": "^2.8.8",
109
+ "prettier": "^3.3.0",
111
110
  "prop-types": "^15.8.1",
112
- "react-scripts": "5.0.1",
113
- "rollup": "^3.29.3",
111
+ "react": "^18.3.1",
112
+ "react-app-polyfill": "^3.0.0",
113
+ "react-dom": "^18.3.1",
114
+ "rollup": "^4.18.0",
114
115
  "rollup-plugin-copy": "^3.5.0",
115
- "sass": "^1.68.0",
116
- "sass-loader": "^13.3.2",
117
- "semantic-release": "^22.0.10",
118
- "storybook": "^7.6.4",
119
- "style-loader": "^3.3.3",
120
- "stylelint": "^14.16.1",
121
- "stylelint-config-prettier": "^9.0.5",
122
- "stylelint-config-recommended-scss": "^8.0.0",
123
- "stylelint-config-standard": "^29.0.0",
124
- "stylelint-prettier": "3.0.0",
125
- "stylelint-scss": "5.0.1",
126
- "typescript": "^4.9.5",
127
- "vite": "^4.4.9"
116
+ "sass": "^1.77.4",
117
+ "sass-loader": "^14.2.1",
118
+ "semantic-release": "^22.0.12",
119
+ "storybook": "^8.1.5",
120
+ "style-loader": "^4.0.0",
121
+ "stylelint": "^16.6.1",
122
+ "stylelint-config-recommended": "^14.0.0",
123
+ "stylelint-config-recommended-scss": "^14.0.0",
124
+ "stylelint-config-standard": "^36.0.0",
125
+ "stylelint-prettier": "5.0.0",
126
+ "stylelint-scss": "6.3.1",
127
+ "typescript": "^5.4.5",
128
+ "vite": "^5.2.12"
128
129
  },
129
130
  "eslintConfig": {
130
131
  "extends": [