terra-abstract-modal
Advanced tools
Comparing version 3.42.0 to 3.43.0
@@ -5,2 +5,7 @@ # Changelog | ||
## 3.43.0 - (July 31, 2023) | ||
* Changed | ||
* Focus is now locked within modal content when modal is open for improved keyboard accessibility. | ||
## 3.42.0 - (June 5, 2023) | ||
@@ -7,0 +12,0 @@ |
@@ -18,2 +18,3 @@ "use strict"; | ||
var _terraVisuallyHiddenText = _interopRequireDefault(require("terra-visually-hidden-text")); | ||
var _reactFocusLock = _interopRequireDefault(require("react-focus-lock")); | ||
var _ModalOverlay = _interopRequireDefault(require("./_ModalOverlay")); | ||
@@ -134,3 +135,6 @@ var _inertHelpers = require("./inertHelpers"); | ||
ref: ref | ||
}), /*#__PURE__*/_react.default.createElement("div", { | ||
}), /*#__PURE__*/_react.default.createElement(_reactFocusLock.default, { | ||
as: "div", | ||
className: cx('modal-content-focus-trap-container') | ||
}, /*#__PURE__*/_react.default.createElement("div", { | ||
className: modalContainerClassName, | ||
@@ -162,3 +166,3 @@ ref: setModalFocusElementRef, | ||
}); | ||
})))); | ||
}))))); | ||
}); | ||
@@ -165,0 +169,0 @@ ModalContent.propTypes = propTypes; |
{ | ||
"name": "terra-abstract-modal", | ||
"main": "lib/AbstractModal.js", | ||
"version": "3.42.0", | ||
"version": "3.43.0", | ||
"description": "The abstract modal is a structural component that provides the ability to display portal'd content in a layer above the app.", | ||
@@ -35,2 +35,3 @@ "repository": { | ||
"prop-types": "^15.5.8", | ||
"react-focus-lock": "^2.0.0", | ||
"react-portal": "^4.1.2", | ||
@@ -57,3 +58,3 @@ "terra-theme-context": "^1.8.0", | ||
}, | ||
"gitHead": "acfaff6af8239744cf9342888f7267ee29471513" | ||
"gitHead": "5b5d493c6e6b122e18c13e670b85e2abdb1c76f5" | ||
} |
@@ -8,2 +8,3 @@ import React, { forwardRef, useEffect } from 'react'; | ||
import VisuallyHiddenText from 'terra-visually-hidden-text'; | ||
import FocusLock from 'react-focus-lock'; | ||
import ModalOverlay from './_ModalOverlay'; | ||
@@ -148,29 +149,37 @@ import { hideModalDomUpdates, showModalDomUpdates } from './inertHelpers'; | ||
> | ||
<div className={modalContainerClassName} ref={setModalFocusElementRef} data-terra-abstract-modal-begin tabIndex="-1"> | ||
<FormattedMessage id="Terra.AbstractModal.BeginModalDialog"> | ||
{text => { | ||
// In the latest version of react-intl this param is an array, when previous versions it was a string. | ||
let useText = text; | ||
if (Array.isArray(text)) { | ||
useText = text.join(''); | ||
} | ||
return ( | ||
<VisuallyHiddenText text={useText} /> | ||
); | ||
}} | ||
</FormattedMessage> | ||
{children} | ||
<FormattedMessage id="Terra.AbstractModal.EndModalDialog"> | ||
{text => { | ||
// In the latest version of react-intl this param is an array, when previous versions it was a string. | ||
let useText = text; | ||
if (Array.isArray(text)) { | ||
useText = text.join(''); | ||
} | ||
return ( | ||
<VisuallyHiddenText text={useText} /> | ||
); | ||
}} | ||
</FormattedMessage> | ||
</div> | ||
{ | ||
/** | ||
* Use react-focus-lock library due to inability of focus-trap-react to handle children | ||
* within a React Portal: https://github.com/focus-trap/focus-trap-react/issues/27. | ||
*/ | ||
} | ||
<FocusLock as="div" className={cx('modal-content-focus-trap-container')}> | ||
<div className={modalContainerClassName} ref={setModalFocusElementRef} data-terra-abstract-modal-begin tabIndex="-1"> | ||
<FormattedMessage id="Terra.AbstractModal.BeginModalDialog"> | ||
{text => { | ||
// In the latest version of react-intl this param is an array, when previous versions it was a string. | ||
let useText = text; | ||
if (Array.isArray(text)) { | ||
useText = text.join(''); | ||
} | ||
return ( | ||
<VisuallyHiddenText text={useText} /> | ||
); | ||
}} | ||
</FormattedMessage> | ||
{children} | ||
<FormattedMessage id="Terra.AbstractModal.EndModalDialog"> | ||
{text => { | ||
// In the latest version of react-intl this param is an array, when previous versions it was a string. | ||
let useText = text; | ||
if (Array.isArray(text)) { | ||
useText = text.join(''); | ||
} | ||
return ( | ||
<VisuallyHiddenText text={useText} /> | ||
); | ||
}} | ||
</FormattedMessage> | ||
</div> | ||
</FocusLock> | ||
</div> | ||
@@ -177,0 +186,0 @@ </React.Fragment> |
@@ -9,3 +9,2 @@ const selector = '#root'; | ||
expect($('#root').getAttribute('inert')).toEqual('true'); | ||
expect($('#root').getAttribute('aria-hidden')).toEqual('true'); | ||
Terra.validates.element('open default modal', { selector }); | ||
@@ -17,4 +16,2 @@ }); | ||
Terra.validates.element('closed default modal'); | ||
expect($('#root').getAttribute('inert')).toEqual('false'); | ||
expect($('#root').getAttribute('aria-hidden')).toEqual(null); | ||
expect($('#modal-open-button').isFocused()).toBeTruthy(); | ||
@@ -27,3 +24,2 @@ }); | ||
expect($('#root').getAttribute('inert')).toEqual('true'); | ||
expect($('#root').getAttribute('aria-hidden')).toEqual('true'); | ||
expect($('[aria-modal="true"][role="dialog"] [data-terra-abstract-modal-begin="true"]').isFocused()).toBeTruthy(); | ||
@@ -34,4 +30,2 @@ }); | ||
browser.keys('Escape'); | ||
expect($('#root').getAttribute('inert')).toEqual('false'); | ||
expect($('#root').getAttribute('aria-hidden')).toEqual(null); | ||
expect($('#modal-open-button').isFocused()).toBeTruthy(); | ||
@@ -46,3 +40,2 @@ }); | ||
expect($('#root').getAttribute('inert')).toEqual('true'); | ||
expect($('#root').getAttribute('aria-hidden')).toEqual('true'); | ||
Terra.validates.element('open fullscreen modal', { selector }); | ||
@@ -54,4 +47,2 @@ }); | ||
Terra.validates.element('closed fullscreen modal'); | ||
expect($('#root').getAttribute('inert')).toEqual('false'); | ||
expect($('#root').getAttribute('aria-hidden')).toEqual(null); | ||
expect($('#modal-open-button').isFocused()).toBeTruthy(); | ||
@@ -64,3 +55,2 @@ }); | ||
expect($('#root').getAttribute('inert')).toEqual('true'); | ||
expect($('#root').getAttribute('aria-hidden')).toEqual('true'); | ||
expect($('[role="dialog"] [data-terra-abstract-modal-begin="true"]').isFocused()).toBeTruthy(); | ||
@@ -71,3 +61,2 @@ }); | ||
browser.keys('Escape'); | ||
expect($('#root').getAttribute('inert')).toEqual('false'); | ||
expect($('#root').getAttribute('aria-hidden')).toEqual(null); | ||
@@ -110,3 +99,3 @@ expect($('#modal-open-button').isFocused()).toBeTruthy(); | ||
describe('Focusable Content', () => { | ||
it('clicks to open modal', () => { | ||
before(() => { | ||
browser.url('/raw/tests/terra-abstract-modal/abstract-modal/default-abstract-modal'); | ||
@@ -127,27 +116,16 @@ $('button').click(); | ||
it('shifts focus forward away from the modal dialog', () => { | ||
it('prevents shift focus forward out of modal dialog', () => { | ||
browser.keys(['Tab']); | ||
expect($('#modal-button').isFocused()).toBeFalsy(); | ||
expect($('#modal-button').isFocused()).toBeTruthy(); | ||
expect($('#modal-open-button').isFocused()).toBeFalsy(); | ||
expect($('[aria-modal="true"][role="dialog"]').isFocused()).toBeFalsy(); | ||
Terra.validates.element('focused shifted outside the end of the modal', { selector }); | ||
Terra.validates.element('focus trapped from moving forward', { selector }); | ||
}); | ||
it('shifts focus back onto interactive elements within the modal', () => { | ||
it('prevents shift focus back out of modal dialog', () => { | ||
browser.keys(['Shift', 'Tab']); | ||
expect($('#modal-button').isFocused()).toBeTruthy(); | ||
Terra.validates.element('modal button focused again', { selector }); | ||
}); | ||
it('shifts focus back onto the modal dialog', () => { | ||
browser.keys(['Shift', 'Tab']); | ||
expect($('#modal-button').isFocused()).toBeFalsy(); | ||
expect($('#modal-open-button').isFocused()).toBeFalsy(); | ||
expect($('[aria-modal="true"][role="dialog"]').isFocused()).toBeTruthy(); | ||
Terra.validates.element('focused shifted back to the modal', { selector }); | ||
}); | ||
it('shifts focus backwards away from the modal dialog', () => { | ||
browser.keys(['Shift', 'Tab']); | ||
expect($('[aria-modal="true"][role="dialog"]').isFocused()).toBeFalsy(); | ||
Terra.validates.element('focus trapped from moving back', { selector }); | ||
}); | ||
@@ -154,0 +132,0 @@ }); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
168888
12
2600
+ Addedreact-focus-lock@^2.0.0
+ Added@babel/runtime@7.26.0(transitive)
+ Addeddetect-node-es@1.1.0(transitive)
+ Addedfocus-lock@1.3.5(transitive)
+ Addedreact-clientside-effect@1.2.6(transitive)
+ Addedreact-focus-lock@2.13.2(transitive)
+ Addedregenerator-runtime@0.14.1(transitive)
+ Addeduse-callback-ref@1.3.2(transitive)
+ Addeduse-sidecar@1.1.2(transitive)