diff --git a/cypress/integration/index.spec.js b/cypress/integration/index.spec.js
index 3feef1e..df50e65 100644
--- a/cypress/integration/index.spec.js
+++ b/cypress/integration/index.spec.js
@@ -15,6 +15,14 @@ function activateTrapWithButton(id) {
.click()
}
+function deactivateTrapWithButton(id) {
+ cy.get(`${id} .trap`)
+ .should('have.class', 'is-active')
+ .get(`${id} .trap > p > button`)
+ .first()
+ .click()
+}
+
function assertActivatedTrap(id) {
cy.get(`${id} .trap`).should('have.class', 'is-active')
}
@@ -119,4 +127,14 @@ describe('focus trap vue', () => {
assertActivatedTrap('#basic')
})
})
+
+ describe('method control of focus trap', () => {
+ it('allows control of trap via exposed methods', () => {
+ activateTrapWithButton('#methods')
+ assertActivatedTrap('#methods')
+
+ deactivateTrapWithButton('#methods')
+ assertDeactivatedTrap('#methods')
+ })
+ })
})
diff --git a/demo/App.vue b/demo/App.vue
index bca79de..47f41b8 100644
--- a/demo/App.vue
+++ b/demo/App.vue
@@ -224,6 +224,41 @@
+
+
+ exposed methods
+
+ Uses the methods exposed by the component (via $refs attachment) to activate and deactivate the focus trap
+
+
+
+
+
+
+
+
+ Here is a focus trap
+ with
+ some
+ focusable parts.
+
+
+
+
+
+
+
+
+
+
@@ -252,6 +287,9 @@ export default {
isActive: false,
clickOutsideEnabled: false,
allowOutsideClick: () => this.demos.aoc.clickOutsideEnabled
+ },
+ methods: {
+ isActive: false,
}
},
}
diff --git a/src/FocusTrap.ts b/src/FocusTrap.ts
index edd6ebe..10b7349 100644
--- a/src/FocusTrap.ts
+++ b/src/FocusTrap.ts
@@ -10,6 +10,7 @@ import {
} from 'vue'
import {
createFocusTrap,
+ FocusTarget,
FocusTrap as FocusTrapI,
MouseEventToBoolean,
} from 'focus-trap'
@@ -38,7 +39,7 @@ export const FocusTrap = defineComponent({
default: false,
},
initialFocus: {
- type: [String, Function] as PropType HTMLElement)>,
+ type: [String, Function] as PropType,
},
fallbackFocus: {
type: [String, Function] as PropType HTMLElement)>,
@@ -47,40 +48,50 @@ export const FocusTrap = defineComponent({
emits: ['update:active', 'activate', 'deactivate'],
- setup(props, { slots, emit }) {
+ setup(props, { slots, emit, expose }) {
let trap: FocusTrapI | null
const el = ref(null)
+ const ensureTrap = () => {
+ if (trap) {
+ return
+ }
+
+ const { initialFocus } = props
+ trap = createFocusTrap(el.value as HTMLElement, {
+ escapeDeactivates: props.escapeDeactivates,
+ allowOutsideClick: event =>
+ typeof props.allowOutsideClick === 'function'
+ ? props.allowOutsideClick(event)
+ : props.allowOutsideClick,
+ returnFocusOnDeactivate: props.returnFocusOnDeactivate,
+ clickOutsideDeactivates: props.clickOutsideDeactivates,
+ onActivate: () => {
+ emit('update:active', true)
+ emit('activate')
+ },
+ onDeactivate: () => {
+ emit('update:active', false)
+ emit('deactivate')
+ },
+ initialFocus: initialFocus
+ ? typeof initialFocus === 'function'
+ ? initialFocus()
+ : initialFocus
+ : (el.value as HTMLElement),
+ fallbackFocus: props.fallbackFocus,
+ })
+ }
+
onMounted(() => {
watch(
() => props.active,
active => {
- const { initialFocus } = props
if (active && el.value) {
// has no effect if already activated
- trap = createFocusTrap(el.value, {
- escapeDeactivates: props.escapeDeactivates,
- allowOutsideClick: event =>
- typeof props.allowOutsideClick === 'function'
- ? props.allowOutsideClick(event)
- : props.allowOutsideClick,
- returnFocusOnDeactivate: props.returnFocusOnDeactivate,
- clickOutsideDeactivates: props.clickOutsideDeactivates,
- onActivate: () => {
- emit('update:active', true)
- emit('activate')
- },
- onDeactivate: () => {
- emit('update:active', false)
- emit('deactivate')
- },
- initialFocus: initialFocus
- ? typeof initialFocus === 'function'
- ? initialFocus()
- : initialFocus
- : el.value,
- fallbackFocus: props.fallbackFocus,
- })
+ ensureTrap()
+
+ // @ts-ignore
trap.activate()
} else if (trap) {
trap.deactivate()
@@ -95,6 +106,17 @@ export const FocusTrap = defineComponent({
trap = null
})
+ expose({
+ activate() {
+ ensureTrap()
+ // @ts-ignore
+ trap.activate()
+ },
+ deactivate() {
+ trap && trap.deactivate()
+ },
+ })
+
return () => {
if (!slots.default) return null