Skip to content

Commit 685a906

Browse files
stefcameronidorosDaviDevMod
authored
Shadow DOM support (v5.3.0) (focus-trap#570)
* chore(dev): declarative shadow root test fixtures - new development shadow-root-utils - refactor setupFixture to render using new utils - refactor current shadow dom tests to use declarative root - debug page renders shadow root fixtures correctly * feat: separate radio light/shadow dom groups * feat: detect display across shadow boundaries * feat: scan through shadow boundary - iterate down dom instead of query when getShadowRoot is provided - new candidate list/tree format with scoped lists * chore(types): added getShadowDom to types * fix: type of getShadowRoot option * test: add test to locate tabbable host * feat: slot elements are not focusable/tabbable * chore: added some jsdocs * refactor: modernize syntax - as requested in PR * Prepare for 5.3.0-beta.0 * 5.3.0-beta.0 * Adjusting code after focus-trap#604 and comments * Disable shadow DOM for isFocusable/isTabbable if getShadowRoot not given This goes along with disabling it for `tabbable()` and `focusable()` when the option isn't given. * Clarify getShadowRoot must be set to enable shadow DOM support * Add support for `getShadowRoot: true` Note this is the equivalent of `getShadowRoot: () => false` which simply enables shadow DOM support for all open shadows. * Prepare for v5.3.0-beta.1 * 5.3.0-beta.1 * Add prepublishOnly script for manual publishing * fix(index.js) The tabIndex of audio, video and details was left to the default if set to some NaN (focus-trap#610) * fix(index.js) The tabIndex of contentEditable elements was assumed to be zero in any case, not only in the case it was not specifically set. * Simplified and optimized 'getTabIndex'. * Made better use of short-circuit evaluation in 'isNodeMatchingSelectorTabbable', reducing the chances to call the computationally expensive 'isNodeMatchingSelectorFocusable'. * (Re)Added 'isScope' parameter to 'getTabIndex'. This parameter wasn't present in the master branch, so I lost it in the rebase process. * Added tests for a `contenteditable` with negative tab index. * Fixed bug, now the getTabIndex can return 0 not only when the tabindex is not explicitly set, but also when is set to a value that gives NaN when parsed as integer (which would have been resulted in the default browser tabIndex, as if the tabindex wasn't set at all). Also added test for the case an element has a tab index that can't be turned into an integer. * Added changeset, added entry in CHANGELOG.md and wrote more tests. * Be consistent with asterisks * Sync package.json/yarn.lock with beta-530 base branch Co-authored-by: Stefan Cameron <stefan@stefcameron.com> * [focus-trap#632] Add test for radios in a form (focus-trap#638) Can't repro the issue, but might as well keep the test since we seem to like fieldsets but not forms for some reason. * Add changeset for shadow root support Co-authored-by: Ido Rosenthal <idoros@gmail.com> Co-authored-by: DaviDevMod <98312056+DaviDevMod@users.noreply.github.com>
1 parent a331ef1 commit 685a906

24 files changed

+1319
-137
lines changed

.changeset/happy-zebras-attend.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tabbable': patch
3+
---
4+
5+
Fixed a bug in `getTabIndex`: the tab index of `<audio>`, `<video>` and `<details>` was left to the browser default if explicitly set to a value that couldn't be parsed as integer, leading to inconsistent behavior across browsers. Also slightly modified the function's logic to make it more efficient. Finally added tests to cover the fix.

.changeset/shadow-root.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'tabbable': minor
3+
---
4+
5+
Adds new Shadow DOM support (must be explicitly enabled using the new `getShadowRoot` option).
6+
- When enabled, supports open shadows by default, and can support closed shadows if the option is a function that returns the shadow for a given node. See documentation for more information.
7+
- Includes all updates from `5.3.0-beta.0` and `5.3.0-beta.1` releases.

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 5.3.0-beta.1
4+
5+
- Add support for setting `getShadowRoot: true` as an easy way to simply *enable* shadow DOM support. This is the equivalent of setting `getShadowRoot: () => false`, which means tabbable will find nodes in **open** shadow roots only.
6+
7+
## 5.3.0-beta.0
8+
9+
- Includes new Shadow DOM support for open shadows by default
10+
- Includes a new `getShadowRoot()` configuration option, enabling support for closed shadows
11+
312
## 5.2.1
413

514
### Patch Changes

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,25 @@ Type: `full` | `non-zero-area` | `none` . Default: `full`.
9191

9292
Configures how to check if an element is displayed, see ["Display check"](#display-check) below.
9393

94+
##### getShadowRoot
95+
96+
By default, tabbable overlooks (i.e. does not consider) __all__ elements contained in shadow DOMs (whether open or closed). This has been the behavior since the beginning.
97+
98+
Setting this option to a _truthy_ value enables Shadow DOM support, which means tabbable will consider elements _inside_ web components as candidates, both open (automatically) and closed (provided this function returns the shadow root).
99+
100+
Type: `boolean | (node: FocusableElement) => ShadowRoot | boolean | undefined`
101+
102+
- `boolean`:
103+
- `true` simply enables shadow DOM support for any __open__ shadow roots, but never presumes there is an undisclosed shadow. This is the equivalent of setting `getShadowRoot: () => false`
104+
- `false` (default) disables shadow DOM support.
105+
- `function`:
106+
- `node` will be a descendent of the `rootNode` given to `tabbable()`, `isTabbable()`, `focusable()`, or `isFocusable()`.
107+
- Returns: The node's `ShadowRoot` if available, `true` indicating a `ShadowRoot` is attached but not available (i.e. "undisclosed"), or a _falsy_ value indicating there is no shadow attached to the node.
108+
109+
> If set to a function, and if it returns `true`, Tabbable assumes a closed `ShadowRoot` is attached and will treat the node as a scope, iterating its children for additional tabbable/focusable candidates as though it was looking inside the shadow, but not. This will get tabbing order _closer_ to -- but not necessarily the same as -- browser order.
110+
>
111+
> Returning `true` from a function will also inform how the node's visibility check is done, causing tabbable to use the __non-zero-area__ [Display Check](#display-check) when determining if it's visible, and so tabbable/focusable.
112+
94113
### isTabbable
95114

96115
```js
@@ -173,8 +192,8 @@ To reliably check if an element is tabbable/focusable, Tabbable defaults to the
173192

174193
The `displayCheck` configuration accepts the following options:
175194

176-
- `full`: (default) Most reliably resemble browser behavior, this option checks that an element is displayed and all of his ancestors are displayed as well (Notice that this doesn't exclude `visibility: hidden` or elements with zero size). This check is by far the slowest option as it might cause layout reflow.
177-
- `non-zero-area`: This option checks display under the assumption that elements that are not displayed have zero area (width AND height equals zero). While not keeping true to browser behavior, this option is much less intensive then the `full` option and better for accessibility as zero-size elements with focusable content are considered a strong accessibility anti-pattern.
195+
- `full`: (default) Most reliably resembling browser behavior, this option checks that an element is displayed and all of his ancestors are displayed as well (notice that this doesn't exclude `visibility: hidden` or elements with zero size). This check is by far the slowest option as it will cause layout reflow.
196+
- `non-zero-area`: This option checks display under the assumption that elements that are not displayed have zero area (width AND height equals zero). While not keeping true to browser behavior, this option may be less intensive than the `full` option, and better for accessibility, as zero-size elements with focusable content are considered a strong accessibility anti-pattern.
178197
- `none`: This completely opts out of the display check. **This option is not recommended**, as it might return elements that are not displayed, and as such not tabbable/focusable and can break accessibility. Make sure you know which elements in your DOM are not displayed and can filter them out yourself before using this option.
179198

180199
**_Feedback and contributions more than welcome!_**

cypress/plugins/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ module.exports = (on, config) => {
2626
require('@cypress/code-coverage/use-browserify-istanbul')
2727
);
2828
}
29+
2930
// fetch fixtures
3031
on('task', {
3132
getFixtures() {
3233
return require('../../test/fixtures/index');
3334
},
3435
});
36+
3537
// IMPORTANT to return the config object
3638
// with the any changed environment variables
3739
return config;

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ type FocusableElement = HTMLElement | SVGElement;
22

33
export type CheckOptions = {
44
displayCheck?: 'full' | 'non-zero-area' | 'none';
5+
getShadowRoot?: boolean | ((node: FocusableElement) => ShadowRoot | boolean | undefined);
56
};
67

78
export type TabbableOptions = {

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tabbable",
3-
"version": "5.2.1",
3+
"version": "5.3.0-beta.1",
44
"description": "Returns an array of all tabbable DOM nodes within a containing node.",
55
"main": "dist/index.js",
66
"module": "dist/index.esm.js",
@@ -31,10 +31,11 @@
3131
"test": "yarn format:check && yarn lint && yarn test:types && yarn test:unit && yarn test:e2e",
3232
"test:types": "tsc index.d.ts",
3333
"test:unit": "jest",
34-
"test:e2e": "cypress run",
34+
"test:e2e": "ELECTRON_ENABLE_LOGGING=1 cypress run",
3535
"test:e2e:dev": "cypress open",
3636
"test:coverage": "cypress run --env coverage=true",
3737
"prepare": "yarn build",
38+
"prepublishOnly": "yarn test && yarn build",
3839
"release": "yarn build && changeset publish"
3940
},
4041
"repository": {

0 commit comments

Comments
 (0)