diff --git a/package.json b/package.json index 193ebb1787b..fbf036d5c65 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,5 @@ }, "resolutions": { "selenium-webdriver": "4.0.0-beta.4" - }, - "packageManager": "yarn@1.22.1" + } } diff --git a/packages/react-ui/.creevey/images/ShadowDom/Simple Components/chrome2022.png b/packages/react-ui/.creevey/images/ShadowDom/Simple Components/chrome2022.png new file mode 100644 index 00000000000..3c2cd242a4e --- /dev/null +++ b/packages/react-ui/.creevey/images/ShadowDom/Simple Components/chrome2022.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ff642e3117c121baed3eb2789efd0458028d8e255bbce995551243273075914 +size 184170 diff --git a/packages/react-ui/.creevey/images/ShadowDom/Simple Components/chrome2022Dark.png b/packages/react-ui/.creevey/images/ShadowDom/Simple Components/chrome2022Dark.png new file mode 100644 index 00000000000..852d5c560db --- /dev/null +++ b/packages/react-ui/.creevey/images/ShadowDom/Simple Components/chrome2022Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e37bfd545ea53c12d2ed98cd00de20002b499cc8c5a21534b2dc87dc8d9dad9 +size 195436 diff --git a/packages/react-ui/.creevey/images/ShadowDom/Simple Components/firefox2022.png b/packages/react-ui/.creevey/images/ShadowDom/Simple Components/firefox2022.png new file mode 100644 index 00000000000..030df1ee665 --- /dev/null +++ b/packages/react-ui/.creevey/images/ShadowDom/Simple Components/firefox2022.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ca790782268d9981ec119681742446cb7557bf7f3be62f063bb7d74f28454cc +size 190015 diff --git a/packages/react-ui/.creevey/images/ShadowDom/Simple Components/firefox2022Dark.png b/packages/react-ui/.creevey/images/ShadowDom/Simple Components/firefox2022Dark.png new file mode 100644 index 00000000000..7a1bb530d64 --- /dev/null +++ b/packages/react-ui/.creevey/images/ShadowDom/Simple Components/firefox2022Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f30fed6c6867cfb9ea50c3d2ab4754ea515feb2c069538b2f4f1459213ef626 +size 198772 diff --git a/packages/react-ui/components/MaskedInput/ColorableInputElement/ColorableInputElement.styles.ts b/packages/react-ui/components/MaskedInput/ColorableInputElement/ColorableInputElement.styles.ts index d3f8bf4d6d3..91de30e963a 100644 --- a/packages/react-ui/components/MaskedInput/ColorableInputElement/ColorableInputElement.styles.ts +++ b/packages/react-ui/components/MaskedInput/ColorableInputElement/ColorableInputElement.styles.ts @@ -1,16 +1,23 @@ -import { injectGlobal, prefix } from '../../../lib/theming/Emotion'; +import type { Emotion } from '@emotion/css/create-instance'; + +import { memoizeStyle, prefix } from '../../../lib/theming/Emotion'; export const globalClasses = prefix('colorable')({ input: 'input', }); -injectGlobal` - input.${globalClasses.input} { - display: inline-block; - background-color: transparent; - background-size: 100%; - background-repeat: repeat; - background-clip: text; - -webkit-text-fill-color: transparent; - } -`; +export const getStyles = (emotion: Emotion) => + memoizeStyle({ + input() { + return emotion.css` + &.${globalClasses.input} { + display: inline-block; + background-color: transparent; + background-size: 100%; + background-repeat: repeat; + background-clip: text; + -webkit-text-fill-color: transparent; + } + `; + }, + }); diff --git a/packages/react-ui/components/MaskedInput/ColorableInputElement/ColorableInputElement.tsx b/packages/react-ui/components/MaskedInput/ColorableInputElement/ColorableInputElement.tsx index 6f12b332aac..006a83913cc 100644 --- a/packages/react-ui/components/MaskedInput/ColorableInputElement/ColorableInputElement.tsx +++ b/packages/react-ui/components/MaskedInput/ColorableInputElement/ColorableInputElement.tsx @@ -7,7 +7,7 @@ import { InputElement, InputElementProps } from '../../Input'; import { forwardRefAndName } from '../../../lib/forwardRefAndName'; import { EmotionContext } from '../../../lib/theming/Emotion'; -import { globalClasses } from './ColorableInputElement.styles'; +import { getStyles, globalClasses } from './ColorableInputElement.styles'; export type ColorableInputElementProps = InputElementProps & { showOnFocus?: boolean; @@ -28,6 +28,7 @@ export const ColorableInputElement = forwardRefAndName( const inputStyle = React.useRef(); const theme = useContext(ThemeContext); const emotion = useContext(EmotionContext); + const styles = getStyles(emotion); const debouncedPaintText = useCallback(debounce(paintText), []); const [active, setActive] = useState(true); @@ -63,7 +64,12 @@ export const ColorableInputElement = forwardRefAndName( onFocus: handleFocus, onBlur: handleBlur, inputRef, - className: emotion.cx(props.className, active && globalClasses.input), + className: emotion.cx(props.className, active && styles.input()), + style: { + ...inputProps.style, + WebkitBackgroundClip: 'text', + backgroundClip: 'text', + }, })} {active && } diff --git a/packages/react-ui/components/PasswordInput/PasswordInput.tsx b/packages/react-ui/components/PasswordInput/PasswordInput.tsx index 9ad91598c1b..be4fcdddc44 100644 --- a/packages/react-ui/components/PasswordInput/PasswordInput.tsx +++ b/packages/react-ui/components/PasswordInput/PasswordInput.tsx @@ -28,6 +28,7 @@ export interface PasswordInputProps extends Pick, export interface PasswordInputState { visible: boolean; + focused: boolean; capsLockEnabled?: boolean | null; } @@ -63,6 +64,7 @@ export class PasswordInput extends React.PureComponent { - this.setState((prevState) => ({ visible: !prevState.visible }), this.handleFocus); + this.setState((prevState) => ({ visible: !prevState.visible }), this.handleFocusOnInput); }; - private handleFocus = () => { + private handleFocusOnInput = () => { if (this.input) { this.input.focus(); } }; + private handleFocus = (event: React.FocusEvent) => { + if (this.state.focused) { + return; + } + + this.setState({ focused: true }); + + if (this.props.onFocus) { + this.props.onFocus(event); + } + }; + private handleBlur = () => { if (this.input) { this.input.blur(); @@ -229,6 +243,12 @@ export class PasswordInput extends React.PureComponent { this.setState({ visible: false }); + + if (!this.state.focused) { + return; + } + + this.setState({ focused: false }); }; private renderMain = (props: CommonWrapperRestProps) => { @@ -238,10 +258,11 @@ export class PasswordInput extends React.PureComponent +
diff --git a/packages/react-ui/components/SidePage/SidePage.tsx b/packages/react-ui/components/SidePage/SidePage.tsx index 8df635dff24..1ae81bd96dc 100644 --- a/packages/react-ui/components/SidePage/SidePage.tsx +++ b/packages/react-ui/components/SidePage/SidePage.tsx @@ -140,6 +140,10 @@ export class SidePage extends React.Component { public componentDidMount() { globalObject.addEventListener?.('keydown', this.handleKeyDown); this.stackSubscription = ModalStack.add(this, this.handleStackChange); + + if (this.layout) { + this.layout.addEventListener('scroll', LayoutEvents.emit); + } } public componentWillUnmount() { @@ -148,6 +152,10 @@ export class SidePage extends React.Component { this.stackSubscription.remove(); } ModalStack.remove(this); + + if (this.layout) { + this.layout.removeEventListener('scroll', LayoutEvents.emit); + } } /** diff --git a/packages/react-ui/components/TokenInput/TokenInput.tsx b/packages/react-ui/components/TokenInput/TokenInput.tsx index 25bd72542ba..df878153134 100644 --- a/packages/react-ui/components/TokenInput/TokenInput.tsx +++ b/packages/react-ui/components/TokenInput/TokenInput.tsx @@ -40,6 +40,7 @@ import { createPropsGetter } from '../../lib/createPropsGetter'; import { getUid } from '../../lib/uidUtils'; import { TokenView } from '../Token/TokenView'; import { ThemeContext } from '../../lib/theming/ThemeContext'; +import { isShadowRoot } from '../../lib/shadowDom/isShadowRoot'; import { TokenInputLocale, TokenInputLocaleHelper } from './locale'; import { getStyles } from './TokenInput.styles'; @@ -725,7 +726,10 @@ export class TokenInput extends React.PureComponent) => { if (this.menuRef && globalObject.document) { const menu = getRootNode(this.tokensInputMenu?.getMenuRef()); - const relatedTarget = event.relatedTarget || globalObject.document.activeElement; + + const isShadowRootElement = isShadowRoot(this.emotion.sheet.container.getRootNode()); + const relatedTarget = + (isShadowRootElement ? event.target : event.relatedTarget) ?? globalObject.document.activeElement; if (menu && menu.contains(relatedTarget)) { return true; diff --git a/packages/react-ui/components/Tooltip/Tooltip.tsx b/packages/react-ui/components/Tooltip/Tooltip.tsx index 953551535cf..3cc7e6f3ba7 100644 --- a/packages/react-ui/components/Tooltip/Tooltip.tsx +++ b/packages/react-ui/components/Tooltip/Tooltip.tsx @@ -9,7 +9,7 @@ import { Popup, PopupPositionsType, PopupProps, ShortPopupPositionsType } from ' import { RenderLayer, RenderLayerProps } from '../../internal/RenderLayer'; import { Nullable } from '../../typings/utility-types'; import { MouseEventType } from '../../typings/event-types'; -import { containsTargetOrRenderContainer } from '../../lib/listenFocusOutside'; +import { clickOutsideContent } from '../../lib/listenFocusOutside'; import { Theme } from '../../lib/theming/Theme'; import { isTestEnv } from '../../lib/currentEnvironment'; import { CommonProps, CommonWrapper } from '../../internal/CommonWrapper'; @@ -17,7 +17,6 @@ import { rootNode, TSetRootNode } from '../../lib/rootNode'; import { InstanceWithAnchorElement } from '../../lib/InstanceWithAnchorElement'; import { createPropsGetter } from '../../lib/createPropsGetter'; import { CloseButtonIcon } from '../../internal/CloseButtonIcon/CloseButtonIcon'; -import { isInstanceOf } from '../../lib/isInstanceOf'; import { EmotionConsumer } from '../../lib/theming/Emotion'; import { ThemeContext } from '../../lib/theming/ThemeContext'; import { @@ -25,6 +24,7 @@ import { ReactUIFeatureFlags, ReactUIFeatureFlagsContext, } from '../../lib/featureFlagsContext'; +import { isShadowRoot } from '../../lib/shadowDom/isShadowRoot'; import { getStyles } from './Tooltip.styles'; @@ -548,12 +548,10 @@ export class Tooltip extends React.PureComponent imp } }; - private isClickOutsideContent(event: Event) { - if (this.contentElement && isInstanceOf(event.target, globalObject.Element)) { - return !containsTargetOrRenderContainer(event.target)(this.contentElement); - } - - return true; + private isClickOutsideContent(event: Event): boolean { + const node = this.contentElement; + const isShadowRootElement = isShadowRoot(this.emotion.sheet.container.getRootNode()); + return clickOutsideContent(event, node, isShadowRootElement); } private handleFocus = () => { diff --git a/packages/react-ui/components/__stories__/shadowDom.stories.tsx b/packages/react-ui/components/__stories__/shadowDom.stories.tsx new file mode 100644 index 00000000000..58d9c954ed5 --- /dev/null +++ b/packages/react-ui/components/__stories__/shadowDom.stories.tsx @@ -0,0 +1,384 @@ +import React, { useState } from 'react'; + +import { StylesContainer } from '../../lib/styles/StylesContainer'; +import shadowRoot from '../../lib/shadowDom/reactShadow'; +import { Autocomplete } from '../Autocomplete'; +import { Button } from '../Button'; +import { Link } from '../Link'; +import { Input } from '../Input'; +import { ComboBox } from '../ComboBox'; +import { Checkbox } from '../Checkbox'; +import { Kebab } from '../Kebab'; +import { Radio } from '../Radio'; +import { Toggle } from '../Toggle'; +import { MenuItem } from '../MenuItem'; +import { Gapped } from '../Gapped'; +import { Group } from '../Group'; +import { Calendar } from '../Calendar'; +import { Center } from '../Center'; +import { CurrencyInput } from '../CurrencyInput'; +import { CurrencyLabel } from '../CurrencyLabel'; +import { DateInput } from '../DateInput'; +import { DatePicker } from '../DatePicker'; +import { Dropdown } from '../Dropdown'; +import { DropdownMenu } from '../DropdownMenu'; +import { FileUploader } from '../FileUploader'; +import { FxInput } from '../FxInput'; +import { GlobalLoader } from '../GlobalLoader'; +import { Hint } from '../Hint'; +import { MenuHeader } from '../MenuHeader'; +import { MenuSeparator } from '../MenuSeparator'; +import { Paging } from '../Paging'; +import { PasswordInput } from '../PasswordInput'; +import { RadioGroup } from '../RadioGroup'; +import { ScrollContainer } from '../ScrollContainer'; +import { Select } from '../Select'; +import { Sticky } from '../Sticky'; +import { Switcher } from '../Switcher'; +import { Tabs } from '../Tabs'; +import { Textarea } from '../Textarea'; +import { Toast } from '../Toast'; +import { SingleToast } from '../SingleToast'; +import { Token } from '../Token'; +import { TokenInput } from '../TokenInput'; +import { Tooltip } from '../Tooltip'; +import { TooltipMenu } from '../TooltipMenu'; +import { ResponsiveLayout } from '../ResponsiveLayout'; +import { MaskedInput } from '../MaskedInput'; +import { Modal } from '../Modal'; +import { SidePage } from '../SidePage'; +import { MiniModal } from '../MiniModal'; + +export default { title: 'ShadowDom' }; + +const Components: React.JSX.Element[] = [ + , + , + , +
, + , + getItems={() => Promise.resolve([1, 2, 3])} renderItem={(x) => x} onValueChange={console.log} />, + , + , + , + , + + MenuItem + MenuItem + MenuItem + , + DropdownMenu}> + MenuItem + MenuItem + MenuItem + , + , + , + gap, + , + + + + , + hint, + , + + MenuItem + MenuItem + MenuItem + , + link, + // , + , + , + , + // , + // , + , +
+ + , + , + , + } />, +