reshaped 3.2.0-canary.4 → 3.2.0-canary.5
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/CHANGELOG.md +6 -0
- package/dist/bundle.css +1 -1
- package/dist/bundle.js +16 -17
- package/dist/components/Actionable/Actionable.js +2 -2
- package/dist/components/Actionable/Actionable.module.css +1 -1
- package/dist/components/Actionable/Actionable.types.d.ts +1 -0
- package/dist/components/Autocomplete/Autocomplete.js +10 -4
- package/dist/components/Dismissible/Dismissible.module.css +1 -1
- package/dist/components/Select/Select.js +1 -1
- package/dist/components/Table/Table.js +6 -4
- package/dist/components/Table/Table.types.d.ts +6 -1
- package/dist/components/_private/Flyout/FlyoutControlled.js +1 -0
- package/dist/components/_private/Flyout/useFlyout.js +2 -3
- package/dist/components/_private/Portal/Portal.js +3 -3
- package/dist/hooks/_private/useOnClickOutside.js +3 -2
- package/dist/hooks/_private/useSingletonHotkeys.js +11 -10
- package/dist/hooks/tests/useHotkeys.stories.js +3 -0
- package/dist/tests/ShadowDOM.stories.d.ts +6 -0
- package/dist/tests/ShadowDOM.stories.js +110 -0
- package/dist/themes/_generator/tests/themes.stories.js +1 -1
- package/dist/utilities/a11y/TrapFocus.js +14 -5
- package/dist/utilities/a11y/focus.d.ts +1 -1
- package/dist/utilities/a11y/focus.js +10 -5
- package/dist/utilities/dom.d.ts +2 -1
- package/dist/utilities/dom.js +12 -2
- package/package.json +4 -3
@@ -5,8 +5,8 @@ import { classNames } from "../../utilities/helpers.js";
|
|
5
5
|
import * as keys from "../../constants/keys.js";
|
6
6
|
import s from "./Actionable.module.css";
|
7
7
|
const Actionable = forwardRef((props, ref) => {
|
8
|
-
const { children, href, onClick, type, disabled, insetFocus, borderRadius, as, fullWidth, className, attributes, } = props;
|
9
|
-
const rootClassNames = classNames(s.root, className, disabled && s["--disabled"], borderRadius && s[`--radius-${borderRadius}`], insetFocus && s["--inset"], fullWidth && s["--full-width"]);
|
8
|
+
const { children, href, onClick, type, disabled, insetFocus, disableFocusRing, borderRadius, as, fullWidth, className, attributes, } = props;
|
9
|
+
const rootClassNames = classNames(s.root, className, disabled && s["--disabled"], borderRadius && s[`--radius-${borderRadius}`], insetFocus && s["--inset"], disableFocusRing && s["--disabled-focus-ring"], fullWidth && s["--full-width"]);
|
10
10
|
const rootAttributes = { ...attributes };
|
11
11
|
const hasClickHandler = onClick || attributes?.onClick;
|
12
12
|
const hasFocusHandler = attributes?.onFocus || attributes?.onBlur;
|
@@ -1 +1 @@
|
|
1
|
-
@layer rs.reset{.root{background:none;border:0;color:inherit;display:inline-block;font-size:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0;position:relative;text-align:initial;text-decoration:none;vertical-align:top;-webkit-tap-highlight-color:transparent}}.root:focus{outline:none}.root:focus-visible{box-shadow:var(--rs-focus-shadow);outline:none;z-index:var(--rs-z-index-raised)}.root.--inset:focus-visible{box-shadow:var(--rs-focus-inset-shadow)}.root.--radius-inherit:focus-visible{box-shadow:none}.root.--radius-inherit:focus-visible>*{box-shadow:var(--rs-focus-shadow)}.root.--radius-inherit.--inset:focus-visible>*{box-shadow:var(--rs-focus-inset-shadow)}[role=button].root,button.root{cursor:pointer;user-select:none}.root.--disabled,.root[disabled]{cursor:not-allowed}.root.--disabled:active,.root[disabled]:active{transform:none}.root.--full-width{width:100%}
|
1
|
+
@layer rs.reset{.root{background:none;border:0;color:inherit;display:inline-block;font-size:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0;position:relative;text-align:initial;text-decoration:none;vertical-align:top;-webkit-tap-highlight-color:transparent}}.root:focus{outline:none}.root:focus-visible{box-shadow:var(--rs-focus-shadow);outline:none;z-index:var(--rs-z-index-raised)}.root.--inset:focus-visible{box-shadow:var(--rs-focus-inset-shadow)}.root.--disabled-focus-ring:focus-visible{box-shadow:none}.root.--radius-inherit:focus-visible{box-shadow:none}.root.--radius-inherit:focus-visible>*{box-shadow:var(--rs-focus-shadow)}.root.--radius-inherit.--inset:focus-visible>*{box-shadow:var(--rs-focus-inset-shadow)}[role=button].root,button.root{cursor:pointer;user-select:none}.root.--disabled,.root[disabled]{cursor:not-allowed}.root.--disabled:active,.root[disabled]:active{transform:none}.root.--full-width{width:100%}
|
@@ -19,7 +19,11 @@ const Autocomplete = (props) => {
|
|
19
19
|
const [active, setActive] = React.useState(false);
|
20
20
|
const hasChildren = !!React.Children.toArray(children).filter(Boolean).length;
|
21
21
|
const lockedRef = React.useRef(false);
|
22
|
-
const handleOpen = React.useCallback(() =>
|
22
|
+
const handleOpen = React.useCallback(() => {
|
23
|
+
if (lockedRef.current)
|
24
|
+
return;
|
25
|
+
setActive(true);
|
26
|
+
}, []);
|
23
27
|
const handleClose = () => setActive(false);
|
24
28
|
useHotkeys({
|
25
29
|
[keys.BACKSPACE]: () => onBackspaceRef.current?.(),
|
@@ -28,9 +32,11 @@ const Autocomplete = (props) => {
|
|
28
32
|
disabled: !onBackspaceRef.current,
|
29
33
|
});
|
30
34
|
useHotkeys({
|
31
|
-
[keys.DOWN]: () =>
|
35
|
+
[keys.DOWN]: () => {
|
36
|
+
handleOpen();
|
37
|
+
},
|
32
38
|
[keys.ENTER]: () => {
|
33
|
-
const el = getActiveElement();
|
39
|
+
const el = getActiveElement(inputRef.current);
|
34
40
|
el?.click();
|
35
41
|
},
|
36
42
|
}, [handleOpen], { ref: inputRef, preventDefault: true });
|
@@ -50,7 +56,7 @@ const Autocomplete = (props) => {
|
|
50
56
|
onInput?.({ value: e.currentTarget.value, name, event: e });
|
51
57
|
textFieldProps.inputAttributes?.onInput?.(e);
|
52
58
|
};
|
53
|
-
return (_jsx(AutocompleteContext.Provider, { value: { onItemClick: handleItemClick }, children: _jsxs(DropdownMenu, { position: "bottom", width: "trigger", triggerType: "focus", trapFocusMode: "selection-menu", active:
|
59
|
+
return (_jsx(AutocompleteContext.Provider, { value: { onItemClick: handleItemClick }, children: _jsxs(DropdownMenu, { position: "bottom", width: "trigger", triggerType: "focus", trapFocusMode: "selection-menu", active: hasChildren && active, onClose: handleClose, onOpen: handleOpen, containerRef: containerRef, disableHideAnimation: true, instanceRef: instanceRef, children: [_jsx(DropdownMenu.Trigger, { children: ({ ref, ...attributes }) => (_jsx(TextField, { ...textFieldProps, name: name, onChange: handleChange, focused: hasChildren && active,
|
54
60
|
// Ignoring the type check since TS can't infer the correct html element type
|
55
61
|
attributes: { ...textFieldProps.attributes, ref }, inputAttributes: {
|
56
62
|
...textFieldProps.inputAttributes,
|
@@ -1 +1 @@
|
|
1
|
-
.root{padding-inline-end:var(--rs-unit-x7);position:relative}.close{inset-block-start:0;inset-inline-end:0;position:absolute;z-index:5}.--hide-close,.--variant-media{padding:0}.--variant-media .close{inset-block-start:var(--rs-unit-x2);inset-inline-end:var(--rs-unit-x2)}.--align-center .close{inset-block-start:50%;transform:translateY(-50%)}
|
1
|
+
.root{min-height:var(--rs-unit-x5);padding-inline-end:var(--rs-unit-x7);position:relative}.close{inset-block-start:0;inset-inline-end:0;position:absolute;z-index:5}.--hide-close,.--variant-media{padding:0}.--variant-media .close{inset-block-start:var(--rs-unit-x2);inset-inline-end:var(--rs-unit-x2)}.--align-center .close{inset-block-start:50%;transform:translateY(-50%)}
|
@@ -41,7 +41,7 @@ const Select = (props) => {
|
|
41
41
|
return 6;
|
42
42
|
return 4;
|
43
43
|
}), svg: icon })) : (startSlot) }));
|
44
|
-
return (_jsxs("div", { ...attributes, className: rootClassName, children: [options ? (_jsxs(_Fragment, { children: [startContent, _jsxs("select", { ...inputAttributes, onFocus: (onFocus || inputAttributes?.onFocus), onBlur: (onBlur || inputAttributes?.onBlur), className: s.input, disabled: disabled, name: name, value: value, defaultValue: defaultValue, onChange: handleChange, id: inputId, children: [placeholder && _jsx("option", { value: "", children: placeholder }), options.map((option) => (_jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value)))] })] })) : (_jsxs(_Fragment, { children: [_jsxs(Actionable, { className: s.input, disabled: disabled, onClick: onClick, attributes: {
|
44
|
+
return (_jsxs("div", { ...attributes, className: rootClassName, children: [options ? (_jsxs(_Fragment, { children: [startContent, _jsxs("select", { ...inputAttributes, onFocus: (onFocus || inputAttributes?.onFocus), onBlur: (onBlur || inputAttributes?.onBlur), className: s.input, disabled: disabled, name: name, value: value, defaultValue: defaultValue, onChange: handleChange, id: inputId, children: [placeholder && _jsx("option", { value: "", children: placeholder }), options.map((option) => (_jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value)))] })] })) : (_jsxs(_Fragment, { children: [_jsxs(Actionable, { className: s.input, disabled: disabled, disableFocusRing: true, onClick: onClick, attributes: {
|
45
45
|
...inputAttributes,
|
46
46
|
onFocus: onFocus || inputAttributes?.onFocus,
|
47
47
|
onBlur: onBlur || inputAttributes?.onBlur,
|
@@ -5,11 +5,11 @@ import getWidthStyles from "../../styles/width/index.js";
|
|
5
5
|
import getMinWidthStyles from "../../styles/minWidth/index.js";
|
6
6
|
import s from "./Table.module.css";
|
7
7
|
const TableCellPrivate = (props) => {
|
8
|
-
const { minWidth, rowSpan, colSpan, align, verticalAlign, tagName: TagName, padding, paddingInline, paddingBlock, children, attributes, } = props;
|
8
|
+
const { minWidth, rowSpan, colSpan, align, verticalAlign, tagName: TagName, padding, paddingInline, paddingBlock, children, className, attributes, } = props;
|
9
9
|
const width = props.width === "auto" ? "0px" : props.width;
|
10
10
|
const widthStyles = getWidthStyles(width);
|
11
11
|
const minWidthStyles = getMinWidthStyles(minWidth || width);
|
12
|
-
const headingClassNames = classNames(s.cell, widthStyles?.classNames, minWidthStyles?.classNames, (width === 0 || width === "0px") && s["cell--width-auto"], align && s[`cell--align-${align}`], verticalAlign && s[`cell--valign-${verticalAlign}`]);
|
12
|
+
const headingClassNames = classNames(s.cell, widthStyles?.classNames, minWidthStyles?.classNames, (width === 0 || width === "0px") && s["cell--width-auto"], align && s[`cell--align-${align}`], verticalAlign && s[`cell--valign-${verticalAlign}`], className);
|
13
13
|
const headingStyle = {
|
14
14
|
...widthStyles?.variables,
|
15
15
|
...minWidthStyles?.variables,
|
@@ -30,10 +30,12 @@ const TableRow = (props) => {
|
|
30
30
|
return (_jsx("tr", { ...attributes, className: rowClassNames, children: children }));
|
31
31
|
};
|
32
32
|
const TableBody = (props) => {
|
33
|
-
|
33
|
+
const { children, attributes, className } = props;
|
34
|
+
return (_jsx("tbody", { ...attributes, className: classNames(className), children: children }));
|
34
35
|
};
|
35
36
|
const TableHead = (props) => {
|
36
|
-
|
37
|
+
const { children, attributes, className } = props;
|
38
|
+
return (_jsx("thead", { ...attributes, className: classNames(className), children: children }));
|
37
39
|
};
|
38
40
|
const Table = (props) => {
|
39
41
|
const { children, border, columnBorder, className, attributes } = props;
|
@@ -23,6 +23,7 @@ export type CellProps = {
|
|
23
23
|
width?: "auto" | string | number;
|
24
24
|
minWidth?: string | number;
|
25
25
|
children?: React.ReactNode;
|
26
|
+
className?: G.ClassName;
|
26
27
|
attributes?: G.Attributes<"td">;
|
27
28
|
};
|
28
29
|
export type HeadingProps = CellProps & {
|
@@ -30,10 +31,14 @@ export type HeadingProps = CellProps & {
|
|
30
31
|
};
|
31
32
|
export type BodyProps = {
|
32
33
|
children: React.ReactNode;
|
34
|
+
className?: G.ClassName;
|
35
|
+
attributes?: G.Attributes<"tbody">;
|
33
36
|
};
|
34
37
|
export type HeadProps = {
|
35
38
|
children: React.ReactNode;
|
39
|
+
className?: G.ClassName;
|
40
|
+
attributes?: G.Attributes<"thead">;
|
36
41
|
};
|
37
|
-
export type PrivateCellProps =
|
42
|
+
export type PrivateCellProps = CellProps & {
|
38
43
|
tagName: "td" | "th";
|
39
44
|
};
|
@@ -113,6 +113,7 @@ const FlyoutRoot = (props) => {
|
|
113
113
|
}, [triggerType]);
|
114
114
|
const handleMouseEnter = React.useCallback(() => {
|
115
115
|
clearTimer();
|
116
|
+
console.log("enter");
|
116
117
|
if (hoverTriggeredWithTouchEventRef.current) {
|
117
118
|
handleOpen();
|
118
119
|
hoverTriggeredWithTouchEventRef.current = false;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from "react";
|
2
2
|
import useRTL from "../../../hooks/useRTL.js";
|
3
|
-
import { getClosestFlyoutTarget } from "../../../utilities/dom.js";
|
3
|
+
import { getClosestFlyoutTarget, getShadowRoot } from "../../../utilities/dom.js";
|
4
4
|
import calculatePosition from "./utilities/calculatePosition.js";
|
5
5
|
const topPos = ["top-start", "top", "top-end"];
|
6
6
|
const bottomPos = ["bottom-start", "bottom", "bottom-end"];
|
@@ -78,8 +78,7 @@ const flyout = (args) => {
|
|
78
78
|
targetClone.style.width = width;
|
79
79
|
}
|
80
80
|
}
|
81
|
-
const
|
82
|
-
const shadowRoot = rootNode instanceof ShadowRoot ? rootNode : null;
|
81
|
+
const shadowRoot = getShadowRoot(triggerEl);
|
83
82
|
// Insert inside shadow root if possible to make sure styles are applied correctly
|
84
83
|
(shadowRoot || document.body).appendChild(targetClone);
|
85
84
|
const flyoutBounds = targetClone.getBoundingClientRect();
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
3
3
|
import React from "react";
|
4
4
|
import ReactDOM from "react-dom";
|
5
5
|
import Theme from "../../Theme/index.js";
|
6
|
+
import { getShadowRoot } from "../../../utilities/dom.js";
|
6
7
|
import s from "./Portal.module.css";
|
7
8
|
const PortalScopeContext = React.createContext({});
|
8
9
|
export const usePortalScope = () => {
|
@@ -15,9 +16,8 @@ export const usePortalScope = () => {
|
|
15
16
|
const Portal = (props) => {
|
16
17
|
const { children, targetRef } = props;
|
17
18
|
const rootRef = React.useRef(null);
|
18
|
-
const
|
19
|
-
const
|
20
|
-
const defaultTargetEl = isShadowDom ? rootNode : document.body;
|
19
|
+
const shadowRoot = getShadowRoot(rootRef.current);
|
20
|
+
const defaultTargetEl = shadowRoot ?? document.body;
|
21
21
|
/**
|
22
22
|
* Check for parent portal to render inside it
|
23
23
|
* To avoid z-iondex issues
|
@@ -7,10 +7,11 @@ const useOnClickOutside = (refs, handler) => {
|
|
7
7
|
return;
|
8
8
|
const handleClick = (event) => {
|
9
9
|
let isInside = false;
|
10
|
+
const clickedEl = event.composedPath()[0];
|
10
11
|
refs.forEach((elRef) => {
|
11
12
|
if (!elRef.current ||
|
12
|
-
elRef.current ===
|
13
|
-
elRef.current.contains(
|
13
|
+
elRef.current === clickedEl ||
|
14
|
+
elRef.current.contains(clickedEl)) {
|
14
15
|
isInside = true;
|
15
16
|
}
|
16
17
|
});
|
@@ -19,7 +19,7 @@ const getEventKey = (e) => {
|
|
19
19
|
if (!e.key)
|
20
20
|
return;
|
21
21
|
// Having alt pressed modifies e.key value, so relying on e.code for it
|
22
|
-
if (e.altKey && e.
|
22
|
+
if (e.altKey && /^[Key|Digit|Numpad]/.test(e.code)) {
|
23
23
|
return e.code.toLowerCase().replace(/key|digit|numpad/, "");
|
24
24
|
}
|
25
25
|
return e.key.toLowerCase();
|
@@ -79,8 +79,9 @@ export class HotkeyStore {
|
|
79
79
|
return;
|
80
80
|
if (hotkeyData?.size) {
|
81
81
|
hotkeyData.forEach((data) => {
|
82
|
+
const eventTarget = e.composedPath()[0];
|
82
83
|
if (data.ref?.current &&
|
83
|
-
!(
|
84
|
+
!(eventTarget === data.ref.current || data.ref.current.contains(eventTarget))) {
|
84
85
|
return;
|
85
86
|
}
|
86
87
|
const resolvedEvent = pressedMap[pressedId];
|
@@ -146,14 +147,6 @@ export const SingletonHotkeysProvider = (props) => {
|
|
146
147
|
return false;
|
147
148
|
return true;
|
148
149
|
};
|
149
|
-
const addHotkeys = React.useCallback((hotkeys, ref, options = {}) => {
|
150
|
-
setHooksCount((prev) => prev + 1);
|
151
|
-
globalHotkeyStore.bindHotkeys(hotkeys, ref, options);
|
152
|
-
return () => {
|
153
|
-
setHooksCount((prev) => prev - 1);
|
154
|
-
globalHotkeyStore.unbindHotkeys(hotkeys);
|
155
|
-
};
|
156
|
-
}, []);
|
157
150
|
const handleWindowKeyDown = React.useCallback((e) => {
|
158
151
|
// Browsers trigger keyboard event without passing e.key when you click on autocomplete
|
159
152
|
if (!e.key)
|
@@ -166,6 +159,14 @@ export const SingletonHotkeysProvider = (props) => {
|
|
166
159
|
return;
|
167
160
|
removePressedKey(e);
|
168
161
|
}, [removePressedKey]);
|
162
|
+
const addHotkeys = React.useCallback((hotkeys, ref, options = {}) => {
|
163
|
+
setHooksCount((prev) => prev + 1);
|
164
|
+
globalHotkeyStore.bindHotkeys(hotkeys, ref, options);
|
165
|
+
return () => {
|
166
|
+
setHooksCount((prev) => prev - 1);
|
167
|
+
globalHotkeyStore.unbindHotkeys(hotkeys);
|
168
|
+
};
|
169
|
+
}, []);
|
169
170
|
React.useEffect(() => {
|
170
171
|
window.addEventListener("keydown", handleWindowKeyDown);
|
171
172
|
window.addEventListener("keyup", handleWindowKeyUp);
|
@@ -10,6 +10,9 @@ function Example() {
|
|
10
10
|
"mod + ArrowUp": () => console.log("top"),
|
11
11
|
"shift + ArrowRight": () => console.log("right"),
|
12
12
|
"shift + ArrowUp": () => console.log("top"),
|
13
|
+
"alt+shift+n": () => console.log("alt+shift+n"),
|
14
|
+
"shift+alt+n": () => console.log("shift+alt+n"),
|
15
|
+
"alt+shiftLeft+n": () => console.log("alt+shiftLeft+n"),
|
13
16
|
});
|
14
17
|
const active = checkHotkeyState("shift + b + n");
|
15
18
|
const shiftActive = checkHotkeyState("shift");
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import React, { useEffect, useState, useRef, forwardRef } from "react";
|
2
|
+
import root from "react-shadow";
|
3
|
+
import { Example } from "../utilities/storybook/index.js";
|
4
|
+
import Autocomplete from "../components/Autocomplete/index.js";
|
5
|
+
import View from "../components/View/index.js";
|
6
|
+
import DropdownMenu from "../components/DropdownMenu/index.js";
|
7
|
+
import Reshaped from "../components/Reshaped/index.js";
|
8
|
+
import Select from "../components/Select/index.js";
|
9
|
+
import Button from "../components/Button/index.js";
|
10
|
+
import Tooltip from "../components/Tooltip/index.js";
|
11
|
+
export default {
|
12
|
+
title: "Meta/ShadowDOM",
|
13
|
+
};
|
14
|
+
const getStylesData = () => {
|
15
|
+
const sourceStylesContainer = document.head;
|
16
|
+
return Array.from(sourceStylesContainer.children).filter((x) => x instanceof HTMLStyleElement);
|
17
|
+
};
|
18
|
+
// Create a component to render inside the Shadow DOM
|
19
|
+
const ShadowDiv = forwardRef((props, ref) => {
|
20
|
+
const shadowRef = useRef(null);
|
21
|
+
// Load styles
|
22
|
+
useEffect(() => {
|
23
|
+
if (!shadowRef?.current)
|
24
|
+
return;
|
25
|
+
// Add styles to the shadow DOM
|
26
|
+
// const shadowEl = shadowRef?.current.shadowRoot;
|
27
|
+
const shadowEl = shadowRef?.current.shadowRoot;
|
28
|
+
if (!shadowEl)
|
29
|
+
return;
|
30
|
+
const sourceStylesContainer = document.head;
|
31
|
+
const observer = new MutationObserver(getStylesData);
|
32
|
+
observer.observe(sourceStylesContainer, {
|
33
|
+
characterData: true,
|
34
|
+
childList: true,
|
35
|
+
subtree: true,
|
36
|
+
});
|
37
|
+
let styleBlock = shadowEl.getElementById("custom-outer-style");
|
38
|
+
if (!styleBlock) {
|
39
|
+
styleBlock = document.createElement("span");
|
40
|
+
styleBlock.id = "custom-outer-style";
|
41
|
+
shadowEl.appendChild(styleBlock);
|
42
|
+
}
|
43
|
+
else {
|
44
|
+
styleBlock.innerHTML = "";
|
45
|
+
}
|
46
|
+
styleBlock.append(...getStylesData() // finds all <style> tags containing your web component ID
|
47
|
+
.map((x) => x.cloneNode(true)) // copies styles into the current instance of your web component. You might need to have multiple instances, so all of them need to track the styles.
|
48
|
+
);
|
49
|
+
return () => observer.disconnect();
|
50
|
+
}, []);
|
51
|
+
return (<Reshaped>
|
52
|
+
<root.div className="quote" ref={shadowRef}>
|
53
|
+
<div ref={ref}>
|
54
|
+
{/*
|
55
|
+
Adding padding here since otherwise mouseenter won't trigger on contents
|
56
|
+
when mouse is switching from outside the shadow dom
|
57
|
+
*/}
|
58
|
+
<View padding={4}>{props.children}</View>
|
59
|
+
</div>
|
60
|
+
</root.div>
|
61
|
+
</Reshaped>);
|
62
|
+
});
|
63
|
+
// Main Component
|
64
|
+
const Component = () => {
|
65
|
+
const [valueAutoShadow, setValueAutoShadow] = useState("");
|
66
|
+
const [valueDropdownShadow, setValueDropdownShadow] = useState("");
|
67
|
+
const optionsAuto = ["Pizza", "Pie", "Ice-cream"];
|
68
|
+
const optionsDropdown = ["Turtle", "Cat", "Long-necked giraffe"];
|
69
|
+
const handleChangeAutoShadow = (args) => {
|
70
|
+
console.log("Autocomlete shadow value=", args);
|
71
|
+
setValueAutoShadow(args.value);
|
72
|
+
};
|
73
|
+
const handleChangeDropdownShadow = (val) => {
|
74
|
+
console.log("Dropdown shadow value=", val);
|
75
|
+
setValueDropdownShadow(val);
|
76
|
+
};
|
77
|
+
return (<View paddingBottom={50}>
|
78
|
+
<ShadowDiv>
|
79
|
+
<View gap={4} direction="row" wrap={false}>
|
80
|
+
<Autocomplete name="fruit-shadow" placeholder="Pick your food" value={valueAutoShadow} onChange={handleChangeAutoShadow}>
|
81
|
+
{optionsAuto.map((option) => (<Autocomplete.Item key={option} value={option} onClick={() => handleChangeAutoShadow({ value: option, name: "fruit-auto" })}>
|
82
|
+
{option}
|
83
|
+
</Autocomplete.Item>))}
|
84
|
+
</Autocomplete>
|
85
|
+
|
86
|
+
<DropdownMenu>
|
87
|
+
<DropdownMenu.Trigger>
|
88
|
+
{(attributes) => (<Select placeholder="Pick your animal" name="font" inputAttributes={attributes}>
|
89
|
+
{valueDropdownShadow}
|
90
|
+
</Select>)}
|
91
|
+
</DropdownMenu.Trigger>
|
92
|
+
<DropdownMenu.Content>
|
93
|
+
{optionsDropdown.map((option) => (<DropdownMenu.Item onClick={() => handleChangeDropdownShadow(option)} key={option}>
|
94
|
+
{option}
|
95
|
+
</DropdownMenu.Item>))}
|
96
|
+
</DropdownMenu.Content>
|
97
|
+
</DropdownMenu>
|
98
|
+
|
99
|
+
<Tooltip text="Tooltip for button">
|
100
|
+
{(attributes) => <Button attributes={attributes}>Hover me</Button>}
|
101
|
+
</Tooltip>
|
102
|
+
</View>
|
103
|
+
</ShadowDiv>
|
104
|
+
</View>);
|
105
|
+
};
|
106
|
+
export const behavior = () => (<Example>
|
107
|
+
<Example.Item title="base">
|
108
|
+
<Component />
|
109
|
+
</Example.Item>
|
110
|
+
</Example>);
|
@@ -13,7 +13,7 @@ import Link from "../../../components/Link/index.js";
|
|
13
13
|
import Text from "../../../components/Text/index.js";
|
14
14
|
import { getThemeCSS, generateThemeColors, baseThemeDefinition } from "../../index.js";
|
15
15
|
export default {
|
16
|
-
title: "Themes",
|
16
|
+
title: "Meta/Themes",
|
17
17
|
parameters: {
|
18
18
|
iframe: { url: "https://reshaped.so/docs/tokens/theming/runtime-theming" },
|
19
19
|
},
|
@@ -2,6 +2,7 @@ import Chain from "../Chain.js";
|
|
2
2
|
import * as keys from "../../constants/keys.js";
|
3
3
|
import TrapScreenReader from "./TrapScreenReader.js";
|
4
4
|
import { getActiveElement, getFocusableElements, focusElement, getFocusData } from "./focus.js";
|
5
|
+
import { getShadowRoot } from "../dom.js";
|
5
6
|
import { checkKeyboardMode } from "./keyboardMode.js";
|
6
7
|
class TrapFocus {
|
7
8
|
static chain = new Chain();
|
@@ -34,7 +35,7 @@ class TrapFocus {
|
|
34
35
|
const isDown = navigationMode === "arrows" && key === keys.DOWN;
|
35
36
|
const isPrev = (isBackTab && navigationMode === "tabs") || isUp;
|
36
37
|
const isNext = (isNextTab && navigationMode === "tabs") || isDown;
|
37
|
-
const isFocusedOnTrigger = getActiveElement() === this.trigger;
|
38
|
+
const isFocusedOnTrigger = getActiveElement(this.root) === this.trigger;
|
38
39
|
const focusData = getFocusData({
|
39
40
|
root: this.root,
|
40
41
|
target: isPrev ? "prev" : "next",
|
@@ -61,15 +62,23 @@ class TrapFocus {
|
|
61
62
|
return;
|
62
63
|
focusElement(focusData.el, { pseudoFocus });
|
63
64
|
};
|
64
|
-
addListeners = () =>
|
65
|
-
|
65
|
+
addListeners = () => {
|
66
|
+
const shadowRoot = getShadowRoot(this.root);
|
67
|
+
const el = shadowRoot ?? document;
|
68
|
+
el.addEventListener("keydown", this.handleKeyDown);
|
69
|
+
};
|
70
|
+
removeListeners = () => {
|
71
|
+
const shadowRoot = getShadowRoot(this.root);
|
72
|
+
const el = shadowRoot ?? document;
|
73
|
+
el.removeEventListener("keydown", this.handleKeyDown);
|
74
|
+
};
|
66
75
|
/**
|
67
76
|
* Trap the focus, add observer and keyboard event listeners
|
68
77
|
* and create a chain item
|
69
78
|
*/
|
70
79
|
trap = (options = {}) => {
|
71
80
|
const { mode = "dialog", includeTrigger, initialFocusEl } = options;
|
72
|
-
const trigger = getActiveElement();
|
81
|
+
const trigger = getActiveElement(this.root);
|
73
82
|
const focusable = getFocusableElements(this.root, {
|
74
83
|
additionalElement: includeTrigger ? trigger : undefined,
|
75
84
|
});
|
@@ -77,7 +86,7 @@ class TrapFocus {
|
|
77
86
|
this.options = { ...options, pseudoFocus };
|
78
87
|
this.trigger = trigger;
|
79
88
|
this.mutationObserver = new MutationObserver(() => {
|
80
|
-
const currentActiveElement = getActiveElement();
|
89
|
+
const currentActiveElement = getActiveElement(this.root);
|
81
90
|
// Focus stayed inside the wrapper, no need to refocus
|
82
91
|
if (this.root.contains(currentActiveElement))
|
83
92
|
return;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import type { FocusableElement } from "./types";
|
2
2
|
export declare const focusableSelector = "a,button,input:not([type=\"hidden\"]),textarea,select,details,[tabindex]:not([tabindex=\"-1\"])";
|
3
|
-
export declare const getActiveElement: () => HTMLButtonElement;
|
3
|
+
export declare const getActiveElement: (originEl?: HTMLElement | null) => HTMLButtonElement;
|
4
4
|
export declare const focusElement: (el: FocusableElement, options?: {
|
5
5
|
pseudoFocus?: boolean;
|
6
6
|
}) => void;
|
@@ -1,11 +1,16 @@
|
|
1
|
+
import { getShadowRoot } from "../dom.js";
|
1
2
|
const pseudoFocusAttribute = "data-rs-focus";
|
2
3
|
export const focusableSelector = 'a,button,input:not([type="hidden"]),textarea,select,details,[tabindex]:not([tabindex="-1"])';
|
3
|
-
export const getActiveElement = () => {
|
4
|
-
const
|
5
|
-
|
4
|
+
export const getActiveElement = (originEl) => {
|
5
|
+
const shadowRoot = originEl ? getShadowRoot(originEl) : null;
|
6
|
+
const rootEl = shadowRoot ?? document;
|
7
|
+
const pseudoFocusedEl = rootEl.querySelector(`[${pseudoFocusAttribute}]`);
|
8
|
+
return (pseudoFocusedEl || rootEl.activeElement);
|
6
9
|
};
|
7
10
|
export const focusElement = (el, options) => {
|
8
|
-
|
11
|
+
const shadowRoot = getShadowRoot(el);
|
12
|
+
const rootEl = shadowRoot ?? document;
|
13
|
+
rootEl.querySelector(`[${pseudoFocusAttribute}]`)?.removeAttribute(pseudoFocusAttribute);
|
9
14
|
if (options?.pseudoFocus) {
|
10
15
|
el.setAttribute(pseudoFocusAttribute, "true");
|
11
16
|
}
|
@@ -59,7 +64,7 @@ export const getFocusData = (args) => {
|
|
59
64
|
const { root, target, options } = args;
|
60
65
|
const focusable = getFocusableElements(root, { additionalElement: options?.additionalElement });
|
61
66
|
const focusableLimit = focusable.length - 1;
|
62
|
-
const currentElement = getActiveElement();
|
67
|
+
const currentElement = getActiveElement(root);
|
63
68
|
const currentIndex = focusable.indexOf(currentElement);
|
64
69
|
const positions = {
|
65
70
|
next: currentIndex + 1,
|
package/dist/utilities/dom.d.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
export declare const getClosestFlyoutTarget: (el: HTMLElement | null) => HTMLElement;
|
1
|
+
export declare const getClosestFlyoutTarget: (el: HTMLElement | null, iteration?: number) => HTMLElement;
|
2
2
|
export declare const disableUserSelect: () => void;
|
3
3
|
export declare const enableUserSelect: () => void;
|
4
4
|
export declare const disableScroll: () => void;
|
5
5
|
export declare const enableScroll: () => void;
|
6
|
+
export declare const getShadowRoot: (el: HTMLElement | null) => ShadowRoot | null;
|
package/dist/utilities/dom.js
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
-
export const getClosestFlyoutTarget = (el) => {
|
1
|
+
export const getClosestFlyoutTarget = (el, iteration = 0) => {
|
2
2
|
const style = el && window.getComputedStyle(el);
|
3
3
|
const overflowY = style?.overflowY;
|
4
4
|
const position = style?.position;
|
5
5
|
const isScrollable = overflowY?.includes("scroll");
|
6
6
|
const isFixed = position === "fixed" || position === "sticky";
|
7
|
+
// Only check shadow root on the first run
|
8
|
+
if (iteration === 0) {
|
9
|
+
const shadowRoot = getShadowRoot(el);
|
10
|
+
if (shadowRoot?.firstElementChild)
|
11
|
+
return shadowRoot.firstElementChild;
|
12
|
+
}
|
7
13
|
if (el === document.body || !el)
|
8
14
|
return document.body;
|
9
15
|
if ((isScrollable && el.scrollHeight > el.clientHeight) || isFixed)
|
10
16
|
return el;
|
11
|
-
return getClosestFlyoutTarget(el.parentElement);
|
17
|
+
return getClosestFlyoutTarget(el.parentElement, iteration + 1);
|
12
18
|
};
|
13
19
|
export const disableUserSelect = () => {
|
14
20
|
document.body.style.userSelect = "none";
|
@@ -25,3 +31,7 @@ export const enableScroll = () => {
|
|
25
31
|
window.removeEventListener("wheel", preventDefault);
|
26
32
|
window.removeEventListener("touchmove", preventDefault);
|
27
33
|
};
|
34
|
+
export const getShadowRoot = (el) => {
|
35
|
+
const rootNode = el?.getRootNode();
|
36
|
+
return rootNode instanceof ShadowRoot ? rootNode : null;
|
37
|
+
};
|
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.2.0-canary.
|
4
|
+
"version": "3.2.0-canary.5",
|
5
5
|
"license": "MIT",
|
6
6
|
"email": "hello@reshaped.so",
|
7
7
|
"homepage": "https://reshaped.so",
|
@@ -138,8 +138,9 @@
|
|
138
138
|
"postcss-each": "1.1.0",
|
139
139
|
"postcss-nested": "6.2.0",
|
140
140
|
"prettier": "3.3.3",
|
141
|
-
"react": "
|
142
|
-
"react-dom": "
|
141
|
+
"react": "19.0.0-rc-e4953922-20240919",
|
142
|
+
"react-dom": "19.0.0-rc-e4953922-20240919",
|
143
|
+
"react-shadow": "^20.5.0",
|
143
144
|
"resolve-tspaths": "0.8.19",
|
144
145
|
"size-limit": "11.1.4",
|
145
146
|
"storybook": "8.2.8",
|