Skip to content

Commit

Permalink
🩹 enhance actions focus trap
Browse files Browse the repository at this point in the history
add scoped default slot and expose clearFocusTrap

Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
  • Loading branch information
Vinicius Reis committed Aug 12, 2022
1 parent 0324a18 commit 9d188ea
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 32 deletions.
93 changes: 82 additions & 11 deletions src/components/Actions/Actions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,52 @@ export default {
</script>
```

### Scoped slot

```vue
<template>
<div>
<input ref="input" />
<Actions>
<template v-slot:default="{ clearFocusTrap }">
<ActionButton @click="focusInput(clearFocusTrap)" :close-after-click="true">
<template #icon>
<Delete :size="20" />
</template>
Focus input
</ActionButton>
<ActionButton @click="actionDelete">
<template #icon>
<Delete :size="20" />
</template>
Delete
</ActionButton>
</template>
</Actions>
</div>
</template>

<script>
import Delete from 'vue-material-design-icons/Delete'
export default {
components: {
Delete,
},
methods: {
actionDelete() {
alert('Delete')
},
async focusInput(clearFocusTrap) {
await clearFocusTrap({ returnFocus: false })
await this.$nextTick()
this.$refs.input.focus()
},
},
}
</script>
```

</docs>

<script>
Expand All @@ -373,6 +419,26 @@ import DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'
const focusableSelector = '.focusable'
const getActions = (ctx) => {
/**
* Filter the Actions, so that we only get allowed components.
* This also ensure that we don't get 'text' elements, which would
* become problematic later on.
*/
if (ctx.$slots.default !== undefined) {
return ctx.$slots.default.filter(action => action?.componentOptions?.tag)
}
/**
* Expose some internal methods to parent template
*/
if (ctx.$scopedSlots.default) {
return ctx.$scopedSlots.default({ clearFocusTrap: ctx.clearFocusTrap })
}
return []
}
/**
* The Actions component can be used to display one ore more actions.
* If only a single action is provided, it will be rendered as an inline icon.
Expand Down Expand Up @@ -497,6 +563,13 @@ export default {
type: Boolean,
default: false,
},
/**
* Enable popover focus trap
*/
focusTrap: {
type: Boolean,
default: true,
},
},
emits: [
Expand Down Expand Up @@ -712,6 +785,9 @@ export default {
onBlur(event) {
this.$emit('blur', event)
},
clearFocusTrap(options = {}) {
return this.$refs.popover.clearFocusTrap(options)
},
},
/**
* The render function to display the component
Expand All @@ -720,14 +796,7 @@ export default {
* @return {VNodes} The created VNodes
*/
render(h) {
/**
* Filter the Actions, so that we only get allowed components.
* This also ensure that we don't get 'text' elements, which would
* become problematic later on.
*/
const actions = (this.$slots.default || []).filter(
action => action?.componentOptions?.tag
)
const actions = getActions(this)
// Check that we have at least one action
if (actions.length === 0) {
Expand Down Expand Up @@ -799,6 +868,7 @@ export default {
},
})
)
return h('div',
{
class: [
Expand All @@ -811,13 +881,15 @@ export default {
[
h('Popover',
{
ref: 'popover',
props: {
delay: 0,
handleResize: true,
shown: this.opened,
placement: this.placement,
boundary: this.boundariesElement,
container: this.container,
focusTrap: this.focusTrap,
popoverBaseClass: 'action-item__popper',
},
// For some reason the popover component
Expand All @@ -830,6 +902,7 @@ export default {
placement: this.placement,
boundary: this.boundariesElement,
container: this.container,
focusTrap: this.focusTrap,
popoverBaseClass: 'action-item__popper',
},
on: {
Expand Down Expand Up @@ -884,9 +957,7 @@ export default {
tabindex: '-1',
role: 'menu',
},
}, [
actions,
]),
}, actions),
]),
],
),
Expand Down
55 changes: 34 additions & 21 deletions src/components/Popover/Popover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,12 @@ export default {
type: String,
default: '',
},
/**
* Enable popover focus trap
*/
focusTrap: {
type: Boolean,
default: false,
default: true,
},
},
Expand Down Expand Up @@ -142,23 +145,14 @@ export default {
},
beforeDestroy() {
this.afterHide()
this.clearFocusTrap()
},
methods: {
afterShow() {
/**
* Triggered after the tooltip was visually displayed.
*
* This is different from the 'show' and 'apply-show' which
* run earlier than this where there is no guarantee that the
* tooltip is already visible and in the DOM.
*/
this.$emit('after-show')
/**
* Add focus trap for accessibility.
*/
/**
* Add focus trap for accessibility.
*/
useFocusTrap() {
this.$nextTick(() => {
if (!this.focusTrap) {
return
Expand All @@ -179,17 +173,36 @@ export default {
this.$focusTrap.activate()
})
},
/**
* Remove focus trap
*
* @param options
*/
async clearFocusTrap(options = {}) {
try {
this.$focusTrap?.deactivate(options)
this.$focusTrap = null
} catch (err) {
console.warn(err)
}
},
afterShow() {
/**
* Triggered after the tooltip was visually displayed.
*
* This is different from the 'show' and 'apply-show' which
* run earlier than this where there is no guarantee that the
* tooltip is already visible and in the DOM.
*/
this.$emit('after-show')
this.useFocusTrap()
},
afterHide() {
/**
* Triggered after the tooltip was visually hidden.
*/
this.$emit('after-hide')
/**
* Remove focus trap
*/
this.$focusTrap?.deactivate()
this.$focusTrap = null
this.clearFocusTrap()
},
},
}
Expand Down

0 comments on commit 9d188ea

Please sign in to comment.