reshaped 3.9.0 → 3.9.1-canary.3
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/Badge/Badge.js +5 -4
- package/dist/components/Badge/Badge.module.css +1 -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 -58
- 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
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a point is inside a triangle using barycentric coordinates
|
|
3
|
+
*/
|
|
4
|
+
function isPointInTriangle(point, triangle) {
|
|
5
|
+
const [p1, p2, p3] = triangle;
|
|
6
|
+
const denominator = (p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y);
|
|
7
|
+
const a = ((p2.y - p3.y) * (point.x - p3.x) + (p3.x - p2.x) * (point.y - p3.y)) / denominator;
|
|
8
|
+
const b = ((p3.y - p1.y) * (point.x - p3.x) + (p1.x - p3.x) * (point.y - p3.y)) / denominator;
|
|
9
|
+
const c = 1 - a - b;
|
|
10
|
+
return a >= 0 && a <= 1 && b >= 0 && b <= 1 && c >= 0 && c <= 1;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gets the two closest corners of the content element based on the flyout position
|
|
14
|
+
*/
|
|
15
|
+
function getContentCorners(contentRect, position) {
|
|
16
|
+
const corners = {
|
|
17
|
+
topLeft: { x: contentRect.left, y: contentRect.top },
|
|
18
|
+
topRight: { x: contentRect.right, y: contentRect.top },
|
|
19
|
+
bottomLeft: { x: contentRect.left, y: contentRect.bottom },
|
|
20
|
+
bottomRight: { x: contentRect.right, y: contentRect.bottom },
|
|
21
|
+
};
|
|
22
|
+
if (position?.startsWith("bottom")) {
|
|
23
|
+
return [corners.topLeft, corners.topRight];
|
|
24
|
+
}
|
|
25
|
+
else if (position?.startsWith("top")) {
|
|
26
|
+
return [corners.bottomLeft, corners.bottomRight];
|
|
27
|
+
}
|
|
28
|
+
else if (position?.startsWith("start")) {
|
|
29
|
+
return [corners.topRight, corners.bottomRight];
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return [corners.topLeft, corners.bottomLeft];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks if the mouse is over the trigger or content elements
|
|
37
|
+
*/
|
|
38
|
+
function isMouseOverElement(point, contentRef, triggerRef) {
|
|
39
|
+
const elements = document.elementsFromPoint(point.x, point.y);
|
|
40
|
+
return elements.some((el) => (contentRef.current && contentRef.current.contains(el)) ||
|
|
41
|
+
(triggerRef.current && triggerRef.current.contains(el)));
|
|
42
|
+
}
|
|
43
|
+
export function createSafeArea(options) {
|
|
44
|
+
const { contentRef, triggerRef, position, onClose, origin: passedOrigin } = options;
|
|
45
|
+
if (!contentRef.current) {
|
|
46
|
+
// If content doesn't exist, just close immediately
|
|
47
|
+
onClose();
|
|
48
|
+
return () => { };
|
|
49
|
+
}
|
|
50
|
+
const contentRect = contentRef.current.getBoundingClientRect();
|
|
51
|
+
const [corner1, corner2] = getContentCorners(contentRect, position);
|
|
52
|
+
// Add buffer to origin based on position to extend safe area
|
|
53
|
+
const buffer = 10;
|
|
54
|
+
const origin = { x: passedOrigin.x, y: passedOrigin.y };
|
|
55
|
+
if (position?.startsWith("bottom")) {
|
|
56
|
+
origin.y -= buffer;
|
|
57
|
+
}
|
|
58
|
+
else if (position?.startsWith("top")) {
|
|
59
|
+
origin.y += buffer;
|
|
60
|
+
}
|
|
61
|
+
else if (position?.startsWith("start")) {
|
|
62
|
+
origin.x += buffer;
|
|
63
|
+
}
|
|
64
|
+
else if (position?.startsWith("end")) {
|
|
65
|
+
origin.x -= buffer;
|
|
66
|
+
}
|
|
67
|
+
const triangle = [origin, corner1, corner2];
|
|
68
|
+
let timeoutId = null;
|
|
69
|
+
const cleanup = () => {
|
|
70
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
71
|
+
if (timeoutId)
|
|
72
|
+
clearTimeout(timeoutId);
|
|
73
|
+
};
|
|
74
|
+
// Start timeout for 1 second
|
|
75
|
+
const startTimeout = () => {
|
|
76
|
+
if (timeoutId)
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
timeoutId = setTimeout(() => {
|
|
79
|
+
onClose();
|
|
80
|
+
cleanup();
|
|
81
|
+
}, 1000);
|
|
82
|
+
};
|
|
83
|
+
const handleMouseMove = (e) => {
|
|
84
|
+
const currentPoint = { x: e.clientX, y: e.clientY };
|
|
85
|
+
if (isMouseOverElement(currentPoint, contentRef, triggerRef)) {
|
|
86
|
+
cleanup();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (isPointInTriangle(currentPoint, triangle)) {
|
|
90
|
+
startTimeout();
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
onClose();
|
|
94
|
+
cleanup();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
startTimeout();
|
|
98
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
99
|
+
return cleanup;
|
|
100
|
+
}
|
|
@@ -9,7 +9,7 @@ const Select = (props) => {
|
|
|
9
9
|
return (_jsx(SelectRoot, { ...props, children: (props) => {
|
|
10
10
|
const { options } = props;
|
|
11
11
|
const hasOptionChildren = React.Children.toArray(children).some((child) => {
|
|
12
|
-
return React.isValidElement(child) && child.type === "option";
|
|
12
|
+
return (React.isValidElement(child) && (child.type === "option" || child.type === "optgroup"));
|
|
13
13
|
});
|
|
14
14
|
const hasOptions = Boolean(options || hasOptionChildren);
|
|
15
15
|
if (!hasOptions) {
|
|
@@ -7,7 +7,6 @@ const TabsControlled = (props) => {
|
|
|
7
7
|
const { children, value, onChange, onSilentChange, itemWidth, variant, name, disableSelectionAnimation, direction = "row", size = "medium", } = props;
|
|
8
8
|
const id = useElementId();
|
|
9
9
|
const elActiveRef = React.useRef(null);
|
|
10
|
-
// eslint-disable-next-line react-hooks/refs
|
|
11
10
|
const elPrevActiveRef = React.useRef(elActiveRef.current);
|
|
12
11
|
const elScrollableRef = React.useRef(null);
|
|
13
12
|
const [selection, setSelection] = React.useState({
|
|
@@ -89,7 +89,6 @@ const ToastContainer = (props) => {
|
|
|
89
89
|
// Height + padding + borders
|
|
90
90
|
height: status === "entered" ? `calc(${toastHeight}px + var(--rs-unit-x2) + 2px)` : 0,
|
|
91
91
|
// Disable transition when height of the toast can change
|
|
92
|
-
// eslint-disable-next-line react-hooks/refs
|
|
93
92
|
transitionDuration: resizingRef.current ? "0s" : undefined,
|
|
94
93
|
}, onTransitionEnd: handleTransitionEnd, onFocus: stopTimer, onBlur: startTimer, children: _jsx("span", { className: s.wrapper, children: _jsx(Toast, { ...toastProps, collapsed: index > 0 && !inspected, attributes: { ...toastProps.attributes, ref: wrapperRef } }) }) }));
|
|
95
94
|
};
|
|
@@ -10,9 +10,7 @@ const Expandable = (props) => {
|
|
|
10
10
|
const rootRef = React.useRef(null);
|
|
11
11
|
const mountedRef = React.useRef(false);
|
|
12
12
|
const [animatedHeight, setAnimatedHeight] = React.useState(active ? "auto" : null);
|
|
13
|
-
const contentClassNames = classNames(s.root,
|
|
14
|
-
// eslint-disable-next-line react-hooks/refs
|
|
15
|
-
mountedRef.current && animatedHeight !== "auto" && s["--animated"]);
|
|
13
|
+
const contentClassNames = classNames(s.root, mountedRef.current && animatedHeight !== "auto" && s["--animated"]);
|
|
16
14
|
const handleTransitionEnd = (e) => {
|
|
17
15
|
if (e.propertyName !== "height")
|
|
18
16
|
return;
|
|
@@ -18,7 +18,6 @@ const Portal = (props) => {
|
|
|
18
18
|
const { children, targetRef } = props;
|
|
19
19
|
const mountedToggle = useToggle();
|
|
20
20
|
const rootRef = React.useRef(null);
|
|
21
|
-
// eslint-disable-next-line react-hooks/refs
|
|
22
21
|
const rootNode = rootRef.current?.getRootNode();
|
|
23
22
|
const isShadowDom = rootNode instanceof ShadowRoot;
|
|
24
23
|
const defaultTargetEl = isShadowDom ? rootNode : document.body;
|
|
@@ -31,7 +30,6 @@ const Portal = (props) => {
|
|
|
31
30
|
*/
|
|
32
31
|
const portal = usePortalScope();
|
|
33
32
|
const nextScopeRef = targetRef || portal.scopeRef;
|
|
34
|
-
// eslint-disable-next-line react-hooks/refs
|
|
35
33
|
const targetEl = nextScopeRef?.current || defaultTargetEl;
|
|
36
34
|
useIsomorphicLayoutEffect(() => {
|
|
37
35
|
mountedToggle.activate();
|
|
@@ -39,7 +37,6 @@ const Portal = (props) => {
|
|
|
39
37
|
}, []);
|
|
40
38
|
/* Preserve the current theme when rendered in body */
|
|
41
39
|
return [
|
|
42
|
-
// eslint-disable-next-line react-hooks/refs
|
|
43
40
|
ReactDOM.createPortal(_jsx(Theme, { children: children }), targetEl),
|
|
44
41
|
// Make sure this element doesn't affect components using :last-child when their children use portals
|
|
45
42
|
!mountedToggle.active && _jsx("div", { ref: rootRef, className: s.root }, "root"),
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import * as keys from "../../constants/keys.js";
|
|
5
|
+
import { classNames } from "../../utilities/props.js";
|
|
6
|
+
const Actionable = forwardRef((props, ref) => {
|
|
7
|
+
const { children, render, href, onClick, type, disabled, as, stopPropagation, className, attributes, } = props;
|
|
8
|
+
const rootAttributes = { ...attributes };
|
|
9
|
+
const hasClickHandler = onClick || attributes?.onClick;
|
|
10
|
+
const hasFocusHandler = attributes?.onFocus || attributes?.onBlur;
|
|
11
|
+
const isLink = Boolean(href || attributes?.href);
|
|
12
|
+
// Including attributes ref for the cases when event listeners are added through it
|
|
13
|
+
// To make sure it doesn't render a span
|
|
14
|
+
const isButton = Boolean(hasClickHandler || hasFocusHandler || type || attributes?.ref);
|
|
15
|
+
const renderedAsButton = !isLink && isButton && (!as || as === "button");
|
|
16
|
+
// Using any here to let TS save on type resolving, otherwise TS throws an error due to the type complexity
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
let TagName;
|
|
19
|
+
if (isLink) {
|
|
20
|
+
TagName = "a";
|
|
21
|
+
rootAttributes.href = disabled ? undefined : href || attributes?.href;
|
|
22
|
+
}
|
|
23
|
+
else if (renderedAsButton) {
|
|
24
|
+
TagName = "button";
|
|
25
|
+
rootAttributes.type = type || attributes?.type || "button";
|
|
26
|
+
rootAttributes.disabled = disabled || attributes?.disabled;
|
|
27
|
+
}
|
|
28
|
+
else if (isButton) {
|
|
29
|
+
const isFocusable = as === "label";
|
|
30
|
+
const simulateButton = !isFocusable || hasClickHandler || hasFocusHandler;
|
|
31
|
+
TagName = as || "span";
|
|
32
|
+
rootAttributes.role = simulateButton ? "button" : undefined;
|
|
33
|
+
rootAttributes.tabIndex = simulateButton ? 0 : undefined;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
TagName = as || "span";
|
|
37
|
+
}
|
|
38
|
+
const handlePress = (event) => {
|
|
39
|
+
if (disabled)
|
|
40
|
+
return;
|
|
41
|
+
if (stopPropagation)
|
|
42
|
+
event.stopPropagation();
|
|
43
|
+
onClick?.(event);
|
|
44
|
+
attributes?.onClick?.(event);
|
|
45
|
+
};
|
|
46
|
+
const handleKeyDown = (event) => {
|
|
47
|
+
const isSpace = event.key === keys.SPACE;
|
|
48
|
+
const isEnter = event.key === keys.ENTER;
|
|
49
|
+
if (!isSpace && !isEnter)
|
|
50
|
+
return;
|
|
51
|
+
if (rootAttributes.role !== "button")
|
|
52
|
+
return;
|
|
53
|
+
if (stopPropagation)
|
|
54
|
+
event.stopPropagation();
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
handlePress(event);
|
|
57
|
+
};
|
|
58
|
+
const tagAttributes = {
|
|
59
|
+
ref: ref,
|
|
60
|
+
// rootAttributes can receive ref from Flyout
|
|
61
|
+
...rootAttributes,
|
|
62
|
+
className: classNames(className),
|
|
63
|
+
onClick: handlePress,
|
|
64
|
+
onKeyDown: handleKeyDown,
|
|
65
|
+
"aria-disabled": disabled ? true : undefined,
|
|
66
|
+
children: children,
|
|
67
|
+
};
|
|
68
|
+
if (render)
|
|
69
|
+
return render(tagAttributes);
|
|
70
|
+
return _jsx(TagName, { ...tagAttributes });
|
|
71
|
+
});
|
|
72
|
+
Actionable.displayName = "Actionable";
|
|
73
|
+
export default Actionable;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type * as G from "../../types/global";
|
|
3
|
+
export type AttributesRef = React.RefObject<HTMLButtonElement | null>;
|
|
4
|
+
type Attributes = G.Attributes<"button"> & Omit<React.JSX.IntrinsicElements["a"], keyof G.Attributes<"button">> & {
|
|
5
|
+
ref?: AttributesRef;
|
|
6
|
+
};
|
|
7
|
+
export type RenderAttributes = G.Attributes<"a"> & {
|
|
8
|
+
ref: React.RefObject<HTMLAnchorElement | null>;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
};
|
|
11
|
+
export type Props = {
|
|
12
|
+
/** Node for inserting the content */
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
/** Render a custom root element, useful for integrating with routers */
|
|
15
|
+
render?: (attributes: RenderAttributes) => React.ReactNode;
|
|
16
|
+
/** Callback when clicked, renders it as a button tag if href is not provided */
|
|
17
|
+
onClick?: (e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
|
|
18
|
+
/** URL, renders it as an anchor tag */
|
|
19
|
+
href?: string;
|
|
20
|
+
/** Type attribute, renders it as a button tag */
|
|
21
|
+
type?: React.ButtonHTMLAttributes<HTMLButtonElement>["type"];
|
|
22
|
+
/** Disable from user interaction */
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/** Prevent the event from bubbling up to the parent */
|
|
25
|
+
stopPropagation?: boolean;
|
|
26
|
+
/** Render as a different element */
|
|
27
|
+
as?: keyof React.JSX.IntrinsicElements;
|
|
28
|
+
/** Additional classname for the root element */
|
|
29
|
+
className?: G.ClassName;
|
|
30
|
+
/** Additional attributes for the root element */
|
|
31
|
+
attributes?: Attributes;
|
|
32
|
+
};
|
|
33
|
+
export type Ref = HTMLButtonElement | HTMLAnchorElement;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Actionable.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import * as keys from "../constants/keys.js";
|
|
2
3
|
import useHandlerRef from "./useHandlerRef.js";
|
|
3
4
|
const useOnClickOutside = (refs, handler, options) => {
|
|
4
5
|
const { disabled } = options || {};
|
|
@@ -25,11 +26,18 @@ const useOnClickOutside = (refs, handler, options) => {
|
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
28
|
};
|
|
29
|
+
const handleKeyDown = (event) => {
|
|
30
|
+
if (![keys.ENTER, keys.SPACE].includes(event.key))
|
|
31
|
+
return;
|
|
32
|
+
handleMouseDown(event);
|
|
33
|
+
};
|
|
28
34
|
document.addEventListener("mousedown", handleMouseDown, { passive: true });
|
|
29
35
|
document.addEventListener("touchstart", handleMouseDown, { passive: true });
|
|
36
|
+
document.addEventListener("keydown", handleKeyDown, { passive: true });
|
|
30
37
|
return () => {
|
|
31
38
|
document.removeEventListener("mousedown", handleMouseDown);
|
|
32
39
|
document.removeEventListener("touchstart", handleMouseDown);
|
|
40
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
33
41
|
};
|
|
34
42
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
43
|
}, [...refs]);
|
|
@@ -80,6 +80,10 @@ class TrapFocus {
|
|
|
80
80
|
const el = shadowRoot ?? document;
|
|
81
81
|
el.removeEventListener("keydown", this.#handleKeyDown);
|
|
82
82
|
};
|
|
83
|
+
#isLast = () => {
|
|
84
|
+
const tailItem = _a.chain.tailId && _a.chain.get(_a.chain.tailId);
|
|
85
|
+
return tailItem && tailItem.data.#root === this.#root;
|
|
86
|
+
};
|
|
83
87
|
/**
|
|
84
88
|
* Trap the focus, add observer and keyboard event listeners
|
|
85
89
|
* and create a chain item
|
|
@@ -98,6 +102,8 @@ class TrapFocus {
|
|
|
98
102
|
this.#mutationObserver = new MutationObserver(() => {
|
|
99
103
|
if (!this.#root)
|
|
100
104
|
return;
|
|
105
|
+
if (!this.#isLast())
|
|
106
|
+
return;
|
|
101
107
|
const currentActiveElement = getActiveElement(this.#root);
|
|
102
108
|
// Focus stayed inside the wrapper, no need to refocus
|
|
103
109
|
if (this.#root.contains(currentActiveElement))
|
|
@@ -117,10 +123,10 @@ class TrapFocus {
|
|
|
117
123
|
this.#addListeners();
|
|
118
124
|
if (mode === "dialog")
|
|
119
125
|
this.#screenReaderTrap.trap();
|
|
120
|
-
// Don't add back to the chain if we're traversing back
|
|
121
|
-
const tailItem = _a.chain.tailId && _a.chain.get(_a.chain.tailId);
|
|
122
126
|
const currentActiveElement = getActiveElement(this.#root);
|
|
123
|
-
|
|
127
|
+
const isLastInChain = this.#isLast();
|
|
128
|
+
// Don't add back to the chain if we're traversing back
|
|
129
|
+
if (!isLastInChain) {
|
|
124
130
|
this.#chainId = _a.chain.add(this);
|
|
125
131
|
// If the focus was moved manually (e.g. with autoFocus) - keep it there
|
|
126
132
|
if (!this.#root.contains(currentActiveElement)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reshaped",
|
|
3
3
|
"description": "Professionally crafted design system in React & Figma for building products of any scale and complexity",
|
|
4
|
-
"version": "3.9.
|
|
4
|
+
"version": "3.9.1-canary.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"email": "hello@reshaped.so",
|
|
7
7
|
"homepage": "https://reshaped.so",
|
|
@@ -90,10 +90,12 @@
|
|
|
90
90
|
"cssnano": "7.1.1",
|
|
91
91
|
"csstype": "3.1.3",
|
|
92
92
|
"culori": "4.0.2",
|
|
93
|
-
"postcss-custom-media": "11.0.6"
|
|
93
|
+
"postcss-custom-media": "11.0.6",
|
|
94
|
+
"@reshaped/utilities": "3.9.1-canary.3"
|
|
94
95
|
},
|
|
95
96
|
"scripts": {
|
|
96
97
|
"clean": "sh ./bin/clean.sh",
|
|
98
|
+
"dev": "storybook dev -p 3001 -c ../../.storybook --disable-telemetry",
|
|
97
99
|
"build": "pnpm clean && pnpm build:esm && pnpm build:css && pnpm build:bundle",
|
|
98
100
|
"build:themes": "node bin/cli.js theming --config dist/cli/theming/reshaped.config.js --output src/themes",
|
|
99
101
|
"build:esm": "tsc -p tsconfig.esm.json && resolve-tspaths -p tsconfig.esm.json",
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type * as T from "../Flyout.types";
|
|
2
|
-
/**
|
|
3
|
-
* Calculate styles for the current position
|
|
4
|
-
*/
|
|
5
|
-
declare const calculatePosition: (args: {
|
|
6
|
-
triggerBounds: DOMRect;
|
|
7
|
-
flyoutBounds: {
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
};
|
|
11
|
-
passedContainer?: HTMLElement | null;
|
|
12
|
-
containerBounds: DOMRect;
|
|
13
|
-
} & Pick<T.Options, "position" | "rtl" | "width" | "contentGap" | "contentShift" | "fallbackAdjustLayout" | "fallbackMinWidth" | "fallbackMinHeight">) => {
|
|
14
|
-
position: T.Position;
|
|
15
|
-
styles: {
|
|
16
|
-
left: string | null;
|
|
17
|
-
right: string | null;
|
|
18
|
-
top: string | null;
|
|
19
|
-
bottom: string | null;
|
|
20
|
-
transform: string;
|
|
21
|
-
height: string | null;
|
|
22
|
-
width: string | null;
|
|
23
|
-
};
|
|
24
|
-
boundaries: {
|
|
25
|
-
left: number;
|
|
26
|
-
top: number;
|
|
27
|
-
height: number;
|
|
28
|
-
width: number;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
export default calculatePosition;
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { SCREEN_OFFSET } from "./constants.js";
|
|
2
|
-
import { getRTLPosition, centerBySize } from "./helpers.js";
|
|
3
|
-
/**
|
|
4
|
-
* Calculate styles for the current position
|
|
5
|
-
*/
|
|
6
|
-
const calculatePosition = (args) => {
|
|
7
|
-
const { triggerBounds, flyoutBounds, containerBounds, position: passedPosition, rtl, width: passedWidth, contentGap = 0, contentShift = 0, passedContainer, fallbackAdjustLayout,
|
|
8
|
-
// fallbackMinWidth,
|
|
9
|
-
fallbackMinHeight, } = args;
|
|
10
|
-
const isFullWidth = passedWidth === "full" || passedWidth === "100%";
|
|
11
|
-
let left = 0;
|
|
12
|
-
let top = 0;
|
|
13
|
-
let bottom = null;
|
|
14
|
-
let right = null;
|
|
15
|
-
let height = undefined;
|
|
16
|
-
let width = undefined;
|
|
17
|
-
let position = passedPosition;
|
|
18
|
-
if (rtl)
|
|
19
|
-
position = getRTLPosition(position);
|
|
20
|
-
if (isFullWidth || width === "trigger") {
|
|
21
|
-
position = position.includes("top") ? "top" : "bottom";
|
|
22
|
-
}
|
|
23
|
-
const isHorizontalPosition = !!position.match(/^(start|end)/);
|
|
24
|
-
// contentGap adds padding to the flyout to make sure it doesn't disapper while moving the mouse to the content
|
|
25
|
-
// So its width/height is bigger than the visible part of the content
|
|
26
|
-
const flyoutWidth = flyoutBounds.width + (isHorizontalPosition ? contentGap : 0);
|
|
27
|
-
const flyoutHeight = flyoutBounds.height + (!isHorizontalPosition ? contentGap : 0);
|
|
28
|
-
const triggerWidth = triggerBounds.width;
|
|
29
|
-
const triggerHeight = triggerBounds.height;
|
|
30
|
-
// Detect passed container scroll to sync the flyout position with it
|
|
31
|
-
const containerX = passedContainer?.scrollLeft;
|
|
32
|
-
const containerY = passedContainer?.scrollTop;
|
|
33
|
-
const scrollX = containerX ?? window.scrollX;
|
|
34
|
-
const scrollY = containerY ?? window.scrollY;
|
|
35
|
-
const renderContainerHeight = passedContainer?.clientHeight ?? window.innerHeight;
|
|
36
|
-
const renderContainerWidth = passedContainer?.clientWidth ?? window.innerWidth;
|
|
37
|
-
// When rendering in the body, bottom bounds will be larrger than the viewport so we calculate it manually
|
|
38
|
-
const containerBoundsBottom = passedContainer
|
|
39
|
-
? containerBounds.bottom
|
|
40
|
-
: window.innerHeight - scrollY;
|
|
41
|
-
// When inside a container, adjut position based on the container scroll since flyout is rendered outside the scroll area
|
|
42
|
-
const relativeLeft = triggerBounds.left - containerBounds.left + (containerX || 0);
|
|
43
|
-
const relativeRight = containerBounds.right - triggerBounds.right - (containerX || 0);
|
|
44
|
-
const relativeTop = triggerBounds.top - containerBounds.top + (containerY || 0);
|
|
45
|
-
const relativeBottom = containerBoundsBottom - triggerBounds.bottom - (containerY || 0);
|
|
46
|
-
switch (position) {
|
|
47
|
-
case "start":
|
|
48
|
-
case "start-top":
|
|
49
|
-
case "start-bottom":
|
|
50
|
-
left = relativeLeft - flyoutWidth;
|
|
51
|
-
right = relativeRight + triggerWidth;
|
|
52
|
-
break;
|
|
53
|
-
case "end":
|
|
54
|
-
case "end-top":
|
|
55
|
-
case "end-bottom":
|
|
56
|
-
left = relativeLeft + triggerWidth;
|
|
57
|
-
break;
|
|
58
|
-
case "bottom":
|
|
59
|
-
case "top":
|
|
60
|
-
left = relativeLeft + centerBySize(triggerWidth, flyoutWidth) + contentShift;
|
|
61
|
-
break;
|
|
62
|
-
case "top-start":
|
|
63
|
-
case "bottom-start":
|
|
64
|
-
left = relativeLeft + contentShift;
|
|
65
|
-
break;
|
|
66
|
-
case "top-end":
|
|
67
|
-
case "bottom-end":
|
|
68
|
-
left = relativeLeft + triggerWidth - flyoutWidth + contentShift;
|
|
69
|
-
right = relativeRight - contentShift;
|
|
70
|
-
break;
|
|
71
|
-
default:
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
switch (position) {
|
|
75
|
-
case "top":
|
|
76
|
-
case "top-start":
|
|
77
|
-
case "top-end":
|
|
78
|
-
top = relativeTop - flyoutHeight;
|
|
79
|
-
bottom = relativeBottom + triggerHeight;
|
|
80
|
-
break;
|
|
81
|
-
case "bottom":
|
|
82
|
-
case "bottom-start":
|
|
83
|
-
case "bottom-end":
|
|
84
|
-
top = relativeTop + triggerHeight;
|
|
85
|
-
break;
|
|
86
|
-
case "start":
|
|
87
|
-
case "end":
|
|
88
|
-
top = relativeTop + centerBySize(triggerHeight, flyoutHeight) + contentShift;
|
|
89
|
-
break;
|
|
90
|
-
case "start-top":
|
|
91
|
-
case "end-top":
|
|
92
|
-
top = relativeTop + contentShift;
|
|
93
|
-
break;
|
|
94
|
-
case "start-bottom":
|
|
95
|
-
case "end-bottom":
|
|
96
|
-
top = relativeTop + triggerHeight - flyoutHeight + contentShift;
|
|
97
|
-
bottom = relativeBottom - contentShift;
|
|
98
|
-
break;
|
|
99
|
-
default:
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
if (fallbackAdjustLayout) {
|
|
103
|
-
const getOverflow = () => {
|
|
104
|
-
return {
|
|
105
|
-
top: -top + scrollY + SCREEN_OFFSET,
|
|
106
|
-
bottom: top + flyoutHeight + SCREEN_OFFSET - scrollY - renderContainerHeight,
|
|
107
|
-
left: -left + scrollX + SCREEN_OFFSET,
|
|
108
|
-
right: left + flyoutWidth + SCREEN_OFFSET - scrollX - renderContainerWidth,
|
|
109
|
-
};
|
|
110
|
-
};
|
|
111
|
-
const overflow = getOverflow();
|
|
112
|
-
if (isHorizontalPosition) {
|
|
113
|
-
if (overflow.top > 0) {
|
|
114
|
-
top = SCREEN_OFFSET + scrollY;
|
|
115
|
-
if (bottom !== null)
|
|
116
|
-
bottom = bottom - overflow.top;
|
|
117
|
-
}
|
|
118
|
-
else if (overflow.bottom > 0) {
|
|
119
|
-
top = top - overflow.bottom;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
if (overflow.left > 0) {
|
|
124
|
-
left = SCREEN_OFFSET + scrollX;
|
|
125
|
-
if (right !== null)
|
|
126
|
-
right = right - overflow.left;
|
|
127
|
-
}
|
|
128
|
-
else if (overflow.right > 0) {
|
|
129
|
-
left = left - overflow.right;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const updatedOverflow = getOverflow();
|
|
133
|
-
if (updatedOverflow.top > 0) {
|
|
134
|
-
height = Math.max(fallbackMinHeight ? parseInt(fallbackMinHeight) : 0, flyoutHeight - updatedOverflow.top);
|
|
135
|
-
top = top + (flyoutHeight - height);
|
|
136
|
-
}
|
|
137
|
-
else if (updatedOverflow.bottom > 0) {
|
|
138
|
-
height = Math.max(fallbackMinHeight ? parseInt(fallbackMinHeight) : 0, flyoutHeight - updatedOverflow.bottom);
|
|
139
|
-
if (bottom !== null)
|
|
140
|
-
bottom = bottom + (flyoutHeight - height);
|
|
141
|
-
}
|
|
142
|
-
// TODO: Decide if we need horizontal scrolling for the fallbacks, might be a bad practice anyways
|
|
143
|
-
// if (updatedOverflow.left > 0) {
|
|
144
|
-
// width = Math.max(
|
|
145
|
-
// fallbackMinWidth ? parseInt(fallbackMinWidth) : 0,
|
|
146
|
-
// flyoutWidth - updatedOverflow.left
|
|
147
|
-
// );
|
|
148
|
-
// left = left + (flyoutWidth - width);
|
|
149
|
-
// } else if (updatedOverflow.right > 0) {
|
|
150
|
-
// width = Math.max(
|
|
151
|
-
// fallbackMinWidth ? parseInt(fallbackMinWidth) : 0,
|
|
152
|
-
// flyoutWidth - updatedOverflow.right
|
|
153
|
-
// );
|
|
154
|
-
// if (right !== null) right = right + (flyoutWidth - width);
|
|
155
|
-
// }
|
|
156
|
-
}
|
|
157
|
-
if (isFullWidth) {
|
|
158
|
-
left = SCREEN_OFFSET;
|
|
159
|
-
width = window.innerWidth - SCREEN_OFFSET * 2;
|
|
160
|
-
}
|
|
161
|
-
else if (passedWidth === "trigger") {
|
|
162
|
-
width = triggerBounds.width;
|
|
163
|
-
}
|
|
164
|
-
const translateX = right !== null ? -right : left;
|
|
165
|
-
const translateY = bottom !== null ? -bottom : top;
|
|
166
|
-
return {
|
|
167
|
-
position,
|
|
168
|
-
styles: {
|
|
169
|
-
left: right === null ? "0px" : null,
|
|
170
|
-
right: right === null ? null : "0px",
|
|
171
|
-
top: bottom === null ? "0px" : null,
|
|
172
|
-
bottom: bottom === null ? null : "0px",
|
|
173
|
-
transform: `translate(${translateX}px, ${translateY}px)`,
|
|
174
|
-
height: height !== undefined ? `${height}px` : null,
|
|
175
|
-
width: width !== undefined ? `${width}px` : (passedWidth ?? null),
|
|
176
|
-
},
|
|
177
|
-
boundaries: {
|
|
178
|
-
left,
|
|
179
|
-
top,
|
|
180
|
-
height: height ?? Math.ceil(flyoutHeight),
|
|
181
|
-
width: width ?? Math.ceil(flyoutWidth),
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
};
|
|
185
|
-
export default calculatePosition;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const SCREEN_OFFSET = 8;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const SCREEN_OFFSET = 8;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type * as T from "../Flyout.types";
|
|
2
|
-
import type * as G from "../../../types/global";
|
|
3
|
-
/**
|
|
4
|
-
* Set position of the target element to fit on the screen
|
|
5
|
-
*/
|
|
6
|
-
declare const flyout: (args: T.Options & {
|
|
7
|
-
flyoutEl: HTMLElement;
|
|
8
|
-
triggerEl: HTMLElement | null;
|
|
9
|
-
triggerBounds?: DOMRect | G.Coordinates | null;
|
|
10
|
-
}) => T.FlyoutData | undefined;
|
|
11
|
-
export default flyout;
|