@os-design/core 1.0.277 → 1.0.279
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.
- package/dist/Popover/index.d.ts +5 -5
- package/dist/Popover/index.d.ts.map +1 -1
- package/dist/Popover/index.js +125 -45
- package/dist/Popover/utils/usePopoverPosition.d.ts +19 -9
- package/dist/Popover/utils/usePopoverPosition.d.ts.map +1 -1
- package/dist/Popover/utils/usePopoverPosition.js +89 -72
- package/dist/Select/index.d.ts.map +1 -1
- package/dist/Select/index.js +4 -26
- package/package.json +3 -3
- package/src/Popover/index.tsx +156 -57
- package/src/Popover/utils/usePopoverPosition.ts +122 -83
- package/src/Select/index.tsx +3 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@os-design/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.279",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"repository": "git@gitlab.com:os-team/libs/os-design.git",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@os-design/styles": "^1.0.65",
|
|
40
40
|
"@os-design/theming": "^1.0.61",
|
|
41
41
|
"@os-design/time-picker-utils": "^1.0.23",
|
|
42
|
-
"@os-design/utils": "^1.0.
|
|
42
|
+
"@os-design/utils": "^1.0.87",
|
|
43
43
|
"facepaint": "^1.2.1",
|
|
44
44
|
"react-focus-lock": "^2.13.2",
|
|
45
45
|
"react-window": "^1.8.10"
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"react": "18",
|
|
59
59
|
"react-dom": "18"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "322503d6cbd4be09b4709a7e132f4ad814ba8263"
|
|
62
62
|
}
|
package/src/Popover/index.tsx
CHANGED
|
@@ -24,20 +24,22 @@ import {
|
|
|
24
24
|
import usePopoverPosition, {
|
|
25
25
|
type Placement,
|
|
26
26
|
type Rect,
|
|
27
|
+
type ScrollableRect,
|
|
28
|
+
type Size,
|
|
27
29
|
} from './utils/usePopoverPosition.js';
|
|
28
30
|
|
|
29
31
|
type JsxDivProps = Omit<JSX.IntrinsicElements['div'], 'ref'>;
|
|
30
32
|
export interface PopoverProps extends JsxDivProps, WithSize {
|
|
31
|
-
/**
|
|
32
|
-
* The element next to which the popover appears.
|
|
33
|
-
* @default undefined
|
|
34
|
-
*/
|
|
35
|
-
trigger?: RefObject<Element> | Rect;
|
|
36
33
|
/**
|
|
37
34
|
* Where the popover will be rendered.
|
|
38
35
|
* @default document.body
|
|
39
36
|
*/
|
|
40
37
|
container?: RefObject<Element> | Element | null;
|
|
38
|
+
/**
|
|
39
|
+
* The element next to which the popover appears.
|
|
40
|
+
* @default undefined
|
|
41
|
+
*/
|
|
42
|
+
trigger?: RefObject<Element> | Element | Rect | null;
|
|
41
43
|
/**
|
|
42
44
|
* On which side of the element the popover will appear.
|
|
43
45
|
* @default top
|
|
@@ -75,6 +77,18 @@ const fadeOut = keyframes`
|
|
|
75
77
|
to { opacity: 0; }
|
|
76
78
|
`;
|
|
77
79
|
|
|
80
|
+
const widthStyles = (p) =>
|
|
81
|
+
p.width !== null &&
|
|
82
|
+
css`
|
|
83
|
+
width: ${p.width}px;
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const heightStyles = (p) =>
|
|
87
|
+
p.height !== null &&
|
|
88
|
+
css`
|
|
89
|
+
height: ${p.height}px;
|
|
90
|
+
`;
|
|
91
|
+
|
|
78
92
|
const visibleStyles = (p) =>
|
|
79
93
|
p.visible &&
|
|
80
94
|
css`
|
|
@@ -90,6 +104,8 @@ const invisibleStyles = (p) =>
|
|
|
90
104
|
interface ContainerProps extends Pick<PopoverProps, 'visible' | 'size'> {
|
|
91
105
|
top: number;
|
|
92
106
|
left: number;
|
|
107
|
+
width: number | null;
|
|
108
|
+
height: number | null;
|
|
93
109
|
}
|
|
94
110
|
const Container = styled(
|
|
95
111
|
'div',
|
|
@@ -106,26 +122,21 @@ const Container = styled(
|
|
|
106
122
|
box-shadow: 0 0.15em 0.8em ${(p) => clr(p.theme.popoverColorBoxShadow)};
|
|
107
123
|
z-index: 1000; // Greater than the z-index of the Drawer
|
|
108
124
|
|
|
125
|
+
${widthStyles};
|
|
126
|
+
${heightStyles};
|
|
109
127
|
${visibleStyles};
|
|
110
128
|
${invisibleStyles};
|
|
111
129
|
${sizeStyles};
|
|
112
130
|
`;
|
|
113
131
|
|
|
114
|
-
const emptyRect: Rect = {
|
|
115
|
-
top: 0,
|
|
116
|
-
left: 0,
|
|
117
|
-
width: 0,
|
|
118
|
-
height: 0,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
132
|
/**
|
|
122
133
|
* The pop-up window located next to the element.
|
|
123
134
|
*/
|
|
124
135
|
const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
|
125
136
|
(
|
|
126
137
|
{
|
|
127
|
-
trigger,
|
|
128
138
|
container,
|
|
139
|
+
trigger,
|
|
129
140
|
placement = 'top',
|
|
130
141
|
gap = 0.2,
|
|
131
142
|
flip = true,
|
|
@@ -138,73 +149,144 @@ const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
|
|
138
149
|
ref
|
|
139
150
|
) => {
|
|
140
151
|
const [popoverRef, mergedPopoverRef] = useForwardedRef(ref);
|
|
141
|
-
const [
|
|
142
|
-
const [
|
|
152
|
+
const [popoverSize, setPopoverSize] = useState<Size | null>(null);
|
|
153
|
+
const [containerRect, setContainerRect] = useState<ScrollableRect | null>(
|
|
154
|
+
null
|
|
155
|
+
);
|
|
156
|
+
const [triggerRect, setTriggerRect] = useState<Rect | null>(null);
|
|
157
|
+
|
|
143
158
|
const { theme } = useTheme();
|
|
144
159
|
const mounted = useClosable(visible, theme.transitionDelay);
|
|
145
160
|
|
|
146
|
-
//
|
|
147
|
-
const popoverResizeListener = useCallback(() => {
|
|
148
|
-
if (!popoverRef.current) return;
|
|
149
|
-
setPopoverRect(popoverRef.current.getBoundingClientRect());
|
|
150
|
-
}, [popoverRef]);
|
|
151
|
-
useResizeObserver(
|
|
152
|
-
popoverRef.current as HTMLDivElement,
|
|
153
|
-
popoverResizeListener
|
|
154
|
-
);
|
|
155
|
-
|
|
161
|
+
// Save the popover size when it has been rendered
|
|
156
162
|
const measuredPopoverRef = useCallback<RefCallback<HTMLDivElement>>(
|
|
157
163
|
(node) => {
|
|
158
164
|
if (node === null) return;
|
|
159
|
-
|
|
165
|
+
const rect = node.getBoundingClientRect();
|
|
166
|
+
setPopoverSize({
|
|
167
|
+
width: rect.width,
|
|
168
|
+
height: rect.height,
|
|
169
|
+
});
|
|
160
170
|
mergedPopoverRef(node);
|
|
161
171
|
},
|
|
162
172
|
[mergedPopoverRef]
|
|
163
173
|
);
|
|
164
174
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
const triggerResizeListener = useCallback(() => {
|
|
175
|
+
// Update the popover size when it has been resized
|
|
176
|
+
const popoverResizeListener = useCallback(() => {
|
|
168
177
|
window.requestAnimationFrame(() => {
|
|
169
|
-
if (!
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
if (!popoverRef.current) return;
|
|
179
|
+
const rect = popoverRef.current.getBoundingClientRect();
|
|
180
|
+
setPopoverSize({
|
|
181
|
+
width: rect.width,
|
|
182
|
+
height: rect.height,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}, [popoverRef]);
|
|
186
|
+
useResizeObserver(popoverRef.current, popoverResizeListener);
|
|
187
|
+
|
|
188
|
+
const containerRef = useMemo<RefObject<Element>>(() => {
|
|
189
|
+
if (typeof window === 'undefined') {
|
|
190
|
+
return { current: null };
|
|
191
|
+
}
|
|
192
|
+
if (!container) {
|
|
193
|
+
return { current: document.body };
|
|
194
|
+
}
|
|
195
|
+
return 'current' in container ? container : { current: container };
|
|
196
|
+
}, [container]);
|
|
197
|
+
|
|
198
|
+
// Update the container size and its scroll offsets initially and when it has been changed.
|
|
199
|
+
// Must depend on `visible` to update the position when the popover is visible.
|
|
200
|
+
const containerListener = useCallback(() => {
|
|
201
|
+
if (!visible) return;
|
|
202
|
+
window.requestAnimationFrame(() => {
|
|
203
|
+
if (containerRef.current) {
|
|
204
|
+
if (containerRef.current === document.body) {
|
|
205
|
+
setContainerRect({
|
|
206
|
+
top: 0,
|
|
207
|
+
left: 0,
|
|
208
|
+
width: window.innerWidth,
|
|
209
|
+
height: window.innerHeight,
|
|
210
|
+
scrollTop: window.scrollY,
|
|
211
|
+
scrollLeft: window.scrollX,
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
215
|
+
setContainerRect({
|
|
216
|
+
top: rect.top,
|
|
217
|
+
left: rect.left,
|
|
218
|
+
width: rect.width,
|
|
219
|
+
height: rect.height,
|
|
220
|
+
scrollTop: containerRef.current.scrollTop,
|
|
221
|
+
scrollLeft: containerRef.current.scrollLeft,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
180
224
|
}
|
|
181
|
-
setTriggerRect(rect);
|
|
182
225
|
});
|
|
183
|
-
}, [
|
|
226
|
+
}, [containerRef, visible]);
|
|
184
227
|
useBrowserLayoutEffect(() => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}, [triggerResizeListener, visible]);
|
|
188
|
-
useResizeObserver(trigger as never, triggerResizeListener);
|
|
228
|
+
containerListener();
|
|
229
|
+
}, [containerListener]);
|
|
189
230
|
useEvent(
|
|
190
|
-
|
|
231
|
+
typeof window !== 'undefined' && containerRef.current === document.body
|
|
232
|
+
? window
|
|
233
|
+
: null,
|
|
191
234
|
'resize',
|
|
192
|
-
|
|
235
|
+
containerListener
|
|
236
|
+
);
|
|
237
|
+
useResizeObserver(
|
|
238
|
+
typeof window !== 'undefined' && containerRef.current !== document.body
|
|
239
|
+
? containerRef.current
|
|
240
|
+
: null,
|
|
241
|
+
containerListener
|
|
242
|
+
);
|
|
243
|
+
useEvent(
|
|
244
|
+
typeof window !== 'undefined'
|
|
245
|
+
? containerRef.current === document.body
|
|
246
|
+
? document
|
|
247
|
+
: containerRef.current
|
|
248
|
+
: null,
|
|
249
|
+
'scroll',
|
|
250
|
+
containerListener
|
|
193
251
|
);
|
|
194
|
-
useEvent(document, 'scroll', triggerResizeListener);
|
|
195
252
|
|
|
253
|
+
const triggerRef = useMemo<RefObject<Element>>(() => {
|
|
254
|
+
if (!trigger || 'top' in trigger) {
|
|
255
|
+
return { current: null };
|
|
256
|
+
}
|
|
257
|
+
return 'current' in trigger ? trigger : { current: trigger };
|
|
258
|
+
}, [trigger]);
|
|
259
|
+
|
|
260
|
+
// Update the trigger size and its position initially and when it has been changed
|
|
261
|
+
const triggerListener = useCallback(() => {
|
|
262
|
+
if (!containerRect) return;
|
|
263
|
+
window.requestAnimationFrame(() => {
|
|
264
|
+
if (triggerRef.current) {
|
|
265
|
+
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
266
|
+
setTriggerRect({
|
|
267
|
+
top: triggerRect.top - containerRect.top,
|
|
268
|
+
left: triggerRect.left - containerRect.left,
|
|
269
|
+
width: triggerRect.width,
|
|
270
|
+
height: triggerRect.height,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}, [containerRect, triggerRef]);
|
|
275
|
+
useBrowserLayoutEffect(() => {
|
|
276
|
+
triggerListener();
|
|
277
|
+
}, [triggerListener]);
|
|
278
|
+
useResizeObserver(triggerRef.current, triggerListener);
|
|
196
279
|
useEffect(() => {
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
280
|
+
if (trigger && 'top' in trigger) {
|
|
281
|
+
setTriggerRect(trigger);
|
|
282
|
+
}
|
|
200
283
|
}, [trigger]);
|
|
201
284
|
|
|
285
|
+
// Set the aria tags to support accessibility features
|
|
202
286
|
const popoverId = useMemo(
|
|
203
287
|
() => id || `popover-${Math.random().toString(36).slice(2, 11)}`,
|
|
204
288
|
[id]
|
|
205
289
|
);
|
|
206
|
-
|
|
207
|
-
// Set the aria tags to support accessibility features
|
|
208
290
|
useBrowserLayoutEffect(() => {
|
|
209
291
|
if (!trigger) return;
|
|
210
292
|
const { current } = trigger as RefObject<Element>;
|
|
@@ -223,23 +305,40 @@ const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
|
|
223
305
|
|
|
224
306
|
// Get the popover coordinates
|
|
225
307
|
const { top, left } = usePopoverPosition({
|
|
226
|
-
|
|
227
|
-
|
|
308
|
+
popoverSize,
|
|
309
|
+
containerRect,
|
|
310
|
+
triggerRect,
|
|
228
311
|
placement,
|
|
229
312
|
gap,
|
|
230
313
|
flip,
|
|
231
314
|
});
|
|
232
315
|
|
|
316
|
+
const width = useMemo(() => {
|
|
317
|
+
if (!triggerRect) return null;
|
|
318
|
+
return placement === 'top-full' || placement === 'bottom-full'
|
|
319
|
+
? triggerRect.width
|
|
320
|
+
: null;
|
|
321
|
+
}, [placement, triggerRect]);
|
|
322
|
+
|
|
323
|
+
const height = useMemo(() => {
|
|
324
|
+
if (!triggerRect) return null;
|
|
325
|
+
return placement === 'left-full' || placement === 'right-full'
|
|
326
|
+
? triggerRect.width
|
|
327
|
+
: null;
|
|
328
|
+
}, [placement, triggerRect]);
|
|
329
|
+
|
|
233
330
|
// Close the popover when the user clicks outside of it
|
|
234
331
|
useClickOutside(popoverRef, onClose);
|
|
235
332
|
|
|
236
333
|
if (!mounted) return null;
|
|
237
334
|
|
|
238
335
|
return (
|
|
239
|
-
<Portal container={
|
|
336
|
+
<Portal container={containerRef.current}>
|
|
240
337
|
<Container
|
|
241
338
|
top={top}
|
|
242
339
|
left={left}
|
|
340
|
+
width={width}
|
|
341
|
+
height={height}
|
|
243
342
|
visible={visible}
|
|
244
343
|
id={popoverId}
|
|
245
344
|
role='dialog'
|
|
@@ -2,27 +2,39 @@ import { useFontSize } from '@os-design/utils';
|
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
|
|
4
4
|
type Side = 'top' | 'left' | 'right' | 'bottom';
|
|
5
|
-
type Alignment = 'start' | 'end';
|
|
5
|
+
type Alignment = 'start' | 'end' | 'full';
|
|
6
6
|
type AlignedPlacement = `${Side}-${Alignment}`;
|
|
7
7
|
|
|
8
8
|
export type Placement = Side | AlignedPlacement;
|
|
9
9
|
|
|
10
|
-
export interface
|
|
11
|
-
top: number;
|
|
12
|
-
left: number;
|
|
10
|
+
export interface Size {
|
|
13
11
|
width: number;
|
|
14
12
|
height: number;
|
|
15
13
|
}
|
|
16
14
|
|
|
15
|
+
export interface Rect extends Size {
|
|
16
|
+
top: number;
|
|
17
|
+
left: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ScrollableRect extends Rect {
|
|
21
|
+
scrollTop: number;
|
|
22
|
+
scrollLeft: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
interface UsePopoverPositionProps {
|
|
18
26
|
/**
|
|
19
|
-
* The
|
|
27
|
+
* The size of the popover.
|
|
20
28
|
*/
|
|
21
|
-
|
|
29
|
+
popoverSize: Size | null;
|
|
22
30
|
/**
|
|
23
|
-
* The rect of the
|
|
31
|
+
* The rect of the container.
|
|
32
|
+
*/
|
|
33
|
+
containerRect: ScrollableRect | null;
|
|
34
|
+
/**
|
|
35
|
+
* The rect of the element.
|
|
24
36
|
*/
|
|
25
|
-
|
|
37
|
+
triggerRect: Rect | null;
|
|
26
38
|
/**
|
|
27
39
|
* On which side of the element the popover will appear.
|
|
28
40
|
* @default top
|
|
@@ -41,8 +53,9 @@ interface UsePopoverPositionProps {
|
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
interface PopoverPositionGetterOptions {
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
popoverSize: Size;
|
|
57
|
+
containerRect: ScrollableRect;
|
|
58
|
+
triggerRect: Rect;
|
|
46
59
|
gap: number;
|
|
47
60
|
flip: boolean;
|
|
48
61
|
}
|
|
@@ -53,46 +66,45 @@ type PopoverPositionGetterFn = (
|
|
|
53
66
|
type PopoverPositionGetters = Record<PositionKeys, PopoverPositionGetterFn>;
|
|
54
67
|
type FitToWindow = Pick<
|
|
55
68
|
PopoverPositionGetterOptions,
|
|
56
|
-
'
|
|
69
|
+
'popoverSize' | 'containerRect' | 'triggerRect'
|
|
57
70
|
>;
|
|
58
71
|
|
|
59
72
|
const popoverPositionGetters = (
|
|
60
73
|
rectKey: 'top' | 'left'
|
|
61
74
|
): PopoverPositionGetters => {
|
|
62
75
|
const sizeKey = rectKey === 'top' ? 'height' : 'width';
|
|
63
|
-
const
|
|
64
|
-
const windowOffsetKey = rectKey === 'top' ? 'pageYOffset' : 'pageXOffset';
|
|
76
|
+
const scrollOffsetKey = rectKey === 'top' ? 'scrollTop' : 'scrollLeft';
|
|
65
77
|
|
|
66
|
-
const
|
|
78
|
+
const fitToContainer = (
|
|
67
79
|
start: number,
|
|
68
|
-
{
|
|
80
|
+
{ popoverSize, containerRect, triggerRect }: FitToWindow
|
|
69
81
|
): number => {
|
|
70
82
|
let popoverStart = start;
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const popoverEnd = popoverStart +
|
|
76
|
-
|
|
77
|
-
// Fit the popover to the end of the
|
|
78
|
-
if (popoverEnd >
|
|
79
|
-
if (
|
|
80
|
-
popoverStart =
|
|
81
|
-
else if (
|
|
82
|
-
popoverStart =
|
|
83
|
-
else if (
|
|
84
|
-
popoverStart =
|
|
85
|
-
else popoverStart =
|
|
83
|
+
const containerStart = containerRect[scrollOffsetKey];
|
|
84
|
+
const containerEnd = containerStart + containerRect[sizeKey];
|
|
85
|
+
const triggerStart = containerStart + triggerRect[rectKey];
|
|
86
|
+
const triggerEnd = triggerStart + triggerRect[sizeKey];
|
|
87
|
+
const popoverEnd = popoverStart + popoverSize[sizeKey];
|
|
88
|
+
|
|
89
|
+
// Fit the popover to the end of the container
|
|
90
|
+
if (popoverEnd > containerEnd) {
|
|
91
|
+
if (triggerEnd < containerEnd)
|
|
92
|
+
popoverStart = containerEnd - popoverSize[sizeKey];
|
|
93
|
+
else if (popoverSize[sizeKey] > triggerRect[sizeKey])
|
|
94
|
+
popoverStart = triggerEnd - popoverSize[sizeKey];
|
|
95
|
+
else if (containerEnd - triggerStart > popoverSize[sizeKey])
|
|
96
|
+
popoverStart = containerEnd - popoverSize[sizeKey];
|
|
97
|
+
else popoverStart = triggerStart;
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
// Fit the popover to the beginning of the
|
|
89
|
-
if (popoverStart <
|
|
90
|
-
if (
|
|
91
|
-
else if (
|
|
92
|
-
popoverStart =
|
|
93
|
-
else if (
|
|
94
|
-
popoverStart =
|
|
95
|
-
else popoverStart =
|
|
100
|
+
// Fit the popover to the beginning of the container
|
|
101
|
+
if (popoverStart < containerStart) {
|
|
102
|
+
if (triggerStart > containerStart) popoverStart = containerStart;
|
|
103
|
+
else if (popoverSize[sizeKey] > triggerRect[sizeKey])
|
|
104
|
+
popoverStart = triggerStart;
|
|
105
|
+
else if (triggerEnd - containerStart > popoverSize[sizeKey])
|
|
106
|
+
popoverStart = containerStart;
|
|
107
|
+
else popoverStart = triggerEnd - popoverSize[sizeKey];
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
return popoverStart;
|
|
@@ -100,56 +112,68 @@ const popoverPositionGetters = (
|
|
|
100
112
|
|
|
101
113
|
return {
|
|
102
114
|
before(options) {
|
|
103
|
-
const {
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const popoverStart =
|
|
108
|
-
if (flip && popoverStart <
|
|
115
|
+
const { popoverSize, containerRect, triggerRect, gap, flip } = options;
|
|
116
|
+
const containerStart = containerRect[scrollOffsetKey];
|
|
117
|
+
const containerEnd = containerStart + containerRect[sizeKey];
|
|
118
|
+
const triggerStart = containerStart + triggerRect[rectKey];
|
|
119
|
+
const popoverStart = triggerStart - popoverSize[sizeKey] - gap;
|
|
120
|
+
if (flip && popoverStart < containerStart) {
|
|
109
121
|
const afterPopoverStart = this.after({ ...options, flip: false });
|
|
110
|
-
const afterPopoverEnd = afterPopoverStart +
|
|
111
|
-
const diffStart =
|
|
112
|
-
const diffEnd = afterPopoverEnd -
|
|
113
|
-
if (afterPopoverEnd <=
|
|
122
|
+
const afterPopoverEnd = afterPopoverStart + popoverSize[sizeKey];
|
|
123
|
+
const diffStart = containerStart - popoverStart;
|
|
124
|
+
const diffEnd = afterPopoverEnd - containerEnd;
|
|
125
|
+
if (afterPopoverEnd <= containerEnd || diffStart > diffEnd)
|
|
114
126
|
return afterPopoverStart;
|
|
115
127
|
}
|
|
116
128
|
return popoverStart;
|
|
117
129
|
},
|
|
118
130
|
after(options) {
|
|
119
|
-
const {
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
const popoverStart =
|
|
125
|
-
const popoverEnd = popoverStart +
|
|
126
|
-
if (flip && popoverEnd >
|
|
131
|
+
const { popoverSize, containerRect, triggerRect, gap, flip } = options;
|
|
132
|
+
const containerStart = containerRect[scrollOffsetKey];
|
|
133
|
+
const containerEnd = containerStart + containerRect[sizeKey];
|
|
134
|
+
const triggerStart = containerStart + triggerRect[rectKey];
|
|
135
|
+
const triggerEnd = triggerStart + triggerRect[sizeKey];
|
|
136
|
+
const popoverStart = triggerEnd + gap;
|
|
137
|
+
const popoverEnd = popoverStart + popoverSize[sizeKey];
|
|
138
|
+
if (flip && popoverEnd > containerEnd) {
|
|
127
139
|
const beforePopoverStart = this.before({ ...options, flip: false });
|
|
128
|
-
const diffStart =
|
|
129
|
-
const diffEnd = popoverEnd -
|
|
130
|
-
if (beforePopoverStart >=
|
|
140
|
+
const diffStart = containerStart - beforePopoverStart;
|
|
141
|
+
const diffEnd = popoverEnd - containerEnd;
|
|
142
|
+
if (beforePopoverStart >= containerStart || diffEnd > diffStart)
|
|
131
143
|
return beforePopoverStart;
|
|
132
144
|
}
|
|
133
145
|
return popoverStart;
|
|
134
146
|
},
|
|
135
|
-
start: ({
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
return
|
|
147
|
+
start: ({ popoverSize, containerRect, triggerRect }) => {
|
|
148
|
+
const containerStart = containerRect[scrollOffsetKey];
|
|
149
|
+
const triggerStart = containerStart + triggerRect[rectKey];
|
|
150
|
+
return fitToContainer(triggerStart, {
|
|
151
|
+
popoverSize,
|
|
152
|
+
containerRect,
|
|
153
|
+
triggerRect,
|
|
154
|
+
});
|
|
139
155
|
},
|
|
140
|
-
end: ({
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const popoverStart =
|
|
145
|
-
return
|
|
156
|
+
end: ({ popoverSize, containerRect, triggerRect }) => {
|
|
157
|
+
const containerStart = containerRect[scrollOffsetKey];
|
|
158
|
+
const triggerStart = containerStart + triggerRect[rectKey];
|
|
159
|
+
const triggerEnd = triggerStart + triggerRect[sizeKey];
|
|
160
|
+
const popoverStart = triggerEnd - popoverSize[sizeKey];
|
|
161
|
+
return fitToContainer(popoverStart, {
|
|
162
|
+
popoverSize,
|
|
163
|
+
containerRect,
|
|
164
|
+
triggerRect,
|
|
165
|
+
});
|
|
146
166
|
},
|
|
147
|
-
center: ({
|
|
148
|
-
const
|
|
149
|
-
const
|
|
167
|
+
center: ({ popoverSize, containerRect, triggerRect }) => {
|
|
168
|
+
const containerStart = containerRect[scrollOffsetKey];
|
|
169
|
+
const triggerStart = containerStart + triggerRect[rectKey];
|
|
150
170
|
const popoverStart =
|
|
151
|
-
|
|
152
|
-
return
|
|
171
|
+
triggerStart + (triggerRect[sizeKey] - popoverSize[sizeKey]) / 2;
|
|
172
|
+
return fitToContainer(popoverStart, {
|
|
173
|
+
popoverSize,
|
|
174
|
+
containerRect,
|
|
175
|
+
triggerRect,
|
|
176
|
+
});
|
|
153
177
|
},
|
|
154
178
|
};
|
|
155
179
|
};
|
|
@@ -171,12 +195,16 @@ const placementPositionKeysMap: PlacementPositionKeysMap = {
|
|
|
171
195
|
right: ['center', 'after'],
|
|
172
196
|
'top-start': ['before', 'start'],
|
|
173
197
|
'top-end': ['before', 'end'],
|
|
198
|
+
'top-full': ['before', 'start'],
|
|
174
199
|
'bottom-start': ['after', 'start'],
|
|
175
200
|
'bottom-end': ['after', 'end'],
|
|
201
|
+
'bottom-full': ['after', 'start'],
|
|
176
202
|
'left-start': ['start', 'before'],
|
|
177
203
|
'left-end': ['end', 'before'],
|
|
204
|
+
'left-full': ['start', 'before'],
|
|
178
205
|
'right-start': ['start', 'after'],
|
|
179
206
|
'right-end': ['end', 'after'],
|
|
207
|
+
'right-full': ['start', 'after'],
|
|
180
208
|
};
|
|
181
209
|
|
|
182
210
|
interface PopoverPosition {
|
|
@@ -190,8 +218,9 @@ interface PopoverPosition {
|
|
|
190
218
|
* In most cases, it will be the window.
|
|
191
219
|
*/
|
|
192
220
|
const usePopoverPosition = ({
|
|
193
|
-
|
|
194
|
-
|
|
221
|
+
popoverSize,
|
|
222
|
+
containerRect,
|
|
223
|
+
triggerRect,
|
|
195
224
|
placement = 'top',
|
|
196
225
|
gap = 0.5,
|
|
197
226
|
flip = true,
|
|
@@ -199,15 +228,25 @@ const usePopoverPosition = ({
|
|
|
199
228
|
const bodyFontSize = useFontSize(document.body);
|
|
200
229
|
const gapPx = useMemo(() => gap * bodyFontSize, [gap, bodyFontSize]);
|
|
201
230
|
|
|
202
|
-
const positionKeys = useMemo(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
231
|
+
const positionKeys = useMemo(
|
|
232
|
+
() =>
|
|
233
|
+
typeof placement === 'string' && !!placementPositionKeysMap[placement]
|
|
234
|
+
? placementPositionKeysMap[placement]
|
|
235
|
+
: placementPositionKeysMap.top,
|
|
236
|
+
[placement]
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (!popoverSize || !containerRect || !triggerRect) {
|
|
240
|
+
return {
|
|
241
|
+
top: -10000,
|
|
242
|
+
left: -10000,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
207
245
|
|
|
208
246
|
return getPopoverPosition(...positionKeys, {
|
|
209
|
-
|
|
210
|
-
|
|
247
|
+
popoverSize,
|
|
248
|
+
containerRect,
|
|
249
|
+
triggerRect,
|
|
211
250
|
gap: gapPx,
|
|
212
251
|
flip,
|
|
213
252
|
});
|