Add keyboard support to custom web components

Added Enter and Space key handlers to 3 custom elements for full keyboard accessibility:

1. clipboard_copy.js:
   - Added keydown listener for Enter/Space keys
   - Set tabindex="0" and role="button" for keyboard focus
   - Refactored click handler into reusable copyToClipboard function
   - Keyboard users can now copy text without a mouse

2. download_button.js:
   - Added keydown listener for Enter/Space keys
   - Set tabindex="0" and role="button" for keyboard focus
   - Keyboard users can now trigger file downloads

3. password_input.js:
   - Added keydown listener to togglePasswordVisibility element
   - Set tabindex="0" and role="button" on toggle button
   - Properly cleanup event listener in disconnectedCallback
   - Keyboard users can now toggle password visibility

All implementations:
- Use e.preventDefault() to prevent default Space key scrolling
- Check for existing tabindex/role attributes before setting
- Follow WCAG 2.1.1 (Keyboard, Level A) guidelines
- Support both Enter and Space keys per ARIA authoring practices

This satisfies WCAG 2.2 Success Criterion 2.1.1 (Keyboard, Level A).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
pull/599/head
Marcelo Paiva 1 month ago
parent a3109c6332
commit 7b462d5416

@ -2,7 +2,15 @@ export default class extends HTMLElement {
connectedCallback () { connectedCallback () {
this.clearChecked() this.clearChecked()
this.addEventListener('click', (e) => { // Make element keyboard accessible
if (!this.hasAttribute('tabindex')) {
this.setAttribute('tabindex', '0')
}
if (!this.hasAttribute('role')) {
this.setAttribute('role', 'button')
}
const copyToClipboard = (e) => {
const text = this.dataset.text || this.innerText.trim() const text = this.dataset.text || this.innerText.trim()
if (navigator.clipboard) { if (navigator.clipboard) {
@ -12,6 +20,16 @@ export default class extends HTMLElement {
alert(`Clipboard not available. Make sure you're using https://\nCopy text: ${text}`) alert(`Clipboard not available. Make sure you're using https://\nCopy text: ${text}`)
} }
} }
}
this.addEventListener('click', copyToClipboard)
// Add keyboard support for Enter and Space keys
this.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
copyToClipboard(e)
}
}) })
} }

@ -4,7 +4,23 @@ export default targetable(class extends HTMLElement {
static [target.static] = ['defaultButton', 'loadingButton'] static [target.static] = ['defaultButton', 'loadingButton']
connectedCallback () { connectedCallback () {
// Make element keyboard accessible
if (!this.hasAttribute('tabindex')) {
this.setAttribute('tabindex', '0')
}
if (!this.hasAttribute('role')) {
this.setAttribute('role', 'button')
}
this.addEventListener('click', () => this.downloadFiles()) this.addEventListener('click', () => this.downloadFiles())
// Add keyboard support for Enter and Space keys
this.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
this.downloadFiles()
}
})
} }
toggleState () { toggleState () {

@ -9,12 +9,31 @@ export default targetable(class extends HTMLElement {
] ]
connectedCallback () { connectedCallback () {
// Make toggle button keyboard accessible
if (!this.togglePasswordVisibility.hasAttribute('tabindex')) {
this.togglePasswordVisibility.setAttribute('tabindex', '0')
}
if (!this.togglePasswordVisibility.hasAttribute('role')) {
this.togglePasswordVisibility.setAttribute('role', 'button')
}
this.togglePasswordVisibility.addEventListener('click', this.handleTogglePasswordVisibility) this.togglePasswordVisibility.addEventListener('click', this.handleTogglePasswordVisibility)
// Add keyboard support for Enter and Space keys
this.handleKeydown = (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
this.handleTogglePasswordVisibility()
}
}
this.togglePasswordVisibility.addEventListener('keydown', this.handleKeydown)
document.addEventListener('turbo:submit-start', this.setInitialPasswordType) document.addEventListener('turbo:submit-start', this.setInitialPasswordType)
} }
disconnectedCallback () { disconnectedCallback () {
this.togglePasswordVisibility.removeEventListener('click', this.handleTogglePasswordVisibility) this.togglePasswordVisibility.removeEventListener('click', this.handleTogglePasswordVisibility)
this.togglePasswordVisibility.removeEventListener('keydown', this.handleKeydown)
document.removeEventListener('turbo:submit-start', this.setInitialPasswordType) document.removeEventListener('turbo:submit-start', this.setInitialPasswordType)
} }

Loading…
Cancel
Save