reshaped 3.9.0 → 3.9.1-canary.2
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/bundle.css +1 -1
- package/dist/bundle.js +2 -2
- package/dist/components/Accordion/AccordionControlled.js +0 -1
- package/dist/components/Actionable/Actionable.d.ts +8 -3
- package/dist/components/Actionable/Actionable.js +17 -70
- package/dist/components/Actionable/Actionable.types.d.ts +2 -36
- package/dist/components/Actionable/index.d.ts +2 -1
- package/dist/components/Calendar/Calendar.utils.js +6 -7
- package/dist/components/Card/Card.d.ts +1 -1
- package/dist/components/Carousel/Carousel.js +0 -1
- package/dist/components/Flyout/Flyout.module.css +1 -1
- package/dist/components/Flyout/Flyout.types.d.ts +7 -7
- package/dist/components/Flyout/FlyoutContent.js +3 -49
- package/dist/components/Flyout/FlyoutControlled.js +84 -83
- package/dist/components/Flyout/FlyoutTrigger.js +3 -3
- package/dist/components/Flyout/useFlyout.d.ts +3 -4
- package/dist/components/Flyout/useFlyout.js +70 -88
- package/dist/components/Flyout/utilities/safeArea.d.ts +10 -0
- package/dist/components/Flyout/utilities/safeArea.js +100 -0
- package/dist/components/Select/Select.js +1 -1
- package/dist/components/Select/SelectCustomControlled.js +0 -1
- package/dist/components/Tabs/TabsControlled.js +0 -1
- package/dist/components/Toast/ToastContainer.js +0 -1
- package/dist/components/_private/Expandable/Expandable.js +1 -3
- package/dist/components/_private/Portal/Portal.js +0 -3
- package/dist/core/Actionable/Actionable.d.ts +4 -0
- package/dist/core/Actionable/Actionable.js +73 -0
- package/dist/core/Actionable/Actionable.types.d.ts +34 -0
- package/dist/core/Actionable/Actionable.types.js +1 -0
- package/dist/core/Actionable/index.d.ts +2 -0
- package/dist/core/Actionable/index.js +1 -0
- package/dist/hooks/_private/usePrevious.js +0 -1
- package/dist/hooks/useOnClickOutside.js +8 -0
- package/dist/utilities/a11y/TrapFocus.js +9 -3
- package/dist/utilities/dom/index.d.ts +0 -1
- package/dist/utilities/dom/index.js +0 -1
- package/package.json +4 -2
- package/dist/components/Flyout/utilities/calculatePosition.d.ts +0 -31
- package/dist/components/Flyout/utilities/calculatePosition.js +0 -185
- package/dist/components/Flyout/utilities/constants.d.ts +0 -1
- package/dist/components/Flyout/utilities/constants.js +0 -1
- package/dist/components/Flyout/utilities/flyout.d.ts +0 -11
- package/dist/components/Flyout/utilities/flyout.js +0 -115
- package/dist/components/Flyout/utilities/getPositionFallbacks.d.ts +0 -3
- package/dist/components/Flyout/utilities/getPositionFallbacks.js +0 -39
- package/dist/components/Flyout/utilities/helpers.d.ts +0 -7
- package/dist/components/Flyout/utilities/helpers.js +0 -14
- package/dist/components/Flyout/utilities/isFullyVisible.d.ts +0 -13
- package/dist/components/Flyout/utilities/isFullyVisible.js +0 -23
- package/dist/utilities/dom/flyout.d.ts +0 -2
- package/dist/utilities/dom/flyout.js +0 -14
|
@@ -2,30 +2,31 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import useIsDismissible from "../../hooks/_private/useIsDismissible.js";
|
|
5
|
+
import usePrevious from "../../hooks/_private/usePrevious.js";
|
|
5
6
|
import useElementId from "../../hooks/useElementId.js";
|
|
6
7
|
import useHandlerRef from "../../hooks/useHandlerRef.js";
|
|
7
8
|
import useHotkeys from "../../hooks/useHotkeys.js";
|
|
8
9
|
import useIsomorphicLayoutEffect from "../../hooks/useIsomorphicLayoutEffect.js";
|
|
9
10
|
import useOnClickOutside from "../../hooks/useOnClickOutside.js";
|
|
10
|
-
import useRTL from "../../hooks/useRTL.js";
|
|
11
11
|
import { TrapFocus, checkKeyboardMode } from "../../utilities/a11y/index.js";
|
|
12
|
-
import { checkTransitions
|
|
12
|
+
import { checkTransitions } from "../../utilities/animation.js";
|
|
13
13
|
import * as timeouts from "./Flyout.constants.js";
|
|
14
14
|
import { Provider, useFlyoutTriggerContext, useFlyoutContext, useFlyoutContentContext, } from "./Flyout.context.js";
|
|
15
15
|
import useFlyout from "./useFlyout.js";
|
|
16
16
|
import cooldown from "./utilities/cooldown.js";
|
|
17
|
+
import { createSafeArea } from "./utilities/safeArea.js";
|
|
17
18
|
const FlyoutControlled = (props) => {
|
|
18
|
-
const { triggerType = "click", groupTimeouts, onOpen, onClose, children, disabled, forcePosition, fallbackAdjustLayout,
|
|
19
|
+
const { triggerType = "click", groupTimeouts, onOpen, onClose, children, disabled, forcePosition, fallbackAdjustLayout, fallbackMinHeight, trapFocusMode = "dialog", width, disableHideAnimation, disableContentHover, disableCloseOnOutsideClick, autoFocus = true, originCoordinates, contentGap = 2, contentShift, contentMaxHeight, contentMaxWidth, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, initialFocusRef, positionRef, } = props;
|
|
19
20
|
const fallbackPositions = props.fallbackPositions === false || forcePosition ? [] : props.fallbackPositions;
|
|
20
21
|
const onOpenRef = useHandlerRef(onOpen);
|
|
21
22
|
const onCloseRef = useHandlerRef(onClose);
|
|
22
|
-
const
|
|
23
|
+
const active = disabled === true ? false : passedActive;
|
|
24
|
+
const prevActive = usePrevious(active);
|
|
23
25
|
const parentFlyoutContext = useFlyoutContext();
|
|
24
26
|
const { elRef: parentTriggerRef } = useFlyoutTriggerContext() || {};
|
|
25
27
|
const { elRef: parentContentRef } = useFlyoutContentContext() || {};
|
|
26
28
|
const isSubmenu = parentFlyoutContext.trapFocusMode === "action-menu" ||
|
|
27
29
|
parentFlyoutContext.trapFocusMode === "content-menu";
|
|
28
|
-
const [isRTL] = useRTL();
|
|
29
30
|
const internalTriggerElRef = React.useRef(null);
|
|
30
31
|
/**
|
|
31
32
|
* Reuse the parent trigger ref in case we render nested triggers
|
|
@@ -34,20 +35,14 @@ const FlyoutControlled = (props) => {
|
|
|
34
35
|
* Resolving the same inside another Flyout.Content should reset the inheritance
|
|
35
36
|
* For example, if you have a tooltip -> popover inside another popover.content, tooltip shouldn't use its parent context anymore
|
|
36
37
|
*/
|
|
37
|
-
const isParentTriggerInsideFlyout =
|
|
38
|
-
// eslint-disable-next-line react-hooks/refs
|
|
39
|
-
!!parentTriggerRef?.current && parentContentRef?.current?.contains(parentTriggerRef.current);
|
|
38
|
+
const isParentTriggerInsideFlyout = !!parentTriggerRef?.current && parentContentRef?.current?.contains(parentTriggerRef.current);
|
|
40
39
|
const tryParentTrigger = !parentContentRef || isParentTriggerInsideFlyout;
|
|
41
40
|
const triggerElRef = (tryParentTrigger && parentTriggerRef) || internalTriggerElRef;
|
|
42
|
-
const triggerBoundsRef = React.useRef(null);
|
|
43
41
|
const flyoutElRef = React.useRef(null);
|
|
44
42
|
const id = useElementId(passedId);
|
|
45
43
|
const timerRef = React.useRef(null);
|
|
46
44
|
const trapFocusRef = React.useRef(null);
|
|
47
45
|
const lockedRef = React.useRef(false);
|
|
48
|
-
// Check if transition had enough time to start when opening a flyout
|
|
49
|
-
// In some cases there is not enough time to start, like when you're holding tab key
|
|
50
|
-
const transitionStartedRef = React.useRef(false);
|
|
51
46
|
// Lock blur event while pressing anywhere inside the flyout content
|
|
52
47
|
const lockedBlurEffects = React.useRef(false);
|
|
53
48
|
// Focus shouldn't return back to the trigger when user intentionally clicks outside the flyout
|
|
@@ -55,24 +50,24 @@ const FlyoutControlled = (props) => {
|
|
|
55
50
|
// Touch devices trigger onMouseEnter but we don't need to apply regular hover timeouts
|
|
56
51
|
// So we're saving a flag on touch start and then change the mouse enter behavior
|
|
57
52
|
const hoverTriggeredWithTouchEventRef = React.useRef(false);
|
|
53
|
+
// Cleanup function for safe area tracking
|
|
54
|
+
const safeAreaRef = React.useRef(null);
|
|
58
55
|
const originCoordinatesRef = React.useRef(originCoordinates ?? null);
|
|
59
|
-
// eslint-disable-next-line react-hooks/refs
|
|
60
56
|
originCoordinatesRef.current = originCoordinates ?? null;
|
|
61
57
|
const flyout = useFlyout({
|
|
62
58
|
triggerElRef: positionRef ?? triggerElRef,
|
|
63
59
|
flyoutElRef,
|
|
64
|
-
|
|
60
|
+
triggerCoordinatesRef: originCoordinatesRef,
|
|
65
61
|
width,
|
|
66
62
|
position: passedPosition,
|
|
67
|
-
defaultActive:
|
|
68
|
-
// eslint-disable-next-line react-hooks/refs
|
|
63
|
+
defaultActive: active,
|
|
69
64
|
container: containerRef?.current,
|
|
70
65
|
fallbackPositions,
|
|
71
66
|
fallbackAdjustLayout,
|
|
72
|
-
fallbackMinWidth,
|
|
73
67
|
fallbackMinHeight,
|
|
74
68
|
contentGap,
|
|
75
69
|
contentShift,
|
|
70
|
+
onClose: onCloseRef.current,
|
|
76
71
|
});
|
|
77
72
|
const { status, updatePosition, render, hide, remove, show } = flyout;
|
|
78
73
|
const isRendered = status !== "idle";
|
|
@@ -86,6 +81,25 @@ const FlyoutControlled = (props) => {
|
|
|
86
81
|
if (timerRef.current)
|
|
87
82
|
clearTimeout(timerRef.current);
|
|
88
83
|
}, []);
|
|
84
|
+
/**
|
|
85
|
+
* Disable all triggers while mouse is moving over the safe area
|
|
86
|
+
*/
|
|
87
|
+
const disableTriggers = React.useCallback(() => {
|
|
88
|
+
if (triggerType !== "hover")
|
|
89
|
+
return;
|
|
90
|
+
document.querySelectorAll("[data-rs-flyout-active]").forEach((el) => {
|
|
91
|
+
if (el === triggerElRef.current)
|
|
92
|
+
return;
|
|
93
|
+
el.style.pointerEvents = "none";
|
|
94
|
+
});
|
|
95
|
+
}, [triggerElRef, triggerType]);
|
|
96
|
+
const enableTriggers = React.useCallback(() => {
|
|
97
|
+
if (triggerType !== "hover")
|
|
98
|
+
return;
|
|
99
|
+
document.querySelectorAll("[data-rs-flyout-active]").forEach((el) => {
|
|
100
|
+
el.style.removeProperty("pointer-events");
|
|
101
|
+
});
|
|
102
|
+
}, [triggerType]);
|
|
89
103
|
/**
|
|
90
104
|
* Component open/close handlers
|
|
91
105
|
* Called from the internal actions
|
|
@@ -96,17 +110,27 @@ const FlyoutControlled = (props) => {
|
|
|
96
110
|
if (isRendered && triggerType !== "hover")
|
|
97
111
|
return;
|
|
98
112
|
onOpenRef.current?.();
|
|
99
|
-
|
|
113
|
+
disableTriggers();
|
|
114
|
+
}, [onOpenRef, isRendered, triggerType, disableTriggers]);
|
|
100
115
|
const handleClose = React.useCallback((options) => {
|
|
101
116
|
const isLocked = triggerType === "click" && !isDismissible();
|
|
102
117
|
const canClose = !isLocked && (isRendered || disabled);
|
|
103
118
|
if (!canClose)
|
|
104
119
|
return;
|
|
105
120
|
onCloseRef.current?.({ reason: options.reason });
|
|
121
|
+
enableTriggers();
|
|
106
122
|
if (options?.closeParents) {
|
|
107
123
|
parentFlyoutContext?.handleClose?.({ closeParents: true, reason: options.reason });
|
|
108
124
|
}
|
|
109
|
-
}, [
|
|
125
|
+
}, [
|
|
126
|
+
isRendered,
|
|
127
|
+
isDismissible,
|
|
128
|
+
triggerType,
|
|
129
|
+
onCloseRef,
|
|
130
|
+
disabled,
|
|
131
|
+
parentFlyoutContext,
|
|
132
|
+
enableTriggers,
|
|
133
|
+
]);
|
|
110
134
|
/**
|
|
111
135
|
* Trigger event handlers
|
|
112
136
|
*/
|
|
@@ -133,7 +157,7 @@ const FlyoutControlled = (props) => {
|
|
|
133
157
|
return;
|
|
134
158
|
hoverTriggeredWithTouchEventRef.current = true;
|
|
135
159
|
}, [triggerType]);
|
|
136
|
-
const
|
|
160
|
+
const handleContentMouseEnter = React.useCallback(() => {
|
|
137
161
|
clearTimer();
|
|
138
162
|
if (hoverTriggeredWithTouchEventRef.current) {
|
|
139
163
|
handleOpen();
|
|
@@ -144,13 +168,15 @@ const FlyoutControlled = (props) => {
|
|
|
144
168
|
cooldown.warm();
|
|
145
169
|
timerRef.current = setTimeout(() => {
|
|
146
170
|
handleOpen();
|
|
147
|
-
}, groupTimeouts && cooldown.status === "warming"
|
|
148
|
-
? timeouts.mouseEnter
|
|
149
|
-
: isSubmenu
|
|
150
|
-
? timeouts.mouseEnter
|
|
151
|
-
: 0);
|
|
171
|
+
}, groupTimeouts && cooldown.status === "warming" ? timeouts.mouseEnter : 0);
|
|
152
172
|
}
|
|
153
|
-
}, [clearTimer, handleOpen, groupTimeouts
|
|
173
|
+
}, [clearTimer, handleOpen, groupTimeouts]);
|
|
174
|
+
const handleTriggerMouseEnter = React.useCallback((e) => {
|
|
175
|
+
if (e.currentTarget === triggerElRef.current) {
|
|
176
|
+
safeAreaRef.current?.cleanup();
|
|
177
|
+
}
|
|
178
|
+
handleContentMouseEnter();
|
|
179
|
+
}, [triggerElRef, handleContentMouseEnter]);
|
|
154
180
|
const handleMouseLeave = React.useCallback((e) => {
|
|
155
181
|
if (e.relatedTarget === flyoutElRef.current ||
|
|
156
182
|
(e.relatedTarget instanceof Node && flyoutElRef.current?.contains(e.relatedTarget)))
|
|
@@ -160,15 +186,25 @@ const FlyoutControlled = (props) => {
|
|
|
160
186
|
return;
|
|
161
187
|
cooldown.cool();
|
|
162
188
|
clearTimer();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
189
|
+
safeAreaRef.current?.cleanup();
|
|
190
|
+
if (triggerType === "hover" && isRendered) {
|
|
191
|
+
// Safe area coordinates are defined based on the trigger mouse out, even when returning mouse from content to trigger
|
|
192
|
+
const origin = e.currentTarget === flyoutElRef.current && safeAreaRef.current?.origin
|
|
193
|
+
? safeAreaRef.current.origin
|
|
194
|
+
: { x: e.clientX, y: e.clientY };
|
|
195
|
+
const cleanup = createSafeArea({
|
|
196
|
+
contentRef: flyoutElRef,
|
|
197
|
+
triggerRef: triggerElRef,
|
|
198
|
+
position: flyout.position,
|
|
199
|
+
onClose: () => handleClose({}),
|
|
200
|
+
origin,
|
|
201
|
+
});
|
|
202
|
+
safeAreaRef.current = { origin, cleanup };
|
|
167
203
|
}
|
|
168
204
|
else {
|
|
169
205
|
handleClose({});
|
|
170
206
|
}
|
|
171
|
-
}, [clearTimer, handleClose, triggerElRef, flyoutElRef,
|
|
207
|
+
}, [clearTimer, handleClose, triggerElRef, flyoutElRef, triggerType, isRendered, flyout.position]);
|
|
172
208
|
const handleTriggerClick = React.useCallback(() => {
|
|
173
209
|
if (!isRendered) {
|
|
174
210
|
handleOpen();
|
|
@@ -177,13 +213,6 @@ const FlyoutControlled = (props) => {
|
|
|
177
213
|
handleClose({});
|
|
178
214
|
}
|
|
179
215
|
}, [isRendered, handleOpen, handleClose]);
|
|
180
|
-
const handleTriggerMouseDown = React.useCallback(() => {
|
|
181
|
-
const triggerEl = positionRef?.current ?? triggerElRef.current;
|
|
182
|
-
const rect = triggerEl?.getBoundingClientRect();
|
|
183
|
-
if (!rect)
|
|
184
|
-
return;
|
|
185
|
-
triggerBoundsRef.current = rect;
|
|
186
|
-
}, [triggerElRef, positionRef]);
|
|
187
216
|
const handleContentMouseDown = () => {
|
|
188
217
|
lockedBlurEffects.current = true;
|
|
189
218
|
hoverTriggeredWithTouchEventRef.current = true;
|
|
@@ -191,44 +220,27 @@ const FlyoutControlled = (props) => {
|
|
|
191
220
|
const handleContentMouseUp = () => {
|
|
192
221
|
lockedBlurEffects.current = false;
|
|
193
222
|
};
|
|
194
|
-
const handleTransitionStart = React.useCallback((e) => {
|
|
195
|
-
if (!resolvedActive)
|
|
196
|
-
return;
|
|
197
|
-
if (flyoutElRef.current !== e.currentTarget || e.propertyName !== "transform")
|
|
198
|
-
return;
|
|
199
|
-
transitionStartedRef.current = true;
|
|
200
|
-
/**
|
|
201
|
-
* After animation has started, we're sure about the correct bounds
|
|
202
|
-
* so drop the cache to make flyout work when trigger moves around
|
|
203
|
-
*/
|
|
204
|
-
triggerBoundsRef.current = null;
|
|
205
|
-
}, [resolvedActive]);
|
|
206
223
|
const handleTransitionEnd = React.useCallback((e) => {
|
|
207
224
|
if (flyoutElRef.current !== e.currentTarget || e.propertyName !== "transform")
|
|
208
225
|
return;
|
|
209
|
-
if (status === "hidden")
|
|
210
|
-
transitionStartedRef.current = false;
|
|
226
|
+
if (status === "hidden")
|
|
211
227
|
remove();
|
|
212
|
-
}
|
|
213
228
|
}, [remove, status]);
|
|
214
229
|
/**
|
|
215
230
|
* Control the display based on the props
|
|
216
231
|
*/
|
|
217
232
|
useIsomorphicLayoutEffect(() => {
|
|
218
|
-
if (
|
|
233
|
+
if (active) {
|
|
219
234
|
render();
|
|
220
235
|
return;
|
|
221
236
|
}
|
|
222
237
|
if (disabled)
|
|
223
238
|
cooldown.cool();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
* - warmed up tooltips get removed instantly
|
|
228
|
-
*/
|
|
239
|
+
// Prevent calling hide on component mount
|
|
240
|
+
if (prevActive === active)
|
|
241
|
+
return;
|
|
229
242
|
if (checkTransitions() &&
|
|
230
243
|
!disableHideAnimation &&
|
|
231
|
-
transitionStartedRef.current &&
|
|
232
244
|
(cooldown.status === "cooling" || !groupTimeouts)) {
|
|
233
245
|
hide();
|
|
234
246
|
}
|
|
@@ -236,11 +248,10 @@ const FlyoutControlled = (props) => {
|
|
|
236
248
|
// In case transitions are disabled globally - remove from the DOM immediately
|
|
237
249
|
remove();
|
|
238
250
|
}
|
|
239
|
-
}, [
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
onNextFrame(() => show());
|
|
251
|
+
}, [active, prevActive, render, hide, remove, disableHideAnimation, disabled, groupTimeouts]);
|
|
252
|
+
useIsomorphicLayoutEffect(() => {
|
|
253
|
+
if (status === "rendered")
|
|
254
|
+
show();
|
|
244
255
|
}, [status, show]);
|
|
245
256
|
/**
|
|
246
257
|
* Handle focus trap
|
|
@@ -278,7 +289,7 @@ const FlyoutControlled = (props) => {
|
|
|
278
289
|
return;
|
|
279
290
|
if (trapFocusRef.current?.trapped) {
|
|
280
291
|
/* Locking the popover to not open it again on trigger focus */
|
|
281
|
-
if (triggerType === "hover") {
|
|
292
|
+
if (triggerType === "hover" && checkKeyboardMode()) {
|
|
282
293
|
lockedRef.current = true;
|
|
283
294
|
setTimeout(() => {
|
|
284
295
|
lockedRef.current = false;
|
|
@@ -295,29 +306,20 @@ const FlyoutControlled = (props) => {
|
|
|
295
306
|
return () => trapFocusRef.current?.release();
|
|
296
307
|
}, []);
|
|
297
308
|
/**
|
|
298
|
-
*
|
|
309
|
+
* Clean up safe polygon tracking on unmount or when flyout closes
|
|
299
310
|
*/
|
|
300
311
|
React.useEffect(() => {
|
|
301
312
|
if (!isRendered)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (triggerElRef.current)
|
|
306
|
-
resizeObserver.observe(triggerElRef.current);
|
|
307
|
-
if (flyoutElRef.current)
|
|
308
|
-
resizeObserver.observe(flyoutElRef.current);
|
|
309
|
-
return () => resizeObserver.disconnect();
|
|
310
|
-
}, [updatePosition, triggerElRef, isRendered, flyoutElRef]);
|
|
311
|
-
React.useEffect(() => {
|
|
312
|
-
updatePosition({ sync: true });
|
|
313
|
-
}, [isRTL, updatePosition]);
|
|
313
|
+
safeAreaRef.current?.cleanup();
|
|
314
|
+
return () => safeAreaRef.current?.cleanup();
|
|
315
|
+
}, [isRendered]);
|
|
314
316
|
/**
|
|
315
317
|
* Imperative methods for controlling Flyout
|
|
316
318
|
*/
|
|
317
319
|
React.useImperativeHandle(instanceRef, () => ({
|
|
318
320
|
open: handleOpen,
|
|
319
321
|
close: () => handleClose({}),
|
|
320
|
-
updatePosition: () => updatePosition(
|
|
322
|
+
updatePosition: () => updatePosition(),
|
|
321
323
|
}), [handleOpen, handleClose, updatePosition]);
|
|
322
324
|
useHotkeys({ Escape: () => handleClose({ reason: "escape-key" }) }, [handleClose]);
|
|
323
325
|
useOnClickOutside([flyoutElRef, triggerElRef], () => {
|
|
@@ -337,12 +339,11 @@ const FlyoutControlled = (props) => {
|
|
|
337
339
|
handleOpen,
|
|
338
340
|
handleFocus,
|
|
339
341
|
handleBlur,
|
|
340
|
-
|
|
342
|
+
handleTriggerMouseEnter,
|
|
343
|
+
handleContentMouseEnter,
|
|
341
344
|
handleMouseLeave,
|
|
342
345
|
handleTouchStart,
|
|
343
|
-
handleTransitionStart,
|
|
344
346
|
handleTransitionEnd,
|
|
345
|
-
handleMouseDown: handleTriggerMouseDown,
|
|
346
347
|
handleClick: handleTriggerClick,
|
|
347
348
|
handleContentMouseDown,
|
|
348
349
|
handleContentMouseUp,
|
|
@@ -3,17 +3,17 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useFlyoutContext, TriggerProvider } from "./Flyout.context.js";
|
|
4
4
|
const FlyoutTrigger = (props) => {
|
|
5
5
|
const { children } = props;
|
|
6
|
-
const { id, triggerElRef, triggerType, flyout, handleFocus, handleBlur,
|
|
6
|
+
const { id, triggerElRef, triggerType, flyout, handleFocus, handleBlur, handleTriggerMouseEnter, handleMouseLeave, handleTouchStart, handleClick, trapFocusMode, isSubmenu, } = useFlyoutContext();
|
|
7
7
|
const active = flyout.status !== "idle";
|
|
8
8
|
const childrenAttributes = {
|
|
9
9
|
ref: triggerElRef,
|
|
10
|
+
"data-rs-flyout-active": active,
|
|
10
11
|
};
|
|
11
12
|
if (triggerType === "click" || trapFocusMode === "action-menu") {
|
|
12
13
|
childrenAttributes.onClick = handleClick;
|
|
13
|
-
childrenAttributes.onMouseDown = handleMouseDown;
|
|
14
14
|
}
|
|
15
15
|
if (triggerType === "hover") {
|
|
16
|
-
childrenAttributes.onMouseEnter =
|
|
16
|
+
childrenAttributes.onMouseEnter = handleTriggerMouseEnter;
|
|
17
17
|
childrenAttributes.onMouseLeave = handleMouseLeave;
|
|
18
18
|
childrenAttributes.onTouchStart = handleTouchStart;
|
|
19
19
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import type * as T from "./Flyout.types";
|
|
3
2
|
import type * as G from "../../types/global";
|
|
4
|
-
type UseFlyout = (args: Pick<T.Props, "width" | "position" | "defaultActive" | "fallbackAdjustLayout" | "
|
|
3
|
+
type UseFlyout = (args: Pick<T.Props, "width" | "position" | "defaultActive" | "fallbackAdjustLayout" | "fallbackMinHeight" | "contentGap" | "contentShift" | "onClose"> & {
|
|
5
4
|
fallbackPositions?: T.Position[];
|
|
6
5
|
container?: HTMLElement | null;
|
|
7
6
|
triggerElRef: React.RefObject<HTMLElement | null>;
|
|
8
7
|
flyoutElRef: React.RefObject<HTMLElement | null>;
|
|
9
|
-
|
|
8
|
+
triggerCoordinatesRef: React.RefObject<DOMRect | G.Coordinates | null>;
|
|
10
9
|
}) => Pick<T.State, "position" | "status"> & {
|
|
11
10
|
updatePosition: (options?: {
|
|
12
|
-
|
|
11
|
+
fallback?: boolean;
|
|
13
12
|
}) => void;
|
|
14
13
|
render: () => void;
|
|
15
14
|
hide: () => void;
|
|
@@ -1,113 +1,95 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
const flyoutReducer = (state, action) => {
|
|
5
|
-
switch (action.type) {
|
|
6
|
-
case "render":
|
|
7
|
-
// Disable events before it's positioned to avoid mouseleave getting triggered
|
|
8
|
-
return { ...state, status: "rendered" };
|
|
9
|
-
case "position":
|
|
10
|
-
return {
|
|
11
|
-
...state,
|
|
12
|
-
status: action.payload.sync ? state.status : "positioned",
|
|
13
|
-
position: action.payload.position,
|
|
14
|
-
};
|
|
15
|
-
case "show":
|
|
16
|
-
// Checking because we're positioning inside nextAnimationFrame
|
|
17
|
-
if (state.status !== "positioned")
|
|
18
|
-
return state;
|
|
19
|
-
return { ...state, status: "visible" };
|
|
20
|
-
case "hide":
|
|
21
|
-
return { ...state, status: "hidden" };
|
|
22
|
-
case "remove":
|
|
23
|
-
return { ...state, status: "idle" };
|
|
24
|
-
default:
|
|
25
|
-
throw new Error("[Reshaped] Invalid flyout reducer type");
|
|
26
|
-
}
|
|
27
|
-
};
|
|
1
|
+
import { Flyout } from "@reshaped/utilities";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
+
import useIsomorphicLayoutEffect from "../../hooks/useIsomorphicLayoutEffect.js";
|
|
28
4
|
const useFlyout = (args) => {
|
|
29
|
-
const { triggerElRef, flyoutElRef,
|
|
30
|
-
const { position: defaultPosition = "bottom", fallbackPositions, fallbackAdjustLayout,
|
|
31
|
-
const
|
|
5
|
+
const { triggerElRef, flyoutElRef, triggerCoordinatesRef, contentGap, contentShift, onClose, ...options } = args;
|
|
6
|
+
const { position: defaultPosition = "bottom", fallbackPositions, fallbackAdjustLayout, fallbackMinHeight, width, container, } = options;
|
|
7
|
+
const [status, setStatus] = useState("idle");
|
|
8
|
+
const [position, setPosition] = useState(defaultPosition);
|
|
9
|
+
const flyoutRef = useRef(null);
|
|
32
10
|
// Memo the array internally to avoid new arrays triggering useCallback
|
|
33
|
-
const cachedFallbackPositions =
|
|
11
|
+
const cachedFallbackPositions = useMemo(() => fallbackPositions,
|
|
34
12
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
13
|
[fallbackPositions?.join(" ")]);
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
position: defaultPosition,
|
|
39
|
-
status: "idle",
|
|
40
|
-
});
|
|
41
|
-
const render = React.useCallback(() => {
|
|
42
|
-
dispatch({ type: "render" });
|
|
43
|
-
}, []);
|
|
44
|
-
const show = React.useCallback(() => {
|
|
45
|
-
dispatch({ type: "show" });
|
|
46
|
-
}, []);
|
|
47
|
-
const hide = React.useCallback(() => {
|
|
48
|
-
dispatch({ type: "hide" });
|
|
14
|
+
const render = useCallback(() => {
|
|
15
|
+
setStatus("rendered");
|
|
49
16
|
}, []);
|
|
50
|
-
const
|
|
51
|
-
|
|
17
|
+
const hide = useCallback(() => {
|
|
18
|
+
setStatus("hidden");
|
|
52
19
|
}, []);
|
|
53
|
-
const
|
|
54
|
-
lastUsedPositionRef.current = position;
|
|
55
|
-
}, []);
|
|
56
|
-
const updatePosition = React.useCallback((options) => {
|
|
20
|
+
const getFlyoutOptions = useCallback(() => {
|
|
57
21
|
if (!flyoutElRef.current)
|
|
58
22
|
return;
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
23
|
+
const baseUnit = getComputedStyle(flyoutElRef.current).getPropertyValue("--rs-unit-x1");
|
|
24
|
+
const unitModifier = baseUnit ? parseInt(baseUnit) : 4;
|
|
25
|
+
const handleClose = () => {
|
|
26
|
+
onClose?.({});
|
|
27
|
+
hide();
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
trigger: triggerElRef.current,
|
|
31
|
+
content: flyoutElRef.current,
|
|
32
|
+
container,
|
|
33
|
+
triggerCoordinates: triggerCoordinatesRef.current,
|
|
64
34
|
width,
|
|
65
|
-
position:
|
|
66
|
-
fallbackPositions:
|
|
35
|
+
position: defaultPosition,
|
|
36
|
+
fallbackPositions: cachedFallbackPositions,
|
|
67
37
|
fallbackAdjustLayout,
|
|
68
|
-
fallbackMinWidth,
|
|
69
38
|
fallbackMinHeight,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
contentGap,
|
|
75
|
-
contentShift,
|
|
76
|
-
});
|
|
77
|
-
if (nextFlyoutData) {
|
|
78
|
-
dispatch({
|
|
79
|
-
type: "position",
|
|
80
|
-
payload: { ...nextFlyoutData, sync: options?.sync },
|
|
81
|
-
});
|
|
82
|
-
}
|
|
39
|
+
contentGap: (contentGap ?? 0) * unitModifier,
|
|
40
|
+
contentShift: (contentShift ?? 0) * unitModifier,
|
|
41
|
+
onClose: handleClose,
|
|
42
|
+
};
|
|
83
43
|
}, [
|
|
44
|
+
cachedFallbackPositions,
|
|
84
45
|
container,
|
|
46
|
+
contentGap,
|
|
47
|
+
contentShift,
|
|
85
48
|
defaultPosition,
|
|
86
|
-
cachedFallbackPositions,
|
|
87
49
|
fallbackAdjustLayout,
|
|
88
|
-
|
|
89
|
-
|
|
50
|
+
fallbackMinHeight,
|
|
51
|
+
triggerCoordinatesRef,
|
|
90
52
|
triggerElRef,
|
|
91
|
-
triggerBoundsRef,
|
|
92
53
|
width,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
fallbackMinWidth,
|
|
97
|
-
fallbackMinHeight,
|
|
54
|
+
hide,
|
|
55
|
+
onClose,
|
|
56
|
+
flyoutElRef,
|
|
98
57
|
]);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
58
|
+
const show = useCallback(() => {
|
|
59
|
+
const flyoutOptions = getFlyoutOptions();
|
|
60
|
+
if (!flyoutOptions)
|
|
61
|
+
return;
|
|
62
|
+
flyoutRef.current = new Flyout(flyoutOptions);
|
|
63
|
+
const result = flyoutRef.current.open();
|
|
64
|
+
setPosition(result.position);
|
|
65
|
+
setStatus("visible");
|
|
66
|
+
}, [getFlyoutOptions]);
|
|
67
|
+
const remove = useCallback(() => {
|
|
68
|
+
if (!flyoutRef.current)
|
|
69
|
+
return;
|
|
70
|
+
flyoutRef.current.close();
|
|
71
|
+
setStatus("idle");
|
|
72
|
+
}, []);
|
|
73
|
+
const updatePosition = useCallback(() => {
|
|
74
|
+
const flyoutOptions = getFlyoutOptions();
|
|
75
|
+
if (!flyoutRef.current)
|
|
76
|
+
return;
|
|
77
|
+
if (!flyoutOptions)
|
|
78
|
+
return;
|
|
79
|
+
const result = flyoutRef.current.update(flyoutOptions);
|
|
80
|
+
setPosition(result.position);
|
|
81
|
+
}, [getFlyoutOptions]);
|
|
82
|
+
useIsomorphicLayoutEffect(() => {
|
|
83
|
+
updatePosition();
|
|
84
|
+
}, [defaultPosition]);
|
|
85
|
+
return useMemo(() => ({
|
|
86
|
+
position,
|
|
87
|
+
status,
|
|
106
88
|
updatePosition,
|
|
107
89
|
render,
|
|
108
90
|
hide,
|
|
109
91
|
remove,
|
|
110
92
|
show,
|
|
111
|
-
}), [render, updatePosition, hide, remove, show,
|
|
93
|
+
}), [render, updatePosition, hide, remove, show, position, status]);
|
|
112
94
|
};
|
|
113
95
|
export default useFlyout;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type * as G from "../../../types/global";
|
|
2
|
+
type SafePolygonOptions = {
|
|
3
|
+
contentRef: React.RefObject<HTMLElement | null>;
|
|
4
|
+
triggerRef: React.RefObject<HTMLElement | null>;
|
|
5
|
+
position: string | null | undefined;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
origin: G.Coordinates;
|
|
8
|
+
};
|
|
9
|
+
export declare function createSafeArea(options: SafePolygonOptions): () => void;
|
|
10
|
+
export {};
|