reshaped 3.9.0-canary.9 → 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.d.ts +2 -0
- 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.module.css +1 -1
- package/dist/components/Actionable/Actionable.types.d.ts +2 -36
- package/dist/components/Actionable/index.d.ts +2 -1
- package/dist/components/Badge/Badge.js +2 -2
- package/dist/components/Badge/Badge.module.css +1 -1
- package/dist/components/Badge/Badge.types.d.ts +1 -1
- package/dist/components/Button/Button.module.css +1 -1
- package/dist/components/Calendar/Calendar.module.css +1 -1
- package/dist/components/Calendar/Calendar.utils.js +6 -7
- package/dist/components/Card/Card.d.ts +2 -2
- package/dist/components/Card/Card.types.d.ts +5 -5
- package/dist/components/Carousel/Carousel.js +0 -1
- package/dist/components/Flyout/Flyout.constants.d.ts +1 -0
- package/dist/components/Flyout/Flyout.constants.js +1 -0
- package/dist/components/Flyout/Flyout.module.css +1 -1
- package/dist/components/Flyout/Flyout.types.d.ts +10 -8
- package/dist/components/Flyout/FlyoutContent.js +4 -49
- package/dist/components/Flyout/FlyoutControlled.js +94 -76
- 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/Grid/Grid.types.d.ts +4 -4
- package/dist/components/HiddenInput/HiddenInput.js +2 -3
- package/dist/components/Image/Image.js +1 -1
- package/dist/components/Modal/Modal.js +0 -3
- package/dist/components/Popover/Popover.module.css +1 -1
- package/dist/components/Reshaped/Reshaped.css +1 -1
- package/dist/components/ScrollArea/ScrollArea.js +6 -6
- package/dist/components/Select/Select.js +1 -1
- package/dist/components/Select/SelectCustomControlled.js +0 -1
- package/dist/components/Slider/SliderControlled.js +5 -4
- package/dist/components/Tabs/Tabs.module.css +1 -1
- package/dist/components/Tabs/Tabs.types.d.ts +3 -1
- package/dist/components/Tabs/TabsContext.d.ts +1 -0
- package/dist/components/Tabs/TabsControlled.js +2 -2
- package/dist/components/Tabs/TabsItem.js +2 -2
- package/dist/components/Tabs/TabsList.js +9 -5
- package/dist/components/Tabs/TabsPanel.js +1 -1
- package/dist/components/Text/Text.d.ts +1 -1
- package/dist/components/Text/Text.types.d.ts +3 -3
- package/dist/components/Toast/ToastContainer.js +0 -1
- package/dist/components/Tooltip/Tooltip.js +2 -2
- package/dist/components/Tooltip/Tooltip.module.css +1 -1
- package/dist/components/Tooltip/Tooltip.types.d.ts +1 -1
- package/dist/components/View/View.types.d.ts +4 -4
- 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/useDrag.js +0 -3
- package/dist/hooks/_private/usePrevious.js +0 -1
- package/dist/hooks/useOnClickOutside.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types/global.d.ts +1 -1
- package/dist/utilities/a11y/TrapFocus.js +9 -3
- package/dist/utilities/dom/index.d.ts +0 -2
- package/dist/utilities/dom/index.js +0 -2
- package/dist/utilities/scroll/disable.js +4 -2
- package/package.json +7 -99
- package/README.md +0 -24
- package/dist/components/Flyout/utilities/calculatePosition.d.ts +0 -31
- package/dist/components/Flyout/utilities/calculatePosition.js +0 -178
- package/dist/components/Flyout/utilities/flyout.d.ts +0 -11
- package/dist/components/Flyout/utilities/flyout.js +0 -87
- 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 -12
- package/dist/components/Flyout/utilities/isFullyVisible.js +0 -22
- package/dist/utilities/dom/flyout.d.ts +0 -2
- package/dist/utilities/dom/flyout.js +0 -14
- package/dist/utilities/dom/userSelect.d.ts +0 -2
- package/dist/utilities/dom/userSelect.js +0 -6
|
@@ -3,15 +3,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
import Portal from "../_private/Portal/index.js";
|
|
5
5
|
import useIsomorphicLayoutEffect from "../../hooks/useIsomorphicLayoutEffect.js";
|
|
6
|
-
import { findClosestPositionContainer
|
|
7
|
-
import { rafThrottle } from "../../utilities/helpers.js";
|
|
6
|
+
import { findClosestPositionContainer } from "../../utilities/dom/index.js";
|
|
8
7
|
import { classNames } from "../../utilities/props.js";
|
|
9
8
|
import { useFlyoutContext, ContentProvider } from "./Flyout.context.js";
|
|
10
9
|
import s from "./Flyout.module.css";
|
|
11
10
|
import cooldown from "./utilities/cooldown.js";
|
|
12
11
|
const FlyoutContent = (props) => {
|
|
13
12
|
const { children, className, attributes } = props;
|
|
14
|
-
const { flyout, id, flyoutElRef, triggerElRef,
|
|
13
|
+
const { flyout, id, flyoutElRef, triggerElRef, handleTransitionEnd, triggerType, handleContentMouseEnter, handleMouseLeave, handleContentMouseDown, handleContentMouseUp, contentClassName, contentAttributes, contentMaxHeight, contentMaxWidth, trapFocusMode, disableContentHover, autoFocus, width, containerRef: passedContainerRef, isSubmenu, } = useFlyoutContext();
|
|
15
14
|
const { status, position } = flyout;
|
|
16
15
|
const [mounted, setMounted] = React.useState(false);
|
|
17
16
|
const closestFixedContainer = React.useMemo(() => {
|
|
@@ -19,56 +18,12 @@ const FlyoutContent = (props) => {
|
|
|
19
18
|
return null;
|
|
20
19
|
if (!triggerElRef)
|
|
21
20
|
return null;
|
|
22
|
-
// eslint-disable-next-line react-hooks/refs
|
|
23
21
|
return findClosestPositionContainer({ el: triggerElRef.current });
|
|
24
22
|
}, [mounted, triggerElRef]);
|
|
25
|
-
const closestScrollableContainer = React.useMemo(() => {
|
|
26
|
-
if (!mounted)
|
|
27
|
-
return;
|
|
28
|
-
// eslint-disable-next-line react-hooks/refs
|
|
29
|
-
if (!triggerElRef?.current)
|
|
30
|
-
return;
|
|
31
|
-
// eslint-disable-next-line react-hooks/refs
|
|
32
|
-
return findClosestScrollableContainer({ el: triggerElRef.current });
|
|
33
|
-
}, [mounted, triggerElRef]);
|
|
34
23
|
const containerRef = passedContainerRef || { current: closestFixedContainer };
|
|
35
24
|
useIsomorphicLayoutEffect(() => {
|
|
36
25
|
setMounted(true);
|
|
37
26
|
}, []);
|
|
38
|
-
/**
|
|
39
|
-
* transitionStart doesn't exist as a jsx event handler and needs to be handled with vanilla js
|
|
40
|
-
*/
|
|
41
|
-
React.useEffect(() => {
|
|
42
|
-
const el = flyoutElRef.current;
|
|
43
|
-
if (!el)
|
|
44
|
-
return;
|
|
45
|
-
el.addEventListener("transitionstart", handleTransitionStart);
|
|
46
|
-
return () => el.removeEventListener("transitionstart", handleTransitionStart);
|
|
47
|
-
}, [handleTransitionStart, flyoutElRef, status]);
|
|
48
|
-
React.useEffect(() => {
|
|
49
|
-
if (status !== "visible")
|
|
50
|
-
return;
|
|
51
|
-
if (!closestScrollableContainer)
|
|
52
|
-
return;
|
|
53
|
-
const triggerEl = triggerElRef?.current;
|
|
54
|
-
const containerEl = closestScrollableContainer;
|
|
55
|
-
const handleScroll = rafThrottle(() => {
|
|
56
|
-
const triggerBounds = triggerEl?.getBoundingClientRect();
|
|
57
|
-
const containerBounds = containerEl.getBoundingClientRect();
|
|
58
|
-
if (triggerBounds &&
|
|
59
|
-
(triggerBounds.top < containerBounds.top ||
|
|
60
|
-
triggerBounds.left < containerBounds.left ||
|
|
61
|
-
triggerBounds.right > containerBounds.right ||
|
|
62
|
-
triggerBounds.bottom > containerBounds.bottom)) {
|
|
63
|
-
handleClose({});
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
flyout.updatePosition({ sync: true, fallback: false });
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
closestScrollableContainer.addEventListener("scroll", handleScroll, { passive: true });
|
|
70
|
-
return () => closestScrollableContainer.removeEventListener("scroll", handleScroll);
|
|
71
|
-
}, [closestScrollableContainer, flyout, status, handleClose, triggerElRef]);
|
|
72
27
|
if (status === "idle" || !mounted)
|
|
73
28
|
return null;
|
|
74
29
|
const rootClassNames = classNames(s.content, triggerType === "hover" && s["--hover"], status === "visible" && s["--visible"],
|
|
@@ -95,9 +50,9 @@ const FlyoutContent = (props) => {
|
|
|
95
50
|
role = "menubar";
|
|
96
51
|
}
|
|
97
52
|
const content = (_jsx(ContentProvider, { value: { elRef: flyoutElRef }, children: _jsx("div", { className: rootClassNames, style: {
|
|
98
|
-
"--rs-flyout-gap": contentGap,
|
|
99
53
|
"--rs-flyout-max-h": contentMaxHeight,
|
|
100
|
-
|
|
54
|
+
"--rs-flyout-max-w": contentMaxWidth,
|
|
55
|
+
}, ref: flyoutElRef, onTransitionEnd: handleTransitionEnd, onMouseEnter: triggerType === "hover" ? handleContentMouseEnter : undefined, onMouseLeave: triggerType === "hover" ? handleMouseLeave : undefined, onMouseDown: handleContentMouseDown, onTouchStart: handleContentMouseDown, onMouseUp: handleContentMouseUp, onTouchEnd: handleContentMouseUp, children: _jsx("div", { role: role, ...attributes, id: id, tabIndex: !autoFocus ? -1 : undefined, "aria-modal": role === "dialog" ? true : undefined, style: { ...attributes?.style, ...contentAttributes?.style }, className: innerClassNames, children: children }) }) }));
|
|
101
56
|
return _jsx(Portal, { targetRef: containerRef, children: content });
|
|
102
57
|
};
|
|
103
58
|
FlyoutContent.displayName = "Flyout.Content";
|
|
@@ -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,23 +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);
|
|
58
|
-
//
|
|
53
|
+
// Cleanup function for safe area tracking
|
|
54
|
+
const safeAreaRef = React.useRef(null);
|
|
55
|
+
const originCoordinatesRef = React.useRef(originCoordinates ?? null);
|
|
56
|
+
originCoordinatesRef.current = originCoordinates ?? null;
|
|
59
57
|
const flyout = useFlyout({
|
|
60
58
|
triggerElRef: positionRef ?? triggerElRef,
|
|
61
59
|
flyoutElRef,
|
|
62
|
-
|
|
63
|
-
triggerBounds: originCoordinates ?? triggerBoundsRef.current,
|
|
60
|
+
triggerCoordinatesRef: originCoordinatesRef,
|
|
64
61
|
width,
|
|
65
62
|
position: passedPosition,
|
|
66
|
-
defaultActive:
|
|
67
|
-
// eslint-disable-next-line react-hooks/refs
|
|
63
|
+
defaultActive: active,
|
|
68
64
|
container: containerRef?.current,
|
|
69
65
|
fallbackPositions,
|
|
70
66
|
fallbackAdjustLayout,
|
|
71
|
-
fallbackMinWidth,
|
|
72
67
|
fallbackMinHeight,
|
|
73
68
|
contentGap,
|
|
74
69
|
contentShift,
|
|
70
|
+
onClose: onCloseRef.current,
|
|
75
71
|
});
|
|
76
72
|
const { status, updatePosition, render, hide, remove, show } = flyout;
|
|
77
73
|
const isRendered = status !== "idle";
|
|
@@ -85,6 +81,25 @@ const FlyoutControlled = (props) => {
|
|
|
85
81
|
if (timerRef.current)
|
|
86
82
|
clearTimeout(timerRef.current);
|
|
87
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]);
|
|
88
103
|
/**
|
|
89
104
|
* Component open/close handlers
|
|
90
105
|
* Called from the internal actions
|
|
@@ -95,17 +110,27 @@ const FlyoutControlled = (props) => {
|
|
|
95
110
|
if (isRendered && triggerType !== "hover")
|
|
96
111
|
return;
|
|
97
112
|
onOpenRef.current?.();
|
|
98
|
-
|
|
113
|
+
disableTriggers();
|
|
114
|
+
}, [onOpenRef, isRendered, triggerType, disableTriggers]);
|
|
99
115
|
const handleClose = React.useCallback((options) => {
|
|
100
116
|
const isLocked = triggerType === "click" && !isDismissible();
|
|
101
117
|
const canClose = !isLocked && (isRendered || disabled);
|
|
102
118
|
if (!canClose)
|
|
103
119
|
return;
|
|
104
120
|
onCloseRef.current?.({ reason: options.reason });
|
|
121
|
+
enableTriggers();
|
|
105
122
|
if (options?.closeParents) {
|
|
106
|
-
parentFlyoutContext?.handleClose?.({});
|
|
123
|
+
parentFlyoutContext?.handleClose?.({ closeParents: true, reason: options.reason });
|
|
107
124
|
}
|
|
108
|
-
}, [
|
|
125
|
+
}, [
|
|
126
|
+
isRendered,
|
|
127
|
+
isDismissible,
|
|
128
|
+
triggerType,
|
|
129
|
+
onCloseRef,
|
|
130
|
+
disabled,
|
|
131
|
+
parentFlyoutContext,
|
|
132
|
+
enableTriggers,
|
|
133
|
+
]);
|
|
109
134
|
/**
|
|
110
135
|
* Trigger event handlers
|
|
111
136
|
*/
|
|
@@ -132,7 +157,7 @@ const FlyoutControlled = (props) => {
|
|
|
132
157
|
return;
|
|
133
158
|
hoverTriggeredWithTouchEventRef.current = true;
|
|
134
159
|
}, [triggerType]);
|
|
135
|
-
const
|
|
160
|
+
const handleContentMouseEnter = React.useCallback(() => {
|
|
136
161
|
clearTimer();
|
|
137
162
|
if (hoverTriggeredWithTouchEventRef.current) {
|
|
138
163
|
handleOpen();
|
|
@@ -146,15 +171,40 @@ const FlyoutControlled = (props) => {
|
|
|
146
171
|
}, groupTimeouts && cooldown.status === "warming" ? timeouts.mouseEnter : 0);
|
|
147
172
|
}
|
|
148
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]);
|
|
149
180
|
const handleMouseLeave = React.useCallback((e) => {
|
|
150
|
-
if (e.relatedTarget === flyoutElRef.current
|
|
181
|
+
if (e.relatedTarget === flyoutElRef.current ||
|
|
182
|
+
(e.relatedTarget instanceof Node && flyoutElRef.current?.contains(e.relatedTarget)))
|
|
151
183
|
return;
|
|
152
|
-
if (e.relatedTarget === triggerElRef.current
|
|
184
|
+
if (e.relatedTarget === triggerElRef.current ||
|
|
185
|
+
(e.relatedTarget instanceof Node && triggerElRef.current?.contains(e.relatedTarget)))
|
|
153
186
|
return;
|
|
154
187
|
cooldown.cool();
|
|
155
188
|
clearTimer();
|
|
156
|
-
|
|
157
|
-
|
|
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 };
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
handleClose({});
|
|
206
|
+
}
|
|
207
|
+
}, [clearTimer, handleClose, triggerElRef, flyoutElRef, triggerType, isRendered, flyout.position]);
|
|
158
208
|
const handleTriggerClick = React.useCallback(() => {
|
|
159
209
|
if (!isRendered) {
|
|
160
210
|
handleOpen();
|
|
@@ -163,13 +213,6 @@ const FlyoutControlled = (props) => {
|
|
|
163
213
|
handleClose({});
|
|
164
214
|
}
|
|
165
215
|
}, [isRendered, handleOpen, handleClose]);
|
|
166
|
-
const handleTriggerMouseDown = React.useCallback(() => {
|
|
167
|
-
const triggerEl = positionRef?.current ?? triggerElRef.current;
|
|
168
|
-
const rect = triggerEl?.getBoundingClientRect();
|
|
169
|
-
if (!rect)
|
|
170
|
-
return;
|
|
171
|
-
triggerBoundsRef.current = rect;
|
|
172
|
-
}, [triggerElRef, positionRef]);
|
|
173
216
|
const handleContentMouseDown = () => {
|
|
174
217
|
lockedBlurEffects.current = true;
|
|
175
218
|
hoverTriggeredWithTouchEventRef.current = true;
|
|
@@ -177,44 +220,27 @@ const FlyoutControlled = (props) => {
|
|
|
177
220
|
const handleContentMouseUp = () => {
|
|
178
221
|
lockedBlurEffects.current = false;
|
|
179
222
|
};
|
|
180
|
-
const handleTransitionStart = React.useCallback((e) => {
|
|
181
|
-
if (!resolvedActive)
|
|
182
|
-
return;
|
|
183
|
-
if (flyoutElRef.current !== e.currentTarget || e.propertyName !== "transform")
|
|
184
|
-
return;
|
|
185
|
-
transitionStartedRef.current = true;
|
|
186
|
-
/**
|
|
187
|
-
* After animation has started, we're sure about the correct bounds
|
|
188
|
-
* so drop the cache to make flyout work when trigger moves around
|
|
189
|
-
*/
|
|
190
|
-
triggerBoundsRef.current = null;
|
|
191
|
-
}, [resolvedActive]);
|
|
192
223
|
const handleTransitionEnd = React.useCallback((e) => {
|
|
193
224
|
if (flyoutElRef.current !== e.currentTarget || e.propertyName !== "transform")
|
|
194
225
|
return;
|
|
195
|
-
if (status === "hidden")
|
|
196
|
-
transitionStartedRef.current = false;
|
|
226
|
+
if (status === "hidden")
|
|
197
227
|
remove();
|
|
198
|
-
}
|
|
199
228
|
}, [remove, status]);
|
|
200
229
|
/**
|
|
201
230
|
* Control the display based on the props
|
|
202
231
|
*/
|
|
203
232
|
useIsomorphicLayoutEffect(() => {
|
|
204
|
-
if (
|
|
233
|
+
if (active) {
|
|
205
234
|
render();
|
|
206
235
|
return;
|
|
207
236
|
}
|
|
208
237
|
if (disabled)
|
|
209
238
|
cooldown.cool();
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
* - warmed up tooltips get removed instantly
|
|
214
|
-
*/
|
|
239
|
+
// Prevent calling hide on component mount
|
|
240
|
+
if (prevActive === active)
|
|
241
|
+
return;
|
|
215
242
|
if (checkTransitions() &&
|
|
216
243
|
!disableHideAnimation &&
|
|
217
|
-
transitionStartedRef.current &&
|
|
218
244
|
(cooldown.status === "cooling" || !groupTimeouts)) {
|
|
219
245
|
hide();
|
|
220
246
|
}
|
|
@@ -222,11 +248,10 @@ const FlyoutControlled = (props) => {
|
|
|
222
248
|
// In case transitions are disabled globally - remove from the DOM immediately
|
|
223
249
|
remove();
|
|
224
250
|
}
|
|
225
|
-
}, [
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
onNextFrame(() => show());
|
|
251
|
+
}, [active, prevActive, render, hide, remove, disableHideAnimation, disabled, groupTimeouts]);
|
|
252
|
+
useIsomorphicLayoutEffect(() => {
|
|
253
|
+
if (status === "rendered")
|
|
254
|
+
show();
|
|
230
255
|
}, [status, show]);
|
|
231
256
|
/**
|
|
232
257
|
* Handle focus trap
|
|
@@ -264,7 +289,7 @@ const FlyoutControlled = (props) => {
|
|
|
264
289
|
return;
|
|
265
290
|
if (trapFocusRef.current?.trapped) {
|
|
266
291
|
/* Locking the popover to not open it again on trigger focus */
|
|
267
|
-
if (triggerType === "hover") {
|
|
292
|
+
if (triggerType === "hover" && checkKeyboardMode()) {
|
|
268
293
|
lockedRef.current = true;
|
|
269
294
|
setTimeout(() => {
|
|
270
295
|
lockedRef.current = false;
|
|
@@ -281,27 +306,20 @@ const FlyoutControlled = (props) => {
|
|
|
281
306
|
return () => trapFocusRef.current?.release();
|
|
282
307
|
}, []);
|
|
283
308
|
/**
|
|
284
|
-
*
|
|
309
|
+
* Clean up safe polygon tracking on unmount or when flyout closes
|
|
285
310
|
*/
|
|
286
311
|
React.useEffect(() => {
|
|
287
312
|
if (!isRendered)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (triggerElRef.current)
|
|
292
|
-
resizeObserver.observe(triggerElRef.current);
|
|
293
|
-
return () => resizeObserver.disconnect();
|
|
294
|
-
}, [updatePosition, triggerElRef, isRendered]);
|
|
295
|
-
React.useEffect(() => {
|
|
296
|
-
updatePosition({ sync: true });
|
|
297
|
-
}, [isRTL, updatePosition]);
|
|
313
|
+
safeAreaRef.current?.cleanup();
|
|
314
|
+
return () => safeAreaRef.current?.cleanup();
|
|
315
|
+
}, [isRendered]);
|
|
298
316
|
/**
|
|
299
317
|
* Imperative methods for controlling Flyout
|
|
300
318
|
*/
|
|
301
319
|
React.useImperativeHandle(instanceRef, () => ({
|
|
302
320
|
open: handleOpen,
|
|
303
321
|
close: () => handleClose({}),
|
|
304
|
-
updatePosition: () => updatePosition(
|
|
322
|
+
updatePosition: () => updatePosition(),
|
|
305
323
|
}), [handleOpen, handleClose, updatePosition]);
|
|
306
324
|
useHotkeys({ Escape: () => handleClose({ reason: "escape-key" }) }, [handleClose]);
|
|
307
325
|
useOnClickOutside([flyoutElRef, triggerElRef], () => {
|
|
@@ -321,12 +339,11 @@ const FlyoutControlled = (props) => {
|
|
|
321
339
|
handleOpen,
|
|
322
340
|
handleFocus,
|
|
323
341
|
handleBlur,
|
|
324
|
-
|
|
342
|
+
handleTriggerMouseEnter,
|
|
343
|
+
handleContentMouseEnter,
|
|
325
344
|
handleMouseLeave,
|
|
326
345
|
handleTouchStart,
|
|
327
|
-
handleTransitionStart,
|
|
328
346
|
handleTransitionEnd,
|
|
329
|
-
handleMouseDown: handleTriggerMouseDown,
|
|
330
347
|
handleClick: handleTriggerClick,
|
|
331
348
|
handleContentMouseDown,
|
|
332
349
|
handleContentMouseUp,
|
|
@@ -336,6 +353,7 @@ const FlyoutControlled = (props) => {
|
|
|
336
353
|
contentAttributes,
|
|
337
354
|
contentGap,
|
|
338
355
|
contentMaxHeight,
|
|
356
|
+
contentMaxWidth,
|
|
339
357
|
containerRef,
|
|
340
358
|
disableContentHover,
|
|
341
359
|
autoFocus,
|
|
@@ -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
|
-
triggerBounds,
|
|
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;
|