diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6353ab4f96..de2e231581 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
+### BREAKING CHANGES
+- Add `trapFocus` and `autoFocus` props to `Popup` and remove `popupFocusTrapBehavior` and `popupAutoFocusBehavior` @sophieH29 ([#1565](https://github.com/stardust-ui/react/pull/1565))
+
## [v0.34.1](https://github.com/stardust-ui/react/tree/v0.34.1) (2019-07-11)
[Compare changes](https://github.com/stardust-ui/react/compare/v0.34.0...v0.34.1)
diff --git a/docs/src/examples/components/Popup/Types/PopupFocusTrapExample.shorthand.tsx b/docs/src/examples/components/Popup/Types/PopupFocusTrapExample.shorthand.tsx
index ee753335d3..4c9bbb11f2 100644
--- a/docs/src/examples/components/Popup/Types/PopupFocusTrapExample.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Types/PopupFocusTrapExample.shorthand.tsx
@@ -1,11 +1,11 @@
import * as React from 'react'
-import { Button, Flex, Input, Header, Popup, popupFocusTrapBehavior } from '@stardust-ui/react'
+import { Button, Flex, Input, Header, Popup } from '@stardust-ui/react'
const PopupFocusTrapExample = () => (
}
content={{
content: (
diff --git a/docs/src/examples/components/Popup/Usage/PopupExampleOnWithFocusTrap.shorthand.tsx b/docs/src/examples/components/Popup/Usage/PopupExampleOnWithFocusTrap.shorthand.tsx
index 4809367757..7b54c1b720 100644
--- a/docs/src/examples/components/Popup/Usage/PopupExampleOnWithFocusTrap.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Usage/PopupExampleOnWithFocusTrap.shorthand.tsx
@@ -1,5 +1,5 @@
import * as React from 'react'
-import { Button, Flex, Popup, popupFocusTrapBehavior } from '@stardust-ui/react'
+import { Button, Flex, Popup } from '@stardust-ui/react'
const contentWithButtons = {
content: (
@@ -15,19 +15,19 @@ const PopupExampleOnWithFocusTrap = () => (
}
content={contentWithButtons}
- accessibility={popupFocusTrapBehavior}
+ trapFocus
on="click"
/>
}
content={contentWithButtons}
- accessibility={popupFocusTrapBehavior}
+ trapFocus
on="hover"
/>
}
content={contentWithButtons}
- accessibility={popupFocusTrapBehavior}
+ trapFocus
on="focus"
/>
diff --git a/docs/src/examples/components/Portal/Types/PortalExampleFocusTrapped.shorthand.tsx b/docs/src/examples/components/Portal/Types/PortalExampleFocusTrapped.shorthand.tsx
index 7727851529..2b657c4fce 100644
--- a/docs/src/examples/components/Portal/Types/PortalExampleFocusTrapped.shorthand.tsx
+++ b/docs/src/examples/components/Portal/Types/PortalExampleFocusTrapped.shorthand.tsx
@@ -33,7 +33,7 @@ class PortalExampleFocusTrapped extends React.Component {
elementToFocusOnDismiss: null,
// Indicates whether to force focus inside a Portal, if the 'focus' event was invoked at any place.
// 'false' by default.
- forceFocusInsideTrap: false,
+ forceFocusInsideTrapOnOutsideFocus: false,
// Ignore focusing element which activated Portal after it was closed.
// 'false' by default.
ignoreExternalFocusing: false,
diff --git a/docs/src/prototypes/chatMessages/ChatMessageWithPopover/ReactionPopup.tsx b/docs/src/prototypes/chatMessages/ChatMessageWithPopover/ReactionPopup.tsx
index bf1100042d..b74370afec 100644
--- a/docs/src/prototypes/chatMessages/ChatMessageWithPopover/ReactionPopup.tsx
+++ b/docs/src/prototypes/chatMessages/ChatMessageWithPopover/ReactionPopup.tsx
@@ -1,6 +1,6 @@
import * as React from 'react'
import keyboardKey from 'keyboard-key'
-import { Popup, Menu, Reaction, ReactionProps, popupAutoFocusBehavior } from '@stardust-ui/react'
+import { Popup, Menu, Reaction, ReactionProps } from '@stardust-ui/react'
const getAriaLabel = ({ content: numberOfPersons, icon: emojiType }: ReactionProps) => {
if (numberOfPersons === 1) {
@@ -27,6 +27,7 @@ class ReactionPopup extends React.Component {
render() {
return (
{
on="hover"
open={this.state.open}
onOpenChange={this.handleOpenChange}
- accessibility={popupAutoFocusBehavior}
/>
)
}
diff --git a/docs/src/prototypes/chatPane/services/messageFactoryMock.tsx b/docs/src/prototypes/chatPane/services/messageFactoryMock.tsx
index 40661e5778..2cd1613e5b 100644
--- a/docs/src/prototypes/chatPane/services/messageFactoryMock.tsx
+++ b/docs/src/prototypes/chatPane/services/messageFactoryMock.tsx
@@ -3,7 +3,6 @@ import {
Popup,
Button,
Menu,
- popupFocusTrapBehavior,
AvatarProps,
ChatMessageProps,
DividerProps,
@@ -127,7 +126,7 @@ function createMessageContentWithAttachments(content: string, messageId: string)
const actionPopup = (
(
- FocusTrapZone is used to trap the focus in any html element. Pressing TAB key will circle
- focus within the inner focusable elements of the FocusTrapZone. For example, when Popup opens,
- we want the focus go inside Popup and trap there.{' '}
+ FocusTrapZone is used to grab and trap the focus inside an HTML element. Currently can be used
+ only in {code('Popup')} and {code('Dialog')} components. Pressing TAB key will circle focus
+ within the inner focusable elements of the FocusTrapZone. For example, when Popup opens, we
+ want the focus to go inside Popup and trap there.{' '}
{link('Read more about FocusTrapZone.', '/focus-trap-zone')}
- AutoFocusZone is used to focus inner element on mount. For example, when we want to focus
- inner element in Popup when it mounts, but still without focus trap.{' '}
- {link('Read more about AutoFocusZone.', '/auto-focus-zone')}
+ AutoFocusZone is used to focus inner element on mount. Currently can be used in{' '}
+ {code('Popup')}. For example, when we want to focus inner element in Popup when it mounts, but
+ still without focus trap. {link('Read more about AutoFocusZone.', '/auto-focus-zone')}
diff --git a/docs/src/views/AccessibilityBehaviors.tsx b/docs/src/views/AccessibilityBehaviors.tsx
index 253f1c5a3d..39146fe6e9 100644
--- a/docs/src/views/AccessibilityBehaviors.tsx
+++ b/docs/src/views/AccessibilityBehaviors.tsx
@@ -59,20 +59,6 @@ export default () => (
Read more about FocusZone.
- focusTrap - {code('FocusTrapZone')} grabs the focus and traps it within an HTML
- element, usually a dialog or popup.{' '}
- Read more about FocusTrapZone.
-
Type: {code('FocusTrapZoneProps | boolean')}.
-
-
- autoFocus - {code('AutoFocusZone')} is used to grab focus and put it to inner
- element when component mounts. For example, when it is needed to focus an inner element in
- the Popup when it mounts. If true, it is enabled with default properties or can be
- modified by setting object.{' '}
- Read more about AutoFocusZone.
-
Type: {code('AutoFocusZoneProps | boolean')}.
-
childBehaviors - {code('{ [childBehaviorSlot: string]: Accessibility }')} are used
for components such as {code('Menu')} that contain children which, in turn, require their
@@ -214,8 +200,6 @@ export default () => (
},
},
focusZone: {},
- focusTrap: {},
- autoFocus: {},
})
`}
/>
diff --git a/docs/src/views/AutoFocusZone.tsx b/docs/src/views/AutoFocusZone.tsx
index 92f10019ae..5465d2a678 100644
--- a/docs/src/views/AutoFocusZone.tsx
+++ b/docs/src/views/AutoFocusZone.tsx
@@ -16,25 +16,24 @@ export default () => (
Overview
- {code('AutoFocusZone')} can wrap a component and is used to grab focus and put it to an inner
- element when the component mounts. For example, when it is needed to focus an inner element in
- the Popup when it mounts.
+ {code('AutoFocusZone')} can be integrated into {code('Popup')} component and is used to grab
+ focus and put it to an inner element when the component mounts. This can be achieved by
+ specifying {code('autoFocus')} prop on the {code('Popup')}.
If you need both - grabbing the focus and trap the focus in the component - use{' '}
- FocusTrapZone.
+ {code('trapFocus')} prop for {code('Popup')}
+ to integrate FocusTrapZone.
Usage
- In Stardust, {code('AutoFocusZone')} is applied through accessibility behavior, as for{' '}
- {code('FocusZone')} and
- {code('FocusTrapZone')}. To enable auto focus for a component, in the behavior set prop{' '}
- {code('autoFocus')} to {code('true')}
- with default settings or set an object with desired values for auto focus zone props.{' '}
- Read more about Accessibility Behaviors.
+ In {code('Popup')}, set {code('autoFocus')} to {code('true')} with default settings or set an
+ object with desired values for auto focus zone props.
+
By default, focus will be set to the first tabbable element in the Popup:
+ `} />
- {code('AutoFocusZone')}'s props which can be applied in accessibility behavior (
+ {code('AutoFocusZone')}'s props which can be applied to {code('autoFocus')} prop (
{link(
'lookup for API on GitHub',
'https://github.com/stardust-ui/react/blob/master/packages/react/src/lib/accessibility/AutoFocusZone/AutoFocusZone.types.tsx',
@@ -44,31 +43,15 @@ export default () => (
Override {code('AutoFocusZone')} settings
- To be able to add/override {code('AutoFocusZone')} props already set for a component, it is
- needed to override or create a new accessibility behavior.
+ For example, we want to specify the focusable selector for Popup with auto focus. On Popup
+ mount, focus will go to the element matched to that selector. For that purpose, we can specify{' '}
+ {code('firstFocusableSelector')}.
-
- For example, we want to specify the focusable selector for Popup with auto focus, so on Popup
- mount, focus will go to the element matched to that selector. For that purpose, we can to
- override {code('popupAutoFocusBehavior')} and specify {code('firstFocusableSelector')} there.
-
- {
- const behavior = popupFocusTrapBehavior(props)
-
- behavior.autoFocus.firstFocusableSelector = ".btn-submit";
-
- return behavior
- }
- `}
- />
- And then use this new behavior by Popup component:
(
-
+
)`}
/>
- {code('FocusTrapZone')} grabs the focus and traps it within an HTML element, usually a dialog
- or popup. Pressing {code('TAB')} key will circle focus within the inner focusable elements of
- the {code('FocusTrapZone')}. The main purpose is to block user interaction outside{' '}
- {code('FocusTrapZone')}
- in any way. Therefore, keyboard events are not propagated outside {code('FocusTrapZone')}.
+ {code('FocusTrapZone')} grabs the focus and traps it within an HTML element. Currently can be
+ used only in {code('Popup')}
+ and {code('Dialog')} components. Pressing {code('TAB')} key will circle focus within the inner
+ focusable elements of the {code('FocusTrapZone')}. The main purpose is to block user
+ interaction outside {code('FocusTrapZone')} in any way. Therefore, keyboard events are not
+ propagated outside {code('FocusTrapZone')}, hence {code('Popup')} or {code('Dialog')}.
Stardust leverages Focus Trap Zone component which is based on the{' '}
@@ -32,16 +33,18 @@ export default () => (
Usage
- Stardust applies focus trap via accessibility behavior, the same way as it's done for{' '}
- FocusZone. To enable focus trap for component, it is needed, in
- behavior, to set prop {code('trapFocus')} to
+ To apply {code('FocusTrapZone')} to {code('Popup')} set prop {code('trapFocus')} to
{code('true')} with default settings or set an object with desired values for focus trap zone
- props. Read more about Accessibility Behaviors.{' '}
- Currently, it is used for Popup via {code('popupFocusTrapBehavior')} and Dialog via{' '}
- {code('dialogBehavior')}.
+ props.
+ `} />
- {code('FocusTrapZone')}'s props which can be applied in accessibility behavior (
+ {code('Dialog')} component has always set {code('trapFocus')} prop to {code('true')}, but it
+ is also possible to override default settings by specifying object of{' '}
+ {code('FocusTrapZoneProps')}.
+
+
+ {code('FocusTrapZone')}'s props which can be applied to {code('trapFocus')} prop (
{link(
'lookup for API on GitHub',
'https://github.com/stardust-ui/react/blob/master/packages/react/src/lib/accessibility/FocusTrapZone/FocusTrapZone.types.tsx',
@@ -50,31 +53,23 @@ export default () => (
Override {code('FocusTrapZone')} settings
-
- To be able to add/override {code('FocusTrapZone')} props already set for a component, it is
- needed to override or create a new accessibility behavior.
-
For example, we want to disable first focus on Popup mount, so we can control the initial
- focus by ourselves.
+ focus by ourselves:
{
- const behavior = popupFocusTrapBehavior(props)
-
- behavior.trapFocus.disableFirstFocus = true;
-
- return behavior
- }
- `}
+ const Popup = () => (
+
+ )`}
/>
- And then use this new behavior by Popup component:
+
Same usage applies to {code('Dialog')} component:
(
-
+ const Dialog = () => (
+
)`}
/>
Read more about:
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index 4d225f76b3..dd9f5282eb 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -32,12 +32,7 @@ import {
} from '../../lib/positioner'
import PopupContent from './PopupContent'
import { popupBehavior } from '../../lib/accessibility'
-import {
- AutoFocusZone,
- AutoFocusZoneProps,
- FocusTrapZone,
- FocusTrapZoneProps,
-} from '../../lib/accessibility/FocusZone'
+import { AutoFocusZoneProps, FocusTrapZoneProps } from '../../lib/accessibility/FocusZone'
import { Accessibility } from '../../lib/accessibility/types'
import { ReactAccessibilityBehavior } from '../../lib/accessibility/reactTypes'
@@ -60,7 +55,7 @@ export interface PopupProps
/**
* Accessibility behavior if overridden by the user.
* @default popupBehavior
- * @available popupFocusTrapBehavior, dialogBehavior
+ * @available dialogBehavior
* */
accessibility?: Accessibility
@@ -114,6 +109,12 @@ export interface PopupProps
/** Ref for Popup content DOM node. */
contentRef?: React.Ref
+
+ /** Controls whether or not focus trap should be applied, using boolean or FocusTrapZoneProps type value. */
+ trapFocus?: boolean | FocusTrapZoneProps
+
+ /** Controls whether or not auto focus should be applied, using boolean or AutoFocusZoneProps type value. */
+ autoFocus?: boolean | AutoFocusZoneProps
}
export interface PopupState {
@@ -122,6 +123,13 @@ export interface PopupState {
/**
* A Popup displays additional information on top of a page.
+ * @accessibility
+ * Do set `trapFocus` if the focus needs to be trapped inside of the Popup.
+ * Don't use `trapFocus` for `inline` popup, as it leads to broken behavior for screen reader users.
+ * Beware of using `autoFocus` as it just grabs focus and do not traps it. User is able to tab out from popup,
+ * so consider to use `inline` prop to save a correct tab order.
+ * If Popup's content is lazy loaded and focus needs to be trapped inside - make sure to use state change to trigger componentDidUpdate,
+ * so the focus can be set correctly to the first tabbable element inside Popup or manually set focus to the element inside once content is loaded.
*/
export default class Popup extends AutoControlledComponent {
static displayName = 'Popup'
@@ -164,6 +172,8 @@ export default class Popup extends AutoControlledComponent {
- const { content: propsContent, renderContent, contentRef, mountDocument, pointing } = this.props
+ const {
+ content: propsContent,
+ renderContent,
+ contentRef,
+ mountDocument,
+ pointing,
+ trapFocus,
+ autoFocus,
+ } = this.props
const content = renderContent ? renderContent(scheduleUpdate) : propsContent
const documentRef = toRefObject(mountDocument)
- const popupWrapperAttributes = {
- ...(rtl && { dir: 'rtl' }),
- ...accessibility.attributes.popup,
- ...accessibility.keyHandlers.popup,
- className: popupPositionClasses,
- ...this.getContentProps(),
- }
-
- const focusTrapProps = {
- ...(typeof accessibility.focusTrap === 'boolean' ? {} : accessibility.focusTrap),
- ...popupWrapperAttributes,
- } as FocusTrapZoneProps
-
- const autoFocusProps = {
- ...(typeof accessibility.autoFocus === 'boolean' ? {} : accessibility.autoFocus),
- ...popupWrapperAttributes,
- } as AutoFocusZoneProps
-
- /**
- * if there is no focus trap or auto focus wrapper, we should apply
- * HTML attributes and positioning to popup content directly
- */
- const popupContentAttributes =
- accessibility.focusTrap || accessibility.autoFocus ? {} : popupWrapperAttributes
-
const popupContent = Popup.Content.create(content, {
defaultProps: {
- ...popupContentAttributes,
+ ...(rtl && { dir: 'rtl' }),
+ ...accessibility.attributes.popup,
+ ...accessibility.keyHandlers.popup,
+ className: popupPositionClasses,
+ ...this.getContentProps(),
placement,
pointing,
pointerRef: this.pointerTargetRef,
- unstable_wrapped: accessibility.focusTrap || accessibility.autoFocus,
+ trapFocus,
+ autoFocus,
},
overrideProps: this.getContentProps,
})
@@ -461,13 +476,7 @@ export default class Popup extends AutoControlledComponent
- {accessibility.focusTrap ? (
- {popupContent}
- ) : accessibility.autoFocus ? (
- {popupContent}
- ) : (
- popupContent
- )}
+ {popupContent}
- /**
- * @deprecated
- * Indicates that PopupContent is wrapped with FocusZone. Do not use it, it used only for internal implementation and
- * will be removed in future releases.
- */
- unstable_wrapped?: boolean
+ /** Controls whether or not focus trap should be applied, using boolean or FocusTrapZoneProps type value. */
+ trapFocus?: boolean | FocusTrapZoneProps
+
+ /** Controls whether or not auto focus should be applied, using boolean or AutoFocusZoneProps type value. */
+ autoFocus?: boolean | AutoFocusZoneProps
}
class PopupContent extends UIComponent> {
@@ -75,7 +80,8 @@ class PopupContent extends UIComponent> {
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
pointerRef: customPropTypes.ref,
- unstable_wrapped: PropTypes.bool,
+ trapFocus: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
+ autoFocus: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
}
static defaultProps = {
@@ -97,23 +103,24 @@ class PopupContent extends UIComponent> {
unhandledProps,
styles,
}: RenderResultConfig): React.ReactNode {
- const { children, content, pointing, pointerRef } = this.props
-
- return (
-
+ const { children, content, pointing, pointerRef, trapFocus, autoFocus } = this.props
+
+ const popupContentProps: PopupContentProps = {
+ className: classes.root,
+ ...rtlTextContainer.getAttributes({ forElements: [children, content] }),
+ ...accessibility.attributes.root,
+ ...unhandledProps,
+ onMouseEnter: this.handleMouseEnter,
+ onMouseLeave: this.handleMouseLeave,
+ }
+
+ const popupContent = (
+ <>
{pointing && (
{Box.create({}, { defaultProps: { styles: styles.pointer } })}
)}
-
{Box.create(
{},
{
@@ -123,8 +130,30 @@ class PopupContent extends UIComponent> {
},
},
)}
-
+ >
)
+
+ if (trapFocus) {
+ const focusTrapZoneProps = {
+ ...popupContentProps,
+ ...((_.keys(trapFocus).length && trapFocus) as FocusTrapZoneProps),
+ as: ElementType,
+ }
+
+ return {popupContent}
+ }
+
+ if (autoFocus) {
+ const autoFocusZoneProps = {
+ ...popupContentProps,
+ ...((_.keys(autoFocus).length && autoFocus) as AutoFocusZoneProps),
+ as: ElementType,
+ }
+
+ return {popupContent}
+ }
+
+ return {popupContent}
}
}
diff --git a/packages/react/src/components/Toolbar/ToolbarItem.tsx b/packages/react/src/components/Toolbar/ToolbarItem.tsx
index 9f5b46bddc..a0e5632986 100644
--- a/packages/react/src/components/Toolbar/ToolbarItem.tsx
+++ b/packages/react/src/components/Toolbar/ToolbarItem.tsx
@@ -28,7 +28,7 @@ import {
} from '../../types'
import { Popper } from '../../lib/positioner'
import { Accessibility } from '../../lib/accessibility/types'
-import { toolbarItemBehavior, popupFocusTrapBehavior } from '../../lib/accessibility'
+import { toolbarItemBehavior } from '../../lib/accessibility'
import ToolbarMenu from './ToolbarMenu'
import Icon from '../Icon/Icon'
@@ -96,7 +96,7 @@ export interface ToolbarItemProps
/**
* Attaches a `Popup` component to the ToolbarItem.
* Accepts all props as a `Popup`, except `trigger` and `children`.
- * Sets `accessibility` to `popupFocusTrapBehavior` by default.
+ * Traps focus by default.
* @see PopupProps
*/
popup?: Omit | string
@@ -227,7 +227,7 @@ class ToolbarItem extends UIComponent, ToolbarItemS
if (popup) {
return Popup.create(popup, {
defaultProps: {
- accessibility: popupFocusTrapBehavior,
+ trapFocus: true,
},
overrideProps: {
trigger: renderedItem,
diff --git a/packages/react/src/lib/accessibility/Behaviors/Dialog/dialogBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Dialog/dialogBehavior.ts
index 4e4364907f..3d62a97a96 100644
--- a/packages/react/src/lib/accessibility/Behaviors/Dialog/dialogBehavior.ts
+++ b/packages/react/src/lib/accessibility/Behaviors/Dialog/dialogBehavior.ts
@@ -1,6 +1,5 @@
import { Accessibility, AccessibilityAttributes } from '../../types'
-import popupFocusTrapBehavior from '../Popup/popupFocusTrapBehavior'
-import { PopupBehaviorProps } from '../Popup/popupBehavior'
+import popupBehavior, { PopupBehaviorProps } from '../Popup/popupBehavior'
/**
* @description
@@ -13,10 +12,9 @@ import { PopupBehaviorProps } from '../Popup/popupBehavior'
* Adds attribute 'role=dialog' to 'popup' slot.
* Adds attribute 'aria-labelledby' based on the property 'aria-labelledby' to 'popup' slot.
* Adds attribute 'aria-describedby' based on the property 'aria-describedby' to 'popup' slot.
- * Traps focus inside component.
*/
const dialogBehavior: Accessibility = props => {
- const behaviorData = popupFocusTrapBehavior(props)
+ const behaviorData = popupBehavior(props)
const defaultAriaLabelledBy = getDefaultAriaLabelledBy(props)
const defaultAriaDescribedBy = getDefaultAriaDescribedBy(props)
@@ -24,6 +22,7 @@ const dialogBehavior: Accessibility = props => {
behaviorData.attributes.popup = {
...behaviorData.attributes.popup,
role: 'dialog',
+ 'aria-modal': true,
'aria-labelledby': defaultAriaLabelledBy || props['aria-labelledby'],
'aria-describedby': defaultAriaDescribedBy || props['aria-describedby'],
}
diff --git a/packages/react/src/lib/accessibility/Behaviors/Popup/popupAutoFocusBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Popup/popupAutoFocusBehavior.ts
deleted file mode 100644
index aaa7dd6b9c..0000000000
--- a/packages/react/src/lib/accessibility/Behaviors/Popup/popupAutoFocusBehavior.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Accessibility } from '../../types'
-import popupBehavior, { PopupBehaviorProps } from './popupBehavior'
-
-/**
- * @description
- * Adds tabIndex='0' to 'trigger' slot, if it is not tabbable element and no tabIndex attribute provided.
- *
- * @specification
- * Adds attribute 'aria-disabled=true' to 'trigger' slot if 'disabled' property is true. Does not set the attribute otherwise.
- * Automatically focus the first focusable element inside component.
- */
-const popupAutoFocusBehavior: Accessibility = props => ({
- ...popupBehavior(props),
- autoFocus: true,
-})
-
-export default popupAutoFocusBehavior
diff --git a/packages/react/src/lib/accessibility/Behaviors/Popup/popupBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Popup/popupBehavior.ts
index 20006950e0..08f279ae52 100644
--- a/packages/react/src/lib/accessibility/Behaviors/Popup/popupBehavior.ts
+++ b/packages/react/src/lib/accessibility/Behaviors/Popup/popupBehavior.ts
@@ -9,7 +9,8 @@ import { PopupEvents, PopupEventsArray } from '../../../../components/Popup/Popu
*
* @specification
* Adds attribute 'aria-disabled=true' to 'trigger' slot if 'disabled' property is true. Does not set the attribute otherwise.
- * Adds attribute 'role=complementary' to 'popup' slot.
+ * Adds attribute 'role=dialog' to 'popup' slot if 'trapFocus' property is true. Sets the attribute to 'complementary' otherwise.
+ * Adds attribute 'aria-modal=true' to 'popup' slot if 'trapFocus' property is true. Does not set the attribute otherwise.
*/
const popupBehavior: Accessibility = props => {
const onAsArray = _.isArray(props.on) ? props.on : [props.on]
@@ -20,7 +21,8 @@ const popupBehavior: Accessibility = props => {
'aria-disabled': props.disabled,
},
popup: {
- role: 'complementary',
+ role: props.trapFocus ? 'dialog' : 'complementary',
+ 'aria-modal': props.trapFocus ? true : undefined,
},
},
keyActions: {
@@ -84,6 +86,8 @@ const getAriaAttributeFromProps = (
export default popupBehavior
export type PopupBehaviorProps = {
+ /** Indicates if focus should be trapped inside popup's container. */
+ trapFocus?: boolean | object
/** Events triggering the popup. */
on?: PopupEvents | PopupEventsArray
/** Indicates if popup's trigger is disabled. */
diff --git a/packages/react/src/lib/accessibility/Behaviors/Popup/popupFocusTrapBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Popup/popupFocusTrapBehavior.ts
deleted file mode 100644
index 3f7a78cb3d..0000000000
--- a/packages/react/src/lib/accessibility/Behaviors/Popup/popupFocusTrapBehavior.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Accessibility } from '../../types'
-import popupBehavior, { PopupBehaviorProps } from './popupBehavior'
-
-/**
- * @description
- * Adds tabIndex='0' to 'trigger' slot, if it is not tabbable element and no tabIndex attribute provided.
- *
- * @specification
- * Adds attribute 'aria-disabled=true' to 'trigger' slot if 'disabled' property is true. Does not set the attribute otherwise.
- * Adds attribute 'aria-modal=true' to 'popup' slot.
- * Adds attribute 'role=dialog' to 'popup' slot.
- * Traps focus inside component.
- */
-const popupFocusTrapBehavior: Accessibility = props => {
- const behaviorData = popupBehavior(props)
- behaviorData.attributes.popup = {
- ...behaviorData.attributes.popup,
- 'aria-modal': true,
- role: 'dialog',
- }
- behaviorData.focusTrap = true
-
- return behaviorData
-}
-
-export default popupFocusTrapBehavior
diff --git a/packages/react/src/lib/accessibility/index.ts b/packages/react/src/lib/accessibility/index.ts
index 3e5d6b26de..0480f347e3 100644
--- a/packages/react/src/lib/accessibility/index.ts
+++ b/packages/react/src/lib/accessibility/index.ts
@@ -33,8 +33,6 @@ export {
export { default as radioGroupBehavior } from './Behaviors/Radio/radioGroupBehavior'
export { default as radioGroupItemBehavior } from './Behaviors/Radio/radioGroupItemBehavior'
export { default as popupBehavior } from './Behaviors/Popup/popupBehavior'
-export { default as popupFocusTrapBehavior } from './Behaviors/Popup/popupFocusTrapBehavior'
-export { default as popupAutoFocusBehavior } from './Behaviors/Popup/popupAutoFocusBehavior'
export { default as chatBehavior } from './Behaviors/Chat/chatBehavior'
export { default as chatMessageBehavior } from './Behaviors/Chat/chatMessageBehavior'
export { default as gridBehavior } from './Behaviors/Grid/gridBehavior'
diff --git a/packages/react/src/lib/accessibility/types.ts b/packages/react/src/lib/accessibility/types.ts
index 317e8232be..5d6d7c6bfe 100644
--- a/packages/react/src/lib/accessibility/types.ts
+++ b/packages/react/src/lib/accessibility/types.ts
@@ -1,9 +1,4 @@
-import {
- FocusTrapZoneProps,
- FocusZoneProps,
- AutoFocusZoneProps,
- IS_FOCUSABLE_ATTRIBUTE,
-} from './FocusZone'
+import { FocusZoneProps, IS_FOCUSABLE_ATTRIBUTE } from './FocusZone'
export type AriaWidgetRole =
| 'button'
@@ -152,17 +147,12 @@ export type FocusZoneDefinition = {
props?: FocusZoneProps
}
-export type FocusTrapDefinition = FocusTrapZoneProps | boolean
-export type AutoFocusZoneDefinition = AutoFocusZoneProps | boolean
-
export type KeyActions = { [partName: string]: { [actionName: string]: KeyAction } }
export interface AccessibilityDefinition {
attributes?: AccessibilityAttributesBySlot
keyActions?: KeyActions
focusZone?: FocusZoneDefinition
- focusTrap?: FocusTrapDefinition
- autoFocus?: AutoFocusZoneDefinition
childBehaviors?: { [childBehaviorSlot: string]: Accessibility }
}
diff --git a/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts b/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
index 3a705b00e0..2281289e03 100644
--- a/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
+++ b/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
@@ -10,12 +10,6 @@ const popupContentStyles: ComponentSlotStylesInput {
- test('adds tabIndex=0 to trigger if element is not tabbable and tabIndex attribute is not provided', () => {
- const expectedResult = popupAutoFocusBehavior({ trigger: })
- expect(expectedResult.attributes.trigger.tabIndex).toEqual(0)
- })
-
- test('adds tabIndex attribute with value passed as prop', () => {
- const expectedResult = popupAutoFocusBehavior({ trigger: })
- expect(expectedResult.attributes.trigger.tabIndex).toEqual(-1)
- })
-
- test('does not add tabIndex if element is already tabbable', () => {
- const expectedResult = popupAutoFocusBehavior({ trigger: })
- expect(expectedResult.attributes.trigger.tabIndex).toBeUndefined()
- })
-})
diff --git a/packages/react/test/specs/behaviors/popupFocusTrapBehavior-test.tsx b/packages/react/test/specs/behaviors/popupFocusTrapBehavior-test.tsx
deleted file mode 100644
index 4976a7a55e..0000000000
--- a/packages/react/test/specs/behaviors/popupFocusTrapBehavior-test.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { popupFocusTrapBehavior } from 'src/lib/accessibility'
-import Button from 'src/components/Button/Button'
-import * as React from 'react'
-
-describe('PopupFocusTrapBehavior.ts', () => {
- test('adds tabIndex=0 to trigger if element is not tabbable and tabIndex attribute is not provided', () => {
- const expectedResult = popupFocusTrapBehavior({ trigger: })
- expect(expectedResult.attributes.trigger.tabIndex).toEqual(0)
- })
-
- test('adds tabIndex attribute with value passed as prop', () => {
- const expectedResult = popupFocusTrapBehavior({ trigger: })
- expect(expectedResult.attributes.trigger.tabIndex).toEqual(-1)
- })
-
- test('does not add tabIndex if element is already tabbable', () => {
- const expectedResult = popupFocusTrapBehavior({ trigger: })
- expect(expectedResult.attributes.trigger.tabIndex).toBeUndefined()
- })
-})
diff --git a/packages/react/test/specs/components/Popup/Popup-test.tsx b/packages/react/test/specs/components/Popup/Popup-test.tsx
index 7a617b3a40..f76f9aa3a0 100644
--- a/packages/react/test/specs/components/Popup/Popup-test.tsx
+++ b/packages/react/test/specs/components/Popup/Popup-test.tsx
@@ -1,9 +1,6 @@
import * as React from 'react'
import Popup, { PopupEvents } from 'src/components/Popup/Popup'
-import { Accessibility } from 'src/lib/accessibility/types'
-import { popupFocusTrapBehavior, popupBehavior, dialogBehavior } from 'src/lib/accessibility/index'
-
import { domEvent, mountWithProvider } from '../../../utils'
import * as keyboardKey from 'keyboard-key'
import { ReactWrapper } from 'enzyme'
@@ -171,14 +168,14 @@ describe('Popup', () => {
describe('keyboard event propagation', () => {
const expectPopupToHandleStopPropagation = (
- behavior: Accessibility,
+ trapFocus: boolean,
shouldStopPropagation: boolean,
) => {
const popup = mountWithProvider(
text to trigger popup }
content={{ id: contentId }}
- accessibility={behavior}
+ trapFocus={trapFocus}
/>,
)
@@ -192,14 +189,11 @@ describe('Popup', () => {
popupContentElement.simulate('keyDown', { stopPropagation })
expect(stopPropagation).toHaveBeenCalledTimes(shouldStopPropagation ? 1 : 0)
}
- test('stops when focus trap behavior is used', () => {
- expectPopupToHandleStopPropagation(popupFocusTrapBehavior, true)
- })
- test('stops when dialog behavior is used', () => {
- expectPopupToHandleStopPropagation(dialogBehavior, true)
+ test('stops when focus is trapped', () => {
+ expectPopupToHandleStopPropagation(true, true)
})
- test('does not stop when default behavior is used', () => {
- expectPopupToHandleStopPropagation(popupBehavior, false)
+ test('does not stop when focus is not trapped', () => {
+ expectPopupToHandleStopPropagation(false, false)
})
})
})