focus-trap 6.3.0 → 6.6.0
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 +57 -0
- package/README.md +53 -47
- package/dist/focus-trap.esm.js +114 -33
- package/dist/focus-trap.esm.js.map +1 -1
- package/dist/focus-trap.esm.min.js +2 -2
- package/dist/focus-trap.esm.min.js.map +1 -1
- package/dist/focus-trap.js +114 -33
- package/dist/focus-trap.js.map +1 -1
- package/dist/focus-trap.min.js +2 -2
- package/dist/focus-trap.min.js.map +1 -1
- package/dist/focus-trap.umd.js +114 -33
- package/dist/focus-trap.umd.js.map +1 -1
- package/dist/focus-trap.umd.min.js +2 -2
- package/dist/focus-trap.umd.min.js.map +1 -1
- package/index.d.ts +62 -13
- package/index.js +117 -24
- package/package.json +32 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 281e66c: Add option to allow no initial focus when trap activates via `initialFocus: false`
|
|
8
|
+
|
|
9
|
+
There may be cases where we don't want to focus the first tabbable element when a focus trap activates.
|
|
10
|
+
|
|
11
|
+
Examples use-cases:
|
|
12
|
+
|
|
13
|
+
- Modals/dialogs
|
|
14
|
+
- On mobile devices where "tabbing" doesn't make sense without a connected Bluetooth keyboard
|
|
15
|
+
|
|
16
|
+
In addition, this change ensures that any element inside the trap manually focused outside of `focus-trap` code will be brought back in focus if focus is somehow found outside of the trap.
|
|
17
|
+
|
|
18
|
+
Example usage:
|
|
19
|
+
|
|
20
|
+
When the trap activates, there will be no initially focused element inside the new trap.
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const focusTrap = createFocusTrap('#some-container', {
|
|
24
|
+
initialFocus: false,
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- 75be463: `escapeDeactivates` can now be either a boolean (as before) or a function that takes an event and returns a boolean.
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- e2294f0: Fix race condition when activating a second trap where initial focus in the second trap may be thwarted because pausing of first trap clears the `delayInitialFocus` timer created for the second trap before during its activation sequence.
|
|
33
|
+
|
|
34
|
+
## 6.5.1
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- c38bf3f: onPostDeactivate should always be called even if returnFocus/OnDeactivate is disabled.
|
|
39
|
+
|
|
40
|
+
## 6.5.0
|
|
41
|
+
|
|
42
|
+
### Minor Changes
|
|
43
|
+
|
|
44
|
+
- 278e77e: Adding 4 new configuration event options to improve support for animated dialogs and animated focus trap triggers: `checkCanFocusTrap()`, `onPostActivate()`, `checkCanReturnFocus()`, and `onPostDeactivate()`.
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- 8d11e15: Improve docs and types for most options, adding `SVGElement` as a supported type of "DOM node" since it supports the `focus()` method, same as `HTMLElement`.
|
|
49
|
+
|
|
50
|
+
## 6.4.0
|
|
51
|
+
|
|
52
|
+
### Minor Changes
|
|
53
|
+
|
|
54
|
+
- 21c82ce: Bump tabbable from 5.1.6 to 5.2.0. There should be no changes in behavior as a result of this upgrade as `focus-trap` does not currently leverage the new `displayCheck` option.
|
|
55
|
+
|
|
56
|
+
### Patch Changes
|
|
57
|
+
|
|
58
|
+
- 1baf62e: Fix focus trapped on initial focus container with tabindex=-1 when pressing shift+tab (#363)
|
|
59
|
+
|
|
3
60
|
## 6.3.0
|
|
4
61
|
|
|
5
62
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# focus-trap [](https://github.com/focus-trap/focus-trap/actions?query=workflow:CI+branch:master) [](./LICENSE)
|
|
2
2
|
|
|
3
3
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
4
|
-
[](#contributors)
|
|
5
5
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
6
6
|
|
|
7
7
|
Trap focus within a DOM node.
|
|
@@ -28,7 +28,7 @@ When the focus trap is deactivated, this is what should happen:
|
|
|
28
28
|
- Focus is passed to *whichever element had focus when the trap was activated* (e.g. the button that opened the modal or menu).
|
|
29
29
|
- Tabbing and clicking behave normally everywhere.
|
|
30
30
|
|
|
31
|
-
[Check out the demos.](http://focus-trap.github.io/focus-trap/
|
|
31
|
+
[Check out the demos.](http://focus-trap.github.io/focus-trap/)
|
|
32
32
|
|
|
33
33
|
For more advanced usage (e.g. focus traps within focus traps), you can also pause a focus trap's behavior without deactivating it entirely, then unpause at will.
|
|
34
34
|
|
|
@@ -80,20 +80,25 @@ Returns a new focus trap on `element` (one or more "containers" of tabbable node
|
|
|
80
80
|
|
|
81
81
|
> A focus trap must have at least one container with at least one tabbable/focusable node in it to be considered valid. While nodes can be added/removed at runtime, with the trap adjusting to added/removed tabbable nodes, __an error will be thrown__ if the trap ever gets into a state where it determines none of its containers have any tabbable nodes in them _and_ the `fallbackFocus` option does not resolve to an alternate node where focus can go.
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
- **onActivate** {
|
|
86
|
-
- **
|
|
87
|
-
- **
|
|
88
|
-
- **
|
|
89
|
-
- **
|
|
90
|
-
- **
|
|
83
|
+
#### createOptions
|
|
84
|
+
|
|
85
|
+
- **onActivate** `{() => void}`: A function that will be called **before** sending focus to the target element upon activation.
|
|
86
|
+
- **onPostActivate** `{() => void}`: A function that will be called **after** sending focus to the target element upon activation.
|
|
87
|
+
- **checkCanFocusTrap** `{(containers: Array<HTMLElement | SVGElement>) => Promise<void>}`: Animated dialogs have a small delay between when `onActivate` is called and when the focus trap is focusable. `checkCanFocusTrap` expects a promise to be returned. When that promise settles (resolves or rejects), focus will be sent to the first tabbable node (in tab order) in the focus trap (or the node configured in the `initialFocus` option). Due to the lack of Promise support, `checkCanFocusTrap` is not supported in IE unless you provide a Promise polyfill.
|
|
88
|
+
- **onDeactivate** `{() => void}`: A function that will be called **before** returning focus to the node that had focus prior to activation (or configured with the `setReturnFocus` option) upon deactivation.
|
|
89
|
+
- **onPostDeactivate** `{() => void}`: A function that will be called after the trap is deactivated, after `onDeactivate`. If the `returnFocus` deactivation option was set, it will be called **after** returning focus to the node that had focus prior to activation (or configured with the `setReturnFocus` option) upon deactivation; otherwise, it will be called after deactivation completes.
|
|
90
|
+
- **checkCanReturnFocus** `{(trigger: HTMLElement | SVGElement) => Promise<void>}`: An animated trigger button will have a small delay between when `onDeactivate` is called and when the focus is able to be sent back to the trigger. `checkCanReturnFocus` expects a promise to be returned. When that promise settles (resolves or rejects), focus will be sent to to the node that had focus prior to the activation of the trap (or the node configured in the `setReturnFocus` option). Due to the lack of Promise support, `checkCanReturnFocus` is not supported in IE unless you provide a Promise polyfill.
|
|
91
|
+
- **initialFocus** `{HTMLElement | SVGElement | string | () => HTMLElement | SVGElement | false}`: By default, when a focus trap is activated the first element in the focus trap's tab order will receive focus. With this option you can specify a different element to receive that initial focus. Can be a DOM node, or a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns a DOM node. You can also set this option to `false` to prevent any initial focus at all when the trap activates.
|
|
92
|
+
- **fallbackFocus** `{HTMLElement | SVGElement | string | () => HTMLElement | SVGElement}`: By default, an error will be thrown if the focus trap contains no elements in its tab order. With this option you can specify a fallback element to programmatically receive focus if no other tabbable elements are found. For example, you may want a popover's `<div>` to receive focus if the popover's content includes no tabbable elements. *Make sure the fallback element has a negative `tabindex` so it can be programmatically focused.* The option value can be a DOM node, a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns a DOM node.
|
|
93
|
+
- **escapeDeactivates** `{boolean} | (e: KeyboardEvent) => boolean)`: Default: `true`. If `false` or returns `false`, the `Escape` key will not trigger deactivation of the focus trap. This can be useful if you want to force the user to make a decision instead of allowing an easy way out. Note that if a function is given, it's only called if the ESC key was pressed.
|
|
94
|
+
- **clickOutsideDeactivates** `{boolean | (e: MouseEvent | TouchEvent) => boolean}`: If `true` or returns `true`, a click outside the focus trap will deactivate the focus trap and allow the click event to do its thing (i.e. to pass-through to the element that was clicked). This option **takes precedence** over `allowOutsideClick` when it's set to `true`. Default: `false`.
|
|
91
95
|
- ⚠️ If you're using a password manager such as 1Password, where the app adds a clickable icon to all fillable fields, you should avoid using this option, and instead use the `allowOutsideClick` option to better control exactly when the focus trap can be deactivated. The clickable icons are usually positioned absolutely, floating on top of the fields, and therefore _not_ part of the container the trap is managing. When using the `clickOutsideDeactivates` option, clicking on a field's 1Password icon will likely cause the trap to be unintentionally deactivated.
|
|
92
|
-
- **allowOutsideClick** {boolean|(e: MouseEvent) => boolean}
|
|
93
|
-
- **
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
96
|
-
- **
|
|
96
|
+
- **allowOutsideClick** `{boolean | (e: MouseEvent | TouchEvent) => boolean}`: If set and is or returns `true`, a click outside the focus trap will not be prevented, even when `clickOutsideDeactivates` is `false`. When `clickOutsideDeactivates` is `true`, this option is **ignored** (i.e. if it's a function, it will not be called). Use this option to control if (and even which) clicks are allowed outside the trap in conjunction with `clickOutsideDeactivates: false`. Default: `false`.
|
|
97
|
+
- ⚠️ If this is a function, it will be called **twice** on every click: First on `mousedown` (or `touchstart` on mobile), and then on the actual `click` if the function returned `true` on the first event. Be sure to check the event type if the double call is an issue in your code.
|
|
98
|
+
- **returnFocusOnDeactivate** `{boolean}`: Default: `true`. If `false`, when the trap is deactivated, focus will *not* return to the element that had focus before activation.
|
|
99
|
+
- **setReturnFocus** `{HTMLElement | SVGElement | string | () => HTMLElement | SVGElement}`: By default, focus trap on deactivation will return to the element that was focused before activation. With this option you can specify another element to programmatically receive focus after deactivation. Can be a DOM node, or a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns a DOM node.
|
|
100
|
+
- **preventScroll** `{boolean}`: By default, focus() will scroll to the element if not in viewport. It can produce unintended effects like scrolling back to the top of a modal. If set to `true`, no scroll will happen.
|
|
101
|
+
- **delayInitialFocus** `{boolean}`: Default: `true`. Delays the autofocus to the next execution frame when the focus trap is activated. This prevents elements within the focusable element from capturing the event that triggered the focus trap activation.
|
|
97
102
|
|
|
98
103
|
### trap.activate([activateOptions])
|
|
99
104
|
|
|
@@ -113,7 +118,9 @@ Returns the `trap`.
|
|
|
113
118
|
|
|
114
119
|
These options are used to override the focus trap's default behavior for this particular activation.
|
|
115
120
|
|
|
116
|
-
- **onActivate** {
|
|
121
|
+
- **onActivate** `{() => void}`: Default: whatever you chose for `createOptions.onActivate`. `null` or `false` are the equivalent of a `noop`.
|
|
122
|
+
- **onPostActivate** `{() => void}`: Default: whatever you chose for `createOptions.onPostActivate`. `null` or `false` are the equivalent of a `noop`.
|
|
123
|
+
- **checkCanFocusTrap** `{(containers: Array<HTMLElement | SVGElement>) => Promise<void>}`: Default: whatever you chose for `createOptions.checkCanFocusTrap`.
|
|
117
124
|
|
|
118
125
|
### trap.deactivate([deactivateOptions])
|
|
119
126
|
|
|
@@ -125,8 +132,10 @@ Returns the `trap`.
|
|
|
125
132
|
|
|
126
133
|
These options are used to override the focus trap's default behavior for this particular deactivation.
|
|
127
134
|
|
|
128
|
-
- **returnFocus** {boolean}
|
|
129
|
-
- **onDeactivate** {
|
|
135
|
+
- **returnFocus** `{boolean}`: Default: whatever you chose for `createOptions.returnFocusOnDeactivate`.
|
|
136
|
+
- **onDeactivate** `{() => void}`: Default: whatever you chose for `createOptions.onDeactivate`. `null` or `false` are the equivalent of a `noop`.
|
|
137
|
+
- **onPostDeactivate** `{() => void}`: Default: whatever you chose for `createOptions.onPostDeactivate`. `null` or `false` are the equivalent of a `noop`.
|
|
138
|
+
- **checkCanReturnFocus** `{(trigger: HTMLElement | SVGElement) => Promise<void>}`: Default: whatever you chose for `createOptions.checkCanReturnFocus`. Not called if the `returnFocus` option is falsy. `trigger` is either the originally focused node prior to activation, or the result of the `setReturnFocus` configuration option.
|
|
130
139
|
|
|
131
140
|
### trap.pause()
|
|
132
141
|
|
|
@@ -162,7 +171,7 @@ Returns the `trap`.
|
|
|
162
171
|
|
|
163
172
|
## Examples
|
|
164
173
|
|
|
165
|
-
Read code in `
|
|
174
|
+
Read code in `docs/` and [see how it works](http://focus-trap.github.io/focus-trap/).
|
|
166
175
|
|
|
167
176
|
Here's what happens in `default.js` (the "default behavior" demo):
|
|
168
177
|
|
|
@@ -172,25 +181,16 @@ const { createFocusTrap } = require('../../dist/focus-trap');
|
|
|
172
181
|
const container = document.getElementById('default');
|
|
173
182
|
|
|
174
183
|
const focusTrap = createFocusTrap('#default', {
|
|
175
|
-
onActivate:
|
|
176
|
-
|
|
177
|
-
},
|
|
178
|
-
onDeactivate: function () {
|
|
179
|
-
container.className = 'trap';
|
|
180
|
-
},
|
|
184
|
+
onActivate: () => container.classList.add('is-active'),
|
|
185
|
+
onDeactivate: () => container.classList.remove('is-active'),
|
|
181
186
|
});
|
|
182
187
|
|
|
183
188
|
document
|
|
184
189
|
.getElementById('activate-default')
|
|
185
|
-
.addEventListener('click',
|
|
186
|
-
focusTrap.activate();
|
|
187
|
-
});
|
|
188
|
-
|
|
190
|
+
.addEventListener('click', focusTrap.activate);
|
|
189
191
|
document
|
|
190
192
|
.getElementById('deactivate-default')
|
|
191
|
-
.addEventListener('click',
|
|
192
|
-
focusTrap.deactivate();
|
|
193
|
-
});
|
|
193
|
+
.addEventListener('click', focusTrap.deactivate);
|
|
194
194
|
```
|
|
195
195
|
|
|
196
196
|
## Other details
|
|
@@ -232,25 +232,31 @@ In alphabetical order:
|
|
|
232
232
|
<!-- markdownlint-disable -->
|
|
233
233
|
<table>
|
|
234
234
|
<tr>
|
|
235
|
-
<td align="center"><a href="
|
|
236
|
-
<td align="center"><a href="https://
|
|
237
|
-
<td align="center"><a href="https://github.com/
|
|
238
|
-
<td align="center"><a href="
|
|
239
|
-
<td align="center"><a href="https://github.com/
|
|
240
|
-
<td align="center"><a href="https://
|
|
241
|
-
<td align="center"><a href="https://github.com/
|
|
235
|
+
<td align="center"><a href="https://github.com/bparish628"><img src="https://avatars1.githubusercontent.com/u/8492971?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Benjamin Parish</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Abparish628" title="Bug reports">🐛</a></td>
|
|
236
|
+
<td align="center"><a href="https://clintgoodman.com"><img src="https://avatars3.githubusercontent.com/u/5473697?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Clint Goodman</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=cgood92" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=cgood92" title="Documentation">📖</a> <a href="#example-cgood92" title="Examples">💡</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=cgood92" title="Tests">⚠️</a></td>
|
|
237
|
+
<td align="center"><a href="https://github.com/Dan503"><img src="https://avatars.githubusercontent.com/u/10610368?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Tonon</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=Dan503" title="Documentation">📖</a> <a href="#tool-Dan503" title="Tools">🔧</a> <a href="#a11y-Dan503" title="Accessibility">️️️️♿️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=Dan503" title="Code">💻</a></td>
|
|
238
|
+
<td align="center"><a href="http://davidtheclark.com/"><img src="https://avatars2.githubusercontent.com/u/628431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Clark</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Adavidtheclark" title="Bug reports">🐛</a> <a href="#infra-davidtheclark" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Documentation">📖</a> <a href="#maintenance-davidtheclark" title="Maintenance">🚧</a></td>
|
|
239
|
+
<td align="center"><a href="https://github.com/features/security"><img src="https://avatars1.githubusercontent.com/u/27347476?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dependabot</b></sub></a><br /><a href="#maintenance-dependabot" title="Maintenance">🚧</a></td>
|
|
240
|
+
<td align="center"><a href="https://github.com/michael-ar"><img src="https://avatars3.githubusercontent.com/u/18557997?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Reynolds</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Amichael-ar" title="Bug reports">🐛</a></td>
|
|
241
|
+
<td align="center"><a href="https://github.com/liunate"><img src="https://avatars2.githubusercontent.com/u/38996291?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nate Liu</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=liunate" title="Tests">⚠️</a></td>
|
|
242
242
|
</tr>
|
|
243
243
|
<tr>
|
|
244
|
-
<td align="center"><a href="https://
|
|
245
|
-
<td align="center"><a href="https://github.com/
|
|
246
|
-
<td align="center"><a href="https://
|
|
247
|
-
<td align="center"><a href="https://
|
|
248
|
-
<td align="center"><a href="https://
|
|
249
|
-
<td align="center"><a href="https://
|
|
250
|
-
<td align="center"><a href="
|
|
244
|
+
<td align="center"><a href="https://github.com/far-fetched"><img src="https://avatars.githubusercontent.com/u/11621383?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotr Panek</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Afar-fetched" title="Bug reports">🐛</a></td>
|
|
245
|
+
<td align="center"><a href="https://github.com/randypuro"><img src="https://avatars2.githubusercontent.com/u/2579?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Randy Puro</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Arandypuro" title="Bug reports">🐛</a></td>
|
|
246
|
+
<td align="center"><a href="https://github.com/sadick254"><img src="https://avatars2.githubusercontent.com/u/5238135?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sadick</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=sadick254" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=sadick254" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=sadick254" title="Documentation">📖</a></td>
|
|
247
|
+
<td align="center"><a href="https://scottblinch.me/"><img src="https://avatars2.githubusercontent.com/u/4682114?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Scott Blinch</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=scottblinch" title="Documentation">📖</a></td>
|
|
248
|
+
<td align="center"><a href="https://seanmcp.com/"><img src="https://avatars1.githubusercontent.com/u/6360367?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sean McPherson</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=SeanMcP" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=SeanMcP" title="Documentation">📖</a></td>
|
|
249
|
+
<td align="center"><a href="https://recollectr.io"><img src="https://avatars2.githubusercontent.com/u/6835891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Slapbox</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3ASlapbox" title="Bug reports">🐛</a></td>
|
|
250
|
+
<td align="center"><a href="https://stefancameron.com/"><img src="https://avatars3.githubusercontent.com/u/2855350?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Cameron</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=stefcameron" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Astefcameron" title="Bug reports">🐛</a> <a href="#infra-stefcameron" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=stefcameron" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=stefcameron" title="Documentation">📖</a> <a href="#maintenance-stefcameron" title="Maintenance">🚧</a></td>
|
|
251
|
+
</tr>
|
|
252
|
+
<tr>
|
|
253
|
+
<td align="center"><a href="http://tylerhawkins.info/201R/"><img src="https://avatars0.githubusercontent.com/u/13806458?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tyler Hawkins</b></sub></a><br /><a href="#tool-thawkin3" title="Tools">🔧</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=thawkin3" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=thawkin3" title="Documentation">📖</a></td>
|
|
254
|
+
<td align="center"><a href="http://willmruzek.com/"><img src="https://avatars.githubusercontent.com/u/108522?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Will Mruzek</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=mruzekw" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=mruzekw" title="Documentation">📖</a> <a href="#example-mruzekw" title="Examples">💡</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=mruzekw" title="Tests">⚠️</a> <a href="#question-mruzekw" title="Answering Questions">💬</a></td>
|
|
255
|
+
<td align="center"><a href="https://github.com/zioth"><img src="https://avatars3.githubusercontent.com/u/945603?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zioth</b></sub></a><br /><a href="#ideas-zioth" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Azioth" title="Bug reports">🐛</a></td>
|
|
251
256
|
</tr>
|
|
252
257
|
</table>
|
|
253
258
|
|
|
254
|
-
<!-- markdownlint-
|
|
259
|
+
<!-- markdownlint-restore -->
|
|
255
260
|
<!-- prettier-ignore-end -->
|
|
261
|
+
|
|
256
262
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
package/dist/focus-trap.esm.js
CHANGED
|
@@ -1,32 +1,21 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* focus-trap 6.
|
|
2
|
+
* focus-trap 6.6.0
|
|
3
3
|
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
|
|
4
4
|
*/
|
|
5
5
|
import { tabbable, isFocusable } from 'tabbable';
|
|
6
6
|
|
|
7
|
-
function _defineProperty(obj, key, value) {
|
|
8
|
-
if (key in obj) {
|
|
9
|
-
Object.defineProperty(obj, key, {
|
|
10
|
-
value: value,
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
writable: true
|
|
14
|
-
});
|
|
15
|
-
} else {
|
|
16
|
-
obj[key] = value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return obj;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
7
|
function ownKeys(object, enumerableOnly) {
|
|
23
8
|
var keys = Object.keys(object);
|
|
24
9
|
|
|
25
10
|
if (Object.getOwnPropertySymbols) {
|
|
26
11
|
var symbols = Object.getOwnPropertySymbols(object);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
12
|
+
|
|
13
|
+
if (enumerableOnly) {
|
|
14
|
+
symbols = symbols.filter(function (sym) {
|
|
15
|
+
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
30
19
|
keys.push.apply(keys, symbols);
|
|
31
20
|
}
|
|
32
21
|
|
|
@@ -53,7 +42,20 @@ function _objectSpread2(target) {
|
|
|
53
42
|
return target;
|
|
54
43
|
}
|
|
55
44
|
|
|
56
|
-
|
|
45
|
+
function _defineProperty(obj, key, value) {
|
|
46
|
+
if (key in obj) {
|
|
47
|
+
Object.defineProperty(obj, key, {
|
|
48
|
+
value: value,
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
writable: true
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
obj[key] = value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return obj;
|
|
58
|
+
}
|
|
57
59
|
|
|
58
60
|
var activeFocusTraps = function () {
|
|
59
61
|
var trapQueue = [];
|
|
@@ -161,10 +163,17 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
161
163
|
nodeFocusedBeforeActivation: null,
|
|
162
164
|
mostRecentlyFocusedNode: null,
|
|
163
165
|
active: false,
|
|
164
|
-
paused: false
|
|
166
|
+
paused: false,
|
|
167
|
+
// timer ID for when delayInitialFocus is true and initial focus in this trap
|
|
168
|
+
// has been delayed during activation
|
|
169
|
+
delayInitialFocusTimer: undefined
|
|
165
170
|
};
|
|
166
171
|
var trap; // eslint-disable-line prefer-const -- some private functions reference it, and its methods reference private functions, so we must declare here and define later
|
|
167
172
|
|
|
173
|
+
var getOption = function getOption(configOverrideOptions, optionName, configOptionName) {
|
|
174
|
+
return configOverrideOptions && configOverrideOptions[optionName] !== undefined ? configOverrideOptions[optionName] : config[configOptionName || optionName];
|
|
175
|
+
};
|
|
176
|
+
|
|
168
177
|
var containersContain = function containersContain(element) {
|
|
169
178
|
return state.containers.some(function (container) {
|
|
170
179
|
return container.contains(element);
|
|
@@ -200,7 +209,11 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
200
209
|
};
|
|
201
210
|
|
|
202
211
|
var getInitialFocusNode = function getInitialFocusNode() {
|
|
203
|
-
var node;
|
|
212
|
+
var node; // false indicates we want no initialFocus at all
|
|
213
|
+
|
|
214
|
+
if (getOption({}, 'initialFocus') === false) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
204
217
|
|
|
205
218
|
if (getNodeForOption('initialFocus') !== null) {
|
|
206
219
|
node = getNodeForOption('initialFocus');
|
|
@@ -243,6 +256,10 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
243
256
|
};
|
|
244
257
|
|
|
245
258
|
var tryFocus = function tryFocus(node) {
|
|
259
|
+
if (node === false) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
246
263
|
if (node === doc.activeElement) {
|
|
247
264
|
return;
|
|
248
265
|
}
|
|
@@ -331,6 +348,8 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
331
348
|
|
|
332
349
|
if (state.tabbableGroups.length > 0) {
|
|
333
350
|
// make sure the target is actually contained in a group
|
|
351
|
+
// NOTE: the target may also be the container itself if it's tabbable
|
|
352
|
+
// with tabIndex='-1' and was given initial focus
|
|
334
353
|
var containerIndex = findIndex(state.tabbableGroups, function (_ref) {
|
|
335
354
|
var container = _ref.container;
|
|
336
355
|
return container.contains(e.target);
|
|
@@ -348,24 +367,46 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
348
367
|
}
|
|
349
368
|
} else if (e.shiftKey) {
|
|
350
369
|
// REVERSE
|
|
370
|
+
// is the target the first tabbable node in a group?
|
|
351
371
|
var startOfGroupIndex = findIndex(state.tabbableGroups, function (_ref2) {
|
|
352
372
|
var firstTabbableNode = _ref2.firstTabbableNode;
|
|
353
373
|
return e.target === firstTabbableNode;
|
|
354
374
|
});
|
|
355
375
|
|
|
376
|
+
if (startOfGroupIndex < 0 && state.tabbableGroups[containerIndex].container === e.target) {
|
|
377
|
+
// an exception case where the target is the container itself, in which
|
|
378
|
+
// case, we should handle shift+tab as if focus were on the container's
|
|
379
|
+
// first tabbable node, and go to the last tabbable node of the LAST group
|
|
380
|
+
startOfGroupIndex = containerIndex;
|
|
381
|
+
}
|
|
382
|
+
|
|
356
383
|
if (startOfGroupIndex >= 0) {
|
|
384
|
+
// YES: then shift+tab should go to the last tabbable node in the
|
|
385
|
+
// previous group (and wrap around to the last tabbable node of
|
|
386
|
+
// the LAST group if it's the first tabbable node of the FIRST group)
|
|
357
387
|
var destinationGroupIndex = startOfGroupIndex === 0 ? state.tabbableGroups.length - 1 : startOfGroupIndex - 1;
|
|
358
388
|
var destinationGroup = state.tabbableGroups[destinationGroupIndex];
|
|
359
389
|
destinationNode = destinationGroup.lastTabbableNode;
|
|
360
390
|
}
|
|
361
391
|
} else {
|
|
362
392
|
// FORWARD
|
|
393
|
+
// is the target the last tabbable node in a group?
|
|
363
394
|
var lastOfGroupIndex = findIndex(state.tabbableGroups, function (_ref3) {
|
|
364
395
|
var lastTabbableNode = _ref3.lastTabbableNode;
|
|
365
396
|
return e.target === lastTabbableNode;
|
|
366
397
|
});
|
|
367
398
|
|
|
399
|
+
if (lastOfGroupIndex < 0 && state.tabbableGroups[containerIndex].container === e.target) {
|
|
400
|
+
// an exception case where the target is the container itself, in which
|
|
401
|
+
// case, we should handle tab as if focus were on the container's
|
|
402
|
+
// last tabbable node, and go to the first tabbable node of the FIRST group
|
|
403
|
+
lastOfGroupIndex = containerIndex;
|
|
404
|
+
}
|
|
405
|
+
|
|
368
406
|
if (lastOfGroupIndex >= 0) {
|
|
407
|
+
// YES: then tab should go to the first tabbable node in the next
|
|
408
|
+
// group (and wrap around to the first tabbable node of the FIRST
|
|
409
|
+
// group if it's the last tabbable node of the LAST group)
|
|
369
410
|
var _destinationGroupIndex = lastOfGroupIndex === state.tabbableGroups.length - 1 ? 0 : lastOfGroupIndex + 1;
|
|
370
411
|
|
|
371
412
|
var _destinationGroup = state.tabbableGroups[_destinationGroupIndex];
|
|
@@ -379,11 +420,12 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
379
420
|
if (destinationNode) {
|
|
380
421
|
e.preventDefault();
|
|
381
422
|
tryFocus(destinationNode);
|
|
382
|
-
}
|
|
423
|
+
} // else, let the browser take care of [shift+]tab and move the focus
|
|
424
|
+
|
|
383
425
|
};
|
|
384
426
|
|
|
385
427
|
var checkKey = function checkKey(e) {
|
|
386
|
-
if (config.escapeDeactivates !== false
|
|
428
|
+
if (isEscapeEvent(e) && valueOrHandler(config.escapeDeactivates) !== false) {
|
|
387
429
|
e.preventDefault();
|
|
388
430
|
trap.deactivate();
|
|
389
431
|
return;
|
|
@@ -424,7 +466,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
424
466
|
activeFocusTraps.activateTrap(trap); // Delay ensures that the focused element doesn't capture the event
|
|
425
467
|
// that caused the focus trap activation.
|
|
426
468
|
|
|
427
|
-
|
|
469
|
+
state.delayInitialFocusTimer = config.delayInitialFocus ? delay(function () {
|
|
428
470
|
tryFocus(getInitialFocusNode());
|
|
429
471
|
}) : tryFocus(getInitialFocusNode());
|
|
430
472
|
doc.addEventListener('focusin', checkFocusIn, true);
|
|
@@ -469,17 +511,40 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
469
511
|
return this;
|
|
470
512
|
}
|
|
471
513
|
|
|
472
|
-
|
|
514
|
+
var onActivate = getOption(activateOptions, 'onActivate');
|
|
515
|
+
var onPostActivate = getOption(activateOptions, 'onPostActivate');
|
|
516
|
+
var checkCanFocusTrap = getOption(activateOptions, 'checkCanFocusTrap');
|
|
517
|
+
|
|
518
|
+
if (!checkCanFocusTrap) {
|
|
519
|
+
updateTabbableNodes();
|
|
520
|
+
}
|
|
521
|
+
|
|
473
522
|
state.active = true;
|
|
474
523
|
state.paused = false;
|
|
475
524
|
state.nodeFocusedBeforeActivation = doc.activeElement;
|
|
476
|
-
var onActivate = activateOptions && activateOptions.onActivate ? activateOptions.onActivate : config.onActivate;
|
|
477
525
|
|
|
478
526
|
if (onActivate) {
|
|
479
527
|
onActivate();
|
|
480
528
|
}
|
|
481
529
|
|
|
482
|
-
|
|
530
|
+
var finishActivation = function finishActivation() {
|
|
531
|
+
if (checkCanFocusTrap) {
|
|
532
|
+
updateTabbableNodes();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
addListeners();
|
|
536
|
+
|
|
537
|
+
if (onPostActivate) {
|
|
538
|
+
onPostActivate();
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
if (checkCanFocusTrap) {
|
|
543
|
+
checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
|
|
544
|
+
return this;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
finishActivation();
|
|
483
548
|
return this;
|
|
484
549
|
},
|
|
485
550
|
deactivate: function deactivate(deactivateOptions) {
|
|
@@ -487,25 +552,41 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
487
552
|
return this;
|
|
488
553
|
}
|
|
489
554
|
|
|
490
|
-
clearTimeout(
|
|
555
|
+
clearTimeout(state.delayInitialFocusTimer); // noop if undefined
|
|
556
|
+
|
|
557
|
+
state.delayInitialFocusTimer = undefined;
|
|
491
558
|
removeListeners();
|
|
492
559
|
state.active = false;
|
|
493
560
|
state.paused = false;
|
|
494
561
|
activeFocusTraps.deactivateTrap(trap);
|
|
495
|
-
var onDeactivate = deactivateOptions
|
|
562
|
+
var onDeactivate = getOption(deactivateOptions, 'onDeactivate');
|
|
563
|
+
var onPostDeactivate = getOption(deactivateOptions, 'onPostDeactivate');
|
|
564
|
+
var checkCanReturnFocus = getOption(deactivateOptions, 'checkCanReturnFocus');
|
|
496
565
|
|
|
497
566
|
if (onDeactivate) {
|
|
498
567
|
onDeactivate();
|
|
499
568
|
}
|
|
500
569
|
|
|
501
|
-
var returnFocus = deactivateOptions
|
|
570
|
+
var returnFocus = getOption(deactivateOptions, 'returnFocus', 'returnFocusOnDeactivate');
|
|
502
571
|
|
|
503
|
-
|
|
572
|
+
var finishDeactivation = function finishDeactivation() {
|
|
504
573
|
delay(function () {
|
|
505
|
-
|
|
574
|
+
if (returnFocus) {
|
|
575
|
+
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (onPostDeactivate) {
|
|
579
|
+
onPostDeactivate();
|
|
580
|
+
}
|
|
506
581
|
});
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
if (returnFocus && checkCanReturnFocus) {
|
|
585
|
+
checkCanReturnFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)).then(finishDeactivation, finishDeactivation);
|
|
586
|
+
return this;
|
|
507
587
|
}
|
|
508
588
|
|
|
589
|
+
finishDeactivation();
|
|
509
590
|
return this;
|
|
510
591
|
},
|
|
511
592
|
pause: function pause() {
|