Skip to content

Commit 544086d

Browse files
committed
built-in focusTrap
1 parent 847300f commit 544086d

18 files changed

+166
-116
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ const CLASS_TYPES = [String, Object, Array]
249249
transition: { type: String, default: 'vfm' },
250250
overlayTransition: { type: String, default: 'vfm' },
251251
zIndexBase: { type: [String, Number], default: 1000 },
252-
zIndex: { type: [Boolean, String, Number], default: false }
252+
zIndex: { type: [Boolean, String, Number], default: false },
253+
focusTrap: { type: Boolean, default: false }
253254
}
254255
```
255256

dist/VueFinalModal.esm.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/VueFinalModal.esm.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/VueFinalModal.umd.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/VueFinalModal.umd.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/content/en/examples.md

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -603,53 +603,19 @@ export default {
603603

604604
### **Accessibility**
605605

606-
Using [focus-trap-vue](https://github.com/posva/focus-trap-vue) can easily support Accessibility.
607-
608-
**1. Install**
609-
610-
<code-group>
611-
<code-block label="Yarn" active>
612-
613-
```bash
614-
yarn add focus-trap focus-trap-vue
615-
```
616-
617-
</code-block>
618-
<code-block label="NPM">
619-
620-
```bash
621-
npm install focus-trap focus-trap-vue
622-
```
623-
624-
</code-block>
625-
</code-group>
626-
627-
**2. Register component**
628-
629-
```js[main.js]
630-
import { FocusTrap } from 'focus-trap-vue'
631-
Vue.component('FocusTrap', FocusTrap)
632-
```
633-
634-
**3. Wrap the modal component**
635606

636607
<v-accessibility></v-accessibility>
637608

638609
<show-code open class="mt-2">
639610

640611
```html
641612
<template>
642-
<focus-trap :value="value">
643613
<vue-final-modal
644-
:value="value"
645-
v-bind="$attrs"
646-
classes="modal-container"
647-
content-class="modal-content"
648-
v-on="$listeners"
614+
...
615+
focus-trap
649616
>
650617
...modal content
651618
</vue-final-modal>
652-
</focus-trap>
653619
</template>
654620
```
655621

docs/content/en/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,13 @@ Calculate `z-index` automatically with zIndexBase. If zIndex is set, `zIndexBase
372372

373373
Set specific `z-index` to root of the modal element. If zIndex is set, `zIndexBase` will become invalid.
374374

375+
### `focusTrap`
376+
377+
- Type: `Boolean`
378+
- Default: `false`
379+
380+
Enables focus trap meaning that only inputs/buttons that are withing the modal window can be focused by pressing Tab (plugin uses very naive implementation of the focus trap)
381+
375382
## **Events**
376383

377384
### **Live example**

docs/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
},
1111
"dependencies": {
1212
"@nuxt/content-theme-docs": "^0.6.1",
13-
"focus-trap": "^6.1.1",
14-
"focus-trap-vue": "^1.0.0",
1513
"nuxt": "^2.14.1",
1614
"vue-final-modal": "^0.13.3"
1715
},

docs/plugins/global.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import VueFinalModal from '../../lib'
55

66
Vue.use(VueFinalModal)
77

8-
import { FocusTrap } from 'focus-trap-vue'
9-
10-
Vue.component('FocusTrap', FocusTrap)
11-
128
import components from '../../example/src/components/index.js'
139
Object.keys(components).forEach(name => {
1410
Vue.component(name, components[name])

docs/yarn.lock

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4418,18 +4418,6 @@ flush-write-stream@^1.0.0:
44184418
inherits "^2.0.3"
44194419
readable-stream "^2.3.6"
44204420

4421-
focus-trap-vue@^1.0.0:
4422-
version "1.0.0"
4423-
resolved "https://registry.yarnpkg.com/focus-trap-vue/-/focus-trap-vue-1.0.0.tgz#0726c18a33dc2ad88994d978b9c65a537540e4ae"
4424-
integrity sha512-u5S7jOstKV47vs8+zpqhpTt/ghGk9bprrI6noCMyUt+58Ar5jC0Rpw88yJl5uqkAu3O5usk5Sm9bT547BYXoAg==
4425-
4426-
focus-trap@^6.1.1:
4427-
version "6.1.1"
4428-
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.1.1.tgz#61881b58e56783a7ca7331c6969336b986313594"
4429-
integrity sha512-sTTf6esJ2iCdn8WpUEB4LfJl7tvjGLASV3JmXbi7q1N5Ajjyk+65qZdrmjOF01s+/1v81rTNYZrsTeGha5KKpg==
4430-
dependencies:
4431-
tabbable "^5.1.0"
4432-
44334421
for-in@^1.0.2:
44344422
version "1.0.2"
44354423
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -9266,11 +9254,6 @@ svgo@^1.0.0:
92669254
unquote "~1.1.1"
92679255
util.promisify "~1.0.0"
92689256

9269-
tabbable@^5.1.0:
9270-
version "5.1.0"
9271-
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.1.0.tgz#b81115168d0a8359ba69003b6a99d05f8480a664"
9272-
integrity sha512-Y3nSukchqM5UchuZjhj/WyE79Qb4RM/Vx3x3oHO3UYKKpf70Hy3iVRxb61MzCavN74aZsKzvPl4KNG8tQUAjFQ==
9273-
92749257
tailwindcss@^1.8.10:
92759258
version "1.8.10"
92769259
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.8.10.tgz#945ef151c401c04a1c95e6a6bc747387a8d1b9dc"

example/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
},
1010
"dependencies": {
1111
"core-js": "^3.6.5",
12-
"focus-trap": "^6.1.1",
13-
"focus-trap-vue": "^1.0.0",
1412
"tailwindcss": "^1.4.6",
1513
"vue": "^2.6.11",
1614
"vue-final-modal": "link:.."

example/src/components/basic/BasicOptions.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@
7979
<span>attach:</span>
8080
<input v-model="attach" type="checkbox" />
8181
</label>
82+
<label class="flex items-center space-x-2">
83+
<span>focusTrap:</span>
84+
<input v-model="focusTrap" type="checkbox" />
85+
</label>
8286
<button class="vfm-btn" @click="reset">reset</button>
8387
</div>
8488
<h4>Attach area:</h4>
@@ -111,7 +115,8 @@ const initData = () => ({
111115
zIndexBase: 1000,
112116
allowZIndex: false,
113117
zIndex: 0,
114-
attach: false
118+
attach: false,
119+
focusTrap: false
115120
})
116121
117122
export default {
@@ -128,7 +133,8 @@ export default {
128133
overlayTransition: this.overlayTransition ? 'vfm' : '',
129134
zIndexBase: this.zIndexBase,
130135
...(this.allowZIndex && { zIndex: this.zIndex }),
131-
attach: this.attach ? '#attach' : false
136+
attach: this.attach ? '#attach' : false,
137+
focusTrap: this.focusTrap
132138
}
133139
}
134140
},

example/src/components/basic/VAccessibility.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<template>
22
<div>
3-
<v-modal v-model="show" @confirm="show = false" @cancel="show = false">
3+
<v-modal
4+
v-model="show"
5+
@confirm="show = false"
6+
@cancel="show = false"
7+
focus-trap
8+
>
49
<template v-slot:title>Hello, vue-final-modal</template>
510
<p>
611
Vue Final Modal is a renderless, stackable, detachable and lightweight

example/src/components/hoc/VModal.vue

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
<template>
2-
<focus-trap :value="value">
3-
<vue-final-modal
4-
:value="value"
5-
v-bind="$attrs"
6-
classes="modal-container"
7-
content-class="modal-content"
8-
v-on="$listeners"
9-
>
10-
<button class="modal__close" @click="$emit('input', false)">
11-
<mdi-close></mdi-close>
12-
</button>
13-
<span class="modal__title">
14-
<slot name="title"></slot>
15-
</span>
16-
<div class="modal__content">
17-
<slot></slot>
18-
</div>
19-
<div class="modal__action">
20-
<button class="vfm-btn" @click="$emit('confirm')">confirm</button>
21-
<button class="vfm-btn" @click="$emit('cancel')">cancel</button>
22-
</div>
23-
</vue-final-modal>
24-
</focus-trap>
2+
<vue-final-modal
3+
:value="value"
4+
v-bind="$attrs"
5+
classes="modal-container"
6+
content-class="modal-content"
7+
v-on="$listeners"
8+
>
9+
<button class="modal__close" @click="$emit('input', false)">
10+
<mdi-close></mdi-close>
11+
</button>
12+
<span class="modal__title">
13+
<slot name="title"></slot>
14+
</span>
15+
<div class="modal__content">
16+
<slot></slot>
17+
</div>
18+
<div class="modal__action">
19+
<button class="vfm-btn" @click="$emit('confirm')">confirm</button>
20+
<button class="vfm-btn" @click="$emit('cancel')">cancel</button>
21+
</div>
22+
</vue-final-modal>
2523
</template>
2624

2725
<script>

example/src/main.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import VueFinalModal from 'vue-final-modal'
88

99
Vue.use(VueFinalModal)
1010

11-
import { FocusTrap } from 'focus-trap-vue'
12-
13-
Vue.component('FocusTrap', FocusTrap)
14-
1511
Object.keys(components).forEach(name => {
1612
Vue.component(name, components[name])
1713
})

example/yarn.lock

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3955,18 +3955,6 @@ flush-write-stream@^1.0.0:
39553955
inherits "^2.0.3"
39563956
readable-stream "^2.3.6"
39573957

3958-
focus-trap-vue@^1.0.0:
3959-
version "1.0.0"
3960-
resolved "https://registry.yarnpkg.com/focus-trap-vue/-/focus-trap-vue-1.0.0.tgz#0726c18a33dc2ad88994d978b9c65a537540e4ae"
3961-
integrity sha512-u5S7jOstKV47vs8+zpqhpTt/ghGk9bprrI6noCMyUt+58Ar5jC0Rpw88yJl5uqkAu3O5usk5Sm9bT547BYXoAg==
3962-
3963-
focus-trap@^6.1.1:
3964-
version "6.1.1"
3965-
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.1.1.tgz#61881b58e56783a7ca7331c6969336b986313594"
3966-
integrity sha512-sTTf6esJ2iCdn8WpUEB4LfJl7tvjGLASV3JmXbi7q1N5Ajjyk+65qZdrmjOF01s+/1v81rTNYZrsTeGha5KKpg==
3967-
dependencies:
3968-
tabbable "^5.1.0"
3969-
39703958
follow-redirects@^1.0.0:
39713959
version "1.13.0"
39723960
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
@@ -8047,11 +8035,6 @@ symbol-observable@^1.1.0:
80478035
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
80488036
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
80498037

8050-
tabbable@^5.1.0:
8051-
version "5.1.0"
8052-
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.1.0.tgz#b81115168d0a8359ba69003b6a99d05f8480a664"
8053-
integrity sha512-Y3nSukchqM5UchuZjhj/WyE79Qb4RM/Vx3x3oHO3UYKKpf70Hy3iVRxb61MzCavN74aZsKzvPl4KNG8tQUAjFQ==
8054-
80558038
table@^5.2.3:
80568039
version "5.4.6"
80578040
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"

lib/VueFinalModal.vue

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
:aria-expanded="visibility.modal.toString()"
3939
role="dialog"
4040
aria-modal="true"
41-
tabindex="-1"
4241
@click="onClickContainer"
4342
>
4443
<div
@@ -55,6 +54,7 @@
5554
</template>
5655

5756
<script>
57+
import FocusTrap from './focusTrap.js'
5858
import { setStyle, removeStyle } from './dom.js'
5959
6060
const TransitionState = {
@@ -91,7 +91,8 @@ export default {
9191
transition: { type: String, default: 'vfm' },
9292
overlayTransition: { type: String, default: 'vfm' },
9393
zIndexBase: { type: [String, Number], default: 1000 },
94-
zIndex: { type: [Boolean, String, Number], default: false }
94+
zIndex: { type: [Boolean, String, Number], default: false },
95+
focusTrap: { type: Boolean, default: false }
9596
},
9697
data: () => ({
9798
modalStackIndex: null,
@@ -147,6 +148,7 @@ export default {
147148
this.$vfm.modals.push(this)
148149
},
149150
mounted() {
151+
this.$focusTrap = new FocusTrap()
150152
this.mounted()
151153
},
152154
beforeDestroy() {
@@ -201,6 +203,7 @@ export default {
201203
// If there are still nested modals opened
202204
const $_vm = this.$vfm.openedModals[this.$vfm.openedModals.length - 1]
203205
$_vm.handleLockScroll()
206+
$_vm.focusTrap && $_vm.$focusTrap.firstElement().focus()
204207
!$_vm.hideOverlay && ($_vm.visibility.overlay = true)
205208
} else {
206209
// If the closed modal is the last one
@@ -258,12 +261,18 @@ export default {
258261
},
259262
afterModalEnter() {
260263
this.modalTransitionState = TransitionState.Enter
261-
this.$refs.vfmContainer.focus()
264+
if (this.focusTrap) {
265+
this.$focusTrap.enable(this.$refs.vfmContainer)
266+
}
262267
this.$emit('opened')
263268
},
264269
beforeModalLeave() {
265270
this.$emit('before-close')
266271
this.modalTransitionState = TransitionState.Leaving
272+
273+
if (this.$focusTrap.enabled()) {
274+
this.$focusTrap.disable()
275+
}
267276
},
268277
afterModalLeave() {
269278
this.modalTransitionState = TransitionState.Leave

0 commit comments

Comments
 (0)