Skip to content

Commit

Permalink
fix: Re-exposed the activate/deactivate methods on component
Browse files Browse the repository at this point in the history
This closes posva#374
  • Loading branch information
DV8FromTheWorld committed Jun 29, 2021
1 parent a7769e6 commit 444bd7a
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 26 deletions.
18 changes: 18 additions & 0 deletions cypress/integration/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
Expand Down Expand Up @@ -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')
})
})
})
38 changes: 38 additions & 0 deletions demo/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,41 @@
</button>
</p>
</section>

<section id="methods">
<h2 id="methods-heading">exposed methods</h2>
<p>
Uses the methods exposed by the component (via $refs attachment) to activate and deactivate the focus trap
</p>
<p>
<button @click="() => $refs.methodsFocusTrap.activate()">activate trap</button>
</p>

<focus-trap
ref="methodsFocusTrap"
v-model:active="demos.methods.isActive"
>
<div class="trap" :class="demos.methods.isActive && 'is-active'">
<p>
Here is a focus trap
<a href="#">with</a>
<a href="#">some</a>
<a href="#">focusable</a> parts.
</p>
<p>
<label class="inline-label">
Initially focused input
<input ref="ieneInput" />
</label>
</p>
<p>
<button @click="() => $refs.methodsFocusTrap.deactivate()">
deactivate trap via method call
</button>
</p>
</div>
</focus-trap>
</section>
</div>
</template>

Expand Down Expand Up @@ -252,6 +287,9 @@ export default {
isActive: false,
clickOutsideEnabled: false,
allowOutsideClick: () => this.demos.aoc.clickOutsideEnabled
},
methods: {
isActive: false,
}
},
}
Expand Down
74 changes: 48 additions & 26 deletions src/FocusTrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from 'vue'
import {
createFocusTrap,
FocusTarget,
FocusTrap as FocusTrapI,
MouseEventToBoolean,
} from 'focus-trap'
Expand Down Expand Up @@ -38,7 +39,7 @@ export const FocusTrap = defineComponent({
default: false,
},
initialFocus: {
type: [String, Function] as PropType<string | (() => HTMLElement)>,
type: [String, Function] as PropType<FocusTarget>,
},
fallbackFocus: {
type: [String, Function] as PropType<string | (() => HTMLElement)>,
Expand All @@ -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<HTMLElement | null>(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()
Expand All @@ -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

Expand Down

0 comments on commit 444bd7a

Please sign in to comment.