@linzjs/windows 7.1.1 → 7.3.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.
@@ -3,7 +3,7 @@ import './PanelBlue.scss';
3
3
  import '@linzjs/lui/dist/scss/base.scss';
4
4
 
5
5
  import clsx from 'clsx';
6
- import { NumberSize } from 're-resizable';
6
+ import { isEqual, pick } from 'lodash-es';
7
7
  import {
8
8
  forwardRef,
9
9
  PropsWithChildren,
@@ -21,21 +21,25 @@ import { PanelInstanceContext } from './PanelInstanceContext';
21
21
  import { PanelsContext } from './PanelsContext';
22
22
  import { PopinWindow } from './PopinWIndow';
23
23
  import { PopoutWindow } from './PopoutWindow';
24
- import { PanelPosition } from './types';
24
+ import { PanelPosition } from './types/PanelPosition';
25
25
  import { PanelProps } from './types/PanelProps';
26
+ import { PanelSize } from './types/PanelSize';
26
27
  import { useRestoreStateFrom } from './usePanelStateHandler';
27
28
 
28
29
  const defaultInitialSize = { width: 320, height: 200 };
29
30
 
30
31
  export const Panel = (props: PanelProps): ReactElement => {
31
32
  const panelRef = useRef<HTMLDivElement>(null);
32
- const { title, size = defaultInitialSize, className, ignoreSavedState, children } = props;
33
+ const { title, size = defaultInitialSize, className, dynamicBounds, children } = props;
33
34
 
34
35
  const { dockElements, nextStackPosition } = useContext(PanelsContext);
35
36
  const { panelPoppedOut, uniqueId, setTitle, dockId, docked } = useContext(PanelInstanceContext);
36
37
 
37
38
  const savedState = useRestoreStateFrom({ uniqueId, panelPoppedOut: false });
38
39
 
40
+ const lastPanelHeightRef = useRef(panelRef.current?.clientHeight);
41
+ lastPanelHeightRef.current = panelRef.current?.clientHeight ?? lastPanelHeightRef.current ?? 400;
42
+
39
43
  useEffect(() => {
40
44
  setTitle(title);
41
45
  }, [setTitle, title]);
@@ -51,63 +55,52 @@ export const Panel = (props: PanelProps): ReactElement => {
51
55
  }, [title]);
52
56
 
53
57
  const [panelPosition, setPanelPosition] = useState((): PanelPosition => {
54
- const pos = props.position;
55
- if (!ignoreSavedState && savedState?.panelPosition) {
56
- return savedState.panelPosition;
58
+ if (dynamicBounds) {
59
+ const result = dynamicBounds();
60
+ if (result) {
61
+ return pick(result, ['x', 'y']);
62
+ }
57
63
  }
58
64
 
59
- // PanelPosition
60
- if (pos && typeof pos === 'object' && 'selector' in pos) {
61
- const b = document.querySelector(pos.selector)?.getBoundingClientRect();
62
- if (b) {
63
- return { x: b[pos.edgeX ?? 'left'] + (pos.offsetX ?? 0), y: b[pos.edgeY ?? 'bottom'] + (pos.offsetY ?? 0) };
64
- }
65
+ const pos = props.position;
66
+ if (savedState?.panelPosition) {
67
+ return savedState.panelPosition;
65
68
  }
66
69
 
67
70
  if (pos === 'center') {
71
+ const height = size.height === 'auto' ? 400 : size.height;
68
72
  return {
69
73
  x: Math.max((window.innerWidth - size.width) / 2, 0),
70
- y: Math.max((window.innerHeight - size.height) / 2, 0),
74
+ y: Math.max((window.innerHeight - height) / 2, 0),
71
75
  };
72
76
  }
73
77
 
74
78
  if (pos != null && pos !== 'tile') {
75
- return pos as PanelPosition;
79
+ return pos;
76
80
  }
77
81
 
78
82
  return nextStackPosition();
79
83
  });
80
84
 
81
- useInterval(
82
- () => {
83
- const pos = props.position;
84
- if (pos && typeof pos === 'object' && 'selector' in pos) {
85
- const b = document.querySelector(pos.selector)?.getBoundingClientRect();
86
- if (b) {
87
- const newPos = {
88
- x: b[pos.edgeX ?? 'left'] + (pos.offsetX ?? 0),
89
- y: b[pos.edgeY ?? 'bottom'] + (pos.offsetY ?? 0),
90
- };
91
- if (newPos.x !== panelPosition.x || newPos.y !== panelPosition.y) {
92
- setPanelPosition(newPos);
93
- }
94
- }
95
- }
96
- },
97
- props.position && typeof props.position === 'object' && 'selector' in props.position ? 250 : null,
98
- );
99
-
100
- const cropSizeToWindow = useCallback((size: NumberSize) => {
85
+ const cropSizeToWindow = useCallback((size: PanelSize): PanelSize => {
101
86
  return typeof window !== 'undefined' && window.innerWidth && window.innerHeight
102
87
  ? {
103
88
  width: Math.min(window.document.body?.clientWidth ?? window.innerWidth, size.width),
104
- height: Math.min(window.document.body?.clientHeight ?? window.innerHeight, size.height),
89
+ height:
90
+ size.height === 'auto'
91
+ ? 'auto'
92
+ : Math.min(window.document.body?.clientHeight ?? window.innerHeight, size.height),
105
93
  }
106
94
  : size;
107
95
  }, []);
108
96
 
109
- const [panelSize, _setPanelSize] = useState<NumberSize>(() => {
110
- if (!ignoreSavedState && savedState?.panelSize) {
97
+ const [panelSize, _setPanelSize] = useState<PanelSize>(() => {
98
+ const result = dynamicBounds?.();
99
+ if (result) {
100
+ return pick(result, ['width', 'height']);
101
+ }
102
+
103
+ if (savedState?.panelSize) {
111
104
  return cropSizeToWindow(savedState.panelSize);
112
105
  }
113
106
 
@@ -115,14 +108,14 @@ export const Panel = (props: PanelProps): ReactElement => {
115
108
  });
116
109
 
117
110
  const setPanelSize = useCallback(
118
- (size: NumberSize) => {
111
+ (size: PanelSize) => {
119
112
  _setPanelSize(cropSizeToWindow(size));
120
113
  },
121
114
  [cropSizeToWindow],
122
115
  );
123
116
 
124
117
  const setInitialPanelSize = useCallback(
125
- (size: NumberSize) => {
118
+ (size: PanelSize) => {
126
119
  // If panel was already sized from state, then don't resize
127
120
  if (savedState?.panelSize) {
128
121
  return;
@@ -133,8 +126,32 @@ export const Panel = (props: PanelProps): ReactElement => {
133
126
  [savedState?.panelSize, setPanelSize],
134
127
  );
135
128
 
136
- const dockElement = docked && dockId && dockElements[dockId];
129
+ const dynamicResize = useCallback(() => {
130
+ const bounds = dynamicBounds?.();
131
+ if (!bounds) {
132
+ return;
133
+ }
134
+ const newPosition = pick(bounds, ['x', 'y']);
135
+ if (!isEqual(newPosition, panelPosition)) {
136
+ setPanelPosition(newPosition);
137
+ }
138
+ const newSize = pick(bounds, ['width', 'height']);
139
+ if (!isEqual(newSize, panelSize)) {
140
+ setPanelSize(newSize);
141
+ }
142
+ }, [dynamicBounds, panelPosition, panelSize, setPanelSize]);
137
143
 
144
+ useEffect(() => {
145
+ window.addEventListener('resize', dynamicResize);
146
+
147
+ return () => {
148
+ window.removeEventListener('resize', dynamicResize);
149
+ };
150
+ }, [dynamicResize]);
151
+
152
+ useInterval(dynamicResize, dynamicBounds ? 250 : null);
153
+
154
+ const dockElement = docked && dockId && dockElements[dockId];
138
155
  return (
139
156
  <>
140
157
  {dockElement && dockElement.isConnected ? (
@@ -143,9 +160,12 @@ export const Panel = (props: PanelProps): ReactElement => {
143
160
  <PopoutWindow
144
161
  name={uniqueId}
145
162
  title={title}
146
- size={size}
163
+ size={{ width: size.width, height: lastPanelHeightRef.current }}
147
164
  popInPanelPosition={panelPosition}
148
- popInPanelSize={panelSize}
165
+ popInPanelSize={{
166
+ width: panelSize.width,
167
+ height: panelSize.height === 'auto' ? lastPanelHeightRef.current : panelSize.height,
168
+ }}
149
169
  >
150
170
  <div
151
171
  ref={panelRef}
@@ -48,7 +48,7 @@ export const PanelInstanceContextProvider = ({
48
48
  panelClose: () => {
49
49
  panelInstance.window?.close();
50
50
  panelInstance.window = null;
51
- closePanel(panelInstance);
51
+ closePanel(panelInstance.uniqueId);
52
52
  },
53
53
  panelTogglePopout: togglePopOut,
54
54
  panelPoppedOut: poppedOut,
@@ -21,8 +21,9 @@ export interface OpenPanelOptions {
21
21
 
22
22
  export interface PanelsContextType {
23
23
  openPanels: Set<string>;
24
- openPanel: (args: OpenPanelOptions) => void;
25
- closePanel: (panelInstance: PanelInstance) => void;
24
+ // returns unique id of panel that was created/or arleady found, otherwise null on error.
25
+ openPanel: (args: OpenPanelOptions) => string | null;
26
+ closePanel: (uniqueId: string) => void;
26
27
  bringPanelToFront: (panelInstance: PanelInstance) => void;
27
28
  nextStackPosition: () => PanelPosition;
28
29
  dockElements: Record<string, HTMLDivElement>;
@@ -30,8 +31,9 @@ export interface PanelsContextType {
30
31
  panelStateOptions?: PanelStateOptions | null;
31
32
  }
32
33
 
33
- const NoContext = () => {
34
+ const NoContext = <T,>(): T => {
34
35
  console.error('Missing PanelContext Provider');
36
+ return undefined as T;
35
37
  };
36
38
 
37
39
  export const PanelsContext = createContext<PanelsContextType>({
@@ -67,7 +67,7 @@ export const PanelsContextProvider = ({
67
67
  );
68
68
 
69
69
  const openPanel = useCallback(
70
- ({ componentFn, poppedOut = false, uniqueId = v4(), onClose }: OpenPanelOptions): void => {
70
+ ({ componentFn, poppedOut = false, uniqueId = v4(), onClose }: OpenPanelOptions): string | null => {
71
71
  try {
72
72
  const existingPanelInstance = panelInstances.find((pi) => pi.uniqueId === uniqueId);
73
73
  if (existingPanelInstance) {
@@ -76,7 +76,7 @@ export const PanelsContextProvider = ({
76
76
  } else {
77
77
  bringPanelToFront(existingPanelInstance);
78
78
  }
79
- return;
79
+ return existingPanelInstance.uniqueId;
80
80
  }
81
81
 
82
82
  // If there are any exceptions the modal won't show
@@ -91,16 +91,18 @@ export const PanelsContextProvider = ({
91
91
  onClose,
92
92
  },
93
93
  ]);
94
+ return uniqueId;
94
95
  } catch (e) {
95
96
  console.error(e);
97
+ return null;
96
98
  }
97
99
  },
98
100
  [baseZIndex, bringPanelToFront, panelInstances],
99
101
  );
100
102
 
101
103
  const closePanel = useCallback(
102
- (closePanelInstance: PanelInstance | PanelInstance[]) => {
103
- const panelNames = castArray(closePanelInstance).map((pi) => pi.uniqueId);
104
+ (closePanelUniqueIds: string | string[]) => {
105
+ const panelNames = castArray(closePanelUniqueIds);
104
106
  const [closedPanelInstances, stillOpenPanelInstances] = partition(panelInstances, (pi) =>
105
107
  panelNames.includes(pi.uniqueId),
106
108
  );
@@ -120,7 +122,7 @@ export const PanelsContextProvider = ({
120
122
  */
121
123
  useInterval(() => {
122
124
  // close any panels that have a window that is closed
123
- closePanel(panelInstances.filter((pi) => pi.window?.closed));
125
+ closePanel(panelInstances.filter((pi) => pi.window?.closed).map((panel) => panel.uniqueId));
124
126
  }, 500);
125
127
 
126
128
  /**
@@ -1,4 +1,5 @@
1
1
  import clsx from 'clsx';
2
+ import { pick } from 'lodash-es';
2
3
  import { Dispatch, ReactElement, useCallback, useContext, useEffect, useRef } from 'react';
3
4
  import { Rnd } from 'react-rnd';
4
5
 
@@ -37,9 +38,14 @@ export function PopinWindow({
37
38
 
38
39
  const centerWindow = useCallback(() => {
39
40
  const b = panelRef.current?.getSelfElement()?.getBoundingClientRect();
41
+ let height = b?.height ?? size.height;
42
+ // You can't auto height a centered panel
43
+ if (height === 'auto') {
44
+ height = 400;
45
+ }
40
46
  setPanelPosition({
41
47
  x: Math.max((window.innerWidth - (b?.width ?? size.width)) / 2, 0),
42
- y: Math.max((window.innerHeight - (b?.height ?? size.height)) / 2, 0),
48
+ y: Math.max((window.innerHeight - height) / 2, 0),
43
49
  });
44
50
  }, [setPanelPosition, size.height, size.width]);
45
51
 
@@ -106,7 +112,7 @@ export function PopinWindow({
106
112
  minWidth={minWidth}
107
113
  minHeight={minHeight}
108
114
  position={panelPosition}
109
- size={panelSize}
115
+ size={panelSize.height === 'auto' ? (pick(panelSize, 'width') as PanelSize) : panelSize}
110
116
  style={{ zIndex }}
111
117
  disableDragging={!resizeable}
112
118
  enableResizing={resizeable}
@@ -6,15 +6,15 @@ import ReactDOM from 'react-dom';
6
6
  import { makePopoutId } from './generateId';
7
7
  import { openExternalWindow, OpenExternalWindowResponse } from './openExternalWindow';
8
8
  import { PanelInstanceContext } from './PanelInstanceContext';
9
- import { PanelPosition, PanelSize } from './types';
9
+ import { PanelPosition, PanelSizeExact } from './types';
10
10
  import { useRestoreStateFrom, useSaveStateIn } from './usePanelStateHandler';
11
11
 
12
12
  interface _PopoutWindowProps {
13
13
  name: string;
14
14
  title?: string; // The title of the popout window
15
- size: PanelSize;
15
+ size: PanelSizeExact;
16
16
  popInPanelPosition: PanelPosition;
17
- popInPanelSize: PanelSize;
17
+ popInPanelSize: PanelSizeExact;
18
18
 
19
19
  //says if we want to watch for any dynamic style changes
20
20
  //only needed when using UI libraries like MUI
@@ -62,7 +62,7 @@ export const PopoutWindow = ({
62
62
  const oew = openExternalWindowFn ?? openExternalWindow;
63
63
  const { externalWindow, onCloseExternalWindow } = oew({
64
64
  title,
65
- size: panelSize,
65
+ size: panelSize.height === 'auto' ? { ...panelSize, height: 400 } : (panelSize as PanelSizeExact),
66
66
  setContainerElement,
67
67
  setHeadElement,
68
68
  observeStyleChanges,
@@ -2,7 +2,7 @@ import { Dispatch, SetStateAction } from 'react';
2
2
 
3
3
  import { makePopoutId, popoutWindowDivId } from './generateId';
4
4
  import { copyStyleSheets, observeStyleSheetChanges } from './handleStyleSheetsChanges';
5
- import { PanelPosition } from './types';
5
+ import { PanelPosition, PanelSizeExact } from './types';
6
6
 
7
7
  export type OpenExternalWindowResponse = {
8
8
  externalWindow: Window | null;
@@ -26,7 +26,7 @@ export const openExternalWindow = ({
26
26
  position,
27
27
  }: {
28
28
  title: string;
29
- size: { width: number; height: number };
29
+ size: PanelSizeExact;
30
30
  setContainerElement: Dispatch<SetStateAction<HTMLElement | undefined>>;
31
31
  setHeadElement: Dispatch<SetStateAction<HTMLElement | undefined>>;
32
32
  observeStyleChanges?: boolean;
@@ -3,13 +3,7 @@ import { PropsWithChildren } from 'react';
3
3
  import { PanelPosition } from './PanelPosition';
4
4
  import { PanelSize } from './PanelSize';
5
5
 
6
- export interface PanelRelativePositon {
7
- selector: string;
8
- edgeX?: 'left' | 'right';
9
- edgeY?: 'top' | 'bottom';
10
- offsetX?: number;
11
- offsetY?: number;
12
- }
6
+ export type PanelBounds = PanelPosition & PanelSize;
13
7
 
14
8
  interface _PanelProps {
15
9
  className?: string;
@@ -18,11 +12,11 @@ interface _PanelProps {
18
12
  minHeight?: number | string;
19
13
  minWidth?: number | string;
20
14
  modal?: boolean;
21
- position?: PanelPosition | PanelRelativePositon | 'tile' | 'center';
15
+ dynamicBounds?: () => PanelBounds | null;
16
+ position?: PanelPosition | 'tile' | 'center';
22
17
  resizeable?: boolean;
23
18
  size?: PanelSize;
24
19
  title: string;
25
- ignoreSavedState?: boolean;
26
20
  }
27
21
 
28
22
  export type PanelProps = PropsWithChildren<_PanelProps>;
@@ -1,4 +1,9 @@
1
- export interface PanelSize {
1
+ export interface PanelSizeExact {
2
2
  height: number;
3
3
  width: number;
4
4
  }
5
+
6
+ export interface PanelSize {
7
+ height: number | 'auto';
8
+ width: number;
9
+ }
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "popout"
14
14
  ],
15
15
  "main": "./dist/index.ts",
16
- "version": "7.1.1",
16
+ "version": "7.3.0",
17
17
  "peerDependencies": {
18
18
  "@linzjs/lui": ">=21",
19
19
  "lodash-es": ">=4",