-
Notifications
You must be signed in to change notification settings - Fork 266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Keyboard nav for extensions main screen #13176
base: master
Are you sure you want to change the base?
Changes from all commits
8fb6619
7b63219
ea1509d
202aa96
b0250d1
da77f27
2112fcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
<script lang="ts"> | ||
import { defineComponent, PropType } from 'vue'; | ||
import { createFocusTrap, FocusTrap } from 'focus-trap'; | ||
import { basicSetupFocusTrap } from '@shell/composables/focusTrap'; | ||
|
||
export default defineComponent({ | ||
|
||
name: 'Card', | ||
props: { | ||
/** | ||
|
@@ -56,32 +57,17 @@ export default defineComponent({ | |
default: false, | ||
}, | ||
}, | ||
data() { | ||
return { focusTrapInstance: {} as FocusTrap }; | ||
}, | ||
mounted() { | ||
if (this.triggerFocusTrap) { | ||
this.focusTrapInstance = createFocusTrap(this.$refs.cardContainer as HTMLElement, { | ||
escapeDeactivates: true, | ||
allowOutsideClick: true, | ||
}); | ||
|
||
this.$nextTick(() => { | ||
this.focusTrapInstance.activate(); | ||
}); | ||
setup(props) { | ||
if (props.triggerFocusTrap) { | ||
basicSetupFocusTrap('#focus-trap-card-container'); | ||
} | ||
}, | ||
beforeUnmount() { | ||
if (this.focusTrapInstance && this.triggerFocusTrap) { | ||
this.focusTrapInstance.deactivate(); | ||
} | ||
}, | ||
} | ||
}); | ||
</script> | ||
|
||
<template> | ||
<div | ||
ref="cardContainer" | ||
id="focus-trap-card-container" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason we can't stick with the ref? I think that it is preferred over an |
||
class="card-container" | ||
:class="{'highlight-border': showHighlightBorder, 'card-sticky': sticky}" | ||
data-testid="card" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
<script lang="ts"> | ||
import { defineComponent } from 'vue'; | ||
import { watcherBasedSetupFocusTrap } from '@shell/composables/focusTrap'; | ||
|
||
export default defineComponent({ | ||
name: 'AppModal', | ||
|
@@ -56,6 +57,20 @@ export default defineComponent({ | |
name: { | ||
type: String, | ||
default: '', | ||
}, | ||
/** | ||
* Modal visibility (used for focus-trap) | ||
*/ | ||
modalVisibility: { | ||
type: Boolean, | ||
default: false, | ||
}, | ||
Comment on lines
+61
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
/** | ||
* trigger focus trap | ||
*/ | ||
triggerFocusTrap: { | ||
type: Boolean, | ||
default: false, | ||
} | ||
}, | ||
computed: { | ||
|
@@ -85,6 +100,11 @@ export default defineComponent({ | |
}; | ||
} | ||
}, | ||
created() { | ||
if (this.triggerFocusTrap) { | ||
watcherBasedSetupFocusTrap(() => this.modalVisibility, '.modal-container'); | ||
} | ||
}, | ||
Comment on lines
+103
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In
Is there any reason why we can't do the same here? It looks like we would be able to drop the |
||
mounted() { | ||
document.addEventListener('keydown', this.handleEscapeKey); | ||
}, | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,71 @@ | ||||||
/** | ||||||
* focusTrap is a composable based on the "focus-trap" package that allows us to implement focus traps | ||||||
* on components for keyboard navigation is a safe and reusable way | ||||||
*/ | ||||||
import { watch, nextTick, onMounted, onBeforeUnmount } from 'vue'; | ||||||
import { createFocusTrap, FocusTrap } from 'focus-trap'; | ||||||
|
||||||
export function basicSetupFocusTrap(containerSelector: string) { | ||||||
let focusTrapInstance = {} as FocusTrap; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that we can just specify the type:
Suggested change
|
||||||
|
||||||
onMounted(() => { | ||||||
focusTrapInstance = createFocusTrap(document.querySelector(containerSelector) as HTMLElement, { | ||||||
escapeDeactivates: true, | ||||||
allowOutsideClick: true, | ||||||
}); | ||||||
|
||||||
nextTick(() => { | ||||||
focusTrapInstance.activate(); | ||||||
}); | ||||||
}); | ||||||
|
||||||
onBeforeUnmount(() => { | ||||||
if (focusTrapInstance) { | ||||||
focusTrapInstance.deactivate(); | ||||||
} | ||||||
}); | ||||||
} | ||||||
|
||||||
export function watcherBasedSetupFocusTrapWithDestroyIncluded(watchVar:any, containerSelector: string) { | ||||||
let focusTrapInstance = {} as FocusTrap; | ||||||
|
||||||
watch(watchVar, (neu) => { | ||||||
if (neu) { | ||||||
nextTick(() => { | ||||||
focusTrapInstance = createFocusTrap(document.querySelector(containerSelector) as HTMLElement, { | ||||||
escapeDeactivates: true, | ||||||
allowOutsideClick: true, | ||||||
}); | ||||||
nextTick(() => { | ||||||
focusTrapInstance.activate(); | ||||||
}); | ||||||
}); | ||||||
} else if (!neu && focusTrapInstance) { | ||||||
focusTrapInstance.deactivate(); | ||||||
} | ||||||
}); | ||||||
} | ||||||
|
||||||
export function watcherBasedSetupFocusTrap(watchVar:any, containerSelector: string) { | ||||||
let focusTrapInstance = {} as FocusTrap; | ||||||
|
||||||
watch(watchVar, (neu) => { | ||||||
if (neu) { | ||||||
nextTick(() => { | ||||||
focusTrapInstance = createFocusTrap(document.querySelector(containerSelector) as HTMLElement, { | ||||||
escapeDeactivates: true, | ||||||
allowOutsideClick: true, | ||||||
}); | ||||||
nextTick(() => { | ||||||
focusTrapInstance.activate(); | ||||||
}); | ||||||
}); | ||||||
} | ||||||
}, { immediate: true }); | ||||||
|
||||||
onBeforeUnmount(() => { | ||||||
if (focusTrapInstance) { | ||||||
focusTrapInstance.deactivate(); | ||||||
} | ||||||
}); | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a common Vue convention for composable functions to start with
use
. I think thatuseFocusTrap
would be a more idiomatic name for this function.