Skip to content

Commit

Permalink
Make new Color Picker accessible on screen readers
Browse files Browse the repository at this point in the history
Close #7842
  • Loading branch information
hrb-hub committed Oct 30, 2024
1 parent 3b9f003 commit 413733a
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 16 deletions.
58 changes: 46 additions & 12 deletions src/common/gui/base/colorPicker/ColorPickerView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export class ColorPickerView implements Component<ColorPickerAttrs> {
index: i,
selectedColor: attrs.value,
onselect: attrs.onselect,
// add right divider to first color option
className: i === 0 ? ".pr-vpad-s.mr-hpad-small" : undefined,
style:
i === 0
Expand Down Expand Up @@ -233,6 +234,10 @@ export class ColorPickerView implements Component<ColorPickerAttrs> {
},
},
m(".border-radius", {
tabIndex: TabIndex.Default,
role: "radio",
ariaLabel: index === PaletteIndex.customVariant ? lang.get("customColor_label") : `${lang.get("variant_label")} ${index}`,
ariaChecked: isOptionSelected,
style: {
width: px(30),
height: px(30),
Expand All @@ -241,7 +246,6 @@ export class ColorPickerView implements Component<ColorPickerAttrs> {
borderColor: isOptionSelected ? "transparent" : theme.content_border,
backgroundColor: isColorValid ? color : theme.content_border,
},
tabIndex: TabIndex.Default,
onkeydown: (e: KeyboardEvent) => {
if (isKeyPressed(e.key, Keys.SPACE)) {
e.preventDefault()
Expand All @@ -255,40 +259,65 @@ export class ColorPickerView implements Component<ColorPickerAttrs> {
}

private renderHuePicker(onselect: ColorPickerAttrs["onselect"]): Children {
const a11yHueShiftStep = 5

return m(
".rel.overflow-hidden",
{
tabIndex: TabIndex.Default,
style: {
position: "relative",
},
onkeydown: (e: KeyboardEvent) => {
e.preventDefault()
const isRightMove = isKeyPressed(e.key, Keys.RIGHT)
const isLeftMove = isKeyPressed(e.key, Keys.LEFT)
const isStill = isLeftMove && isRightMove

if (!isStill && (isRightMove || isLeftMove)) {
const step = e.shiftKey ? 1 : 5
// holding down shift allows finer hue adjustment
const step = e.shiftKey ? 1 : a11yHueShiftStep
let hueStep = isLeftMove ? -step : step
this.selectedHueAngle = normalizeHueAngle(this.selectedHueAngle + hueStep)

this.postionSliderOnHue(assertNotNull(this.hueImgDom), assertNotNull(this.hueSliderDom))
assertNotNull(this.hueWindowDom).style.backgroundColor = this.model.getHueWindowColor(this.selectedHueAngle)
assertNotNull(this.huePickerDom).style.overflow = "visible"
this.toggleHueWindow(true)
this.generatePalette()

if (!this.isAdvanced || !isValidCSSHexColor(this.customColorHex)) {
onselect(assertNotNull(this.palette[this.fallbackVariantIndex], `no fallback color at ${this.fallbackVariantIndex}`))
}
}
},
onkeyup: () => {
if (assertNotNull(this.huePickerDom).style.overflow !== "hidden") {
assertNotNull(this.huePickerDom).style.overflow = "hidden"
}
},
onkeyup: () => this.toggleHueWindow(false),
oncreate: (vnode) => {
this.huePickerDom = vnode.dom as HTMLElement
},
},
[
// range input used to allow hue to be easily changed on a screen reader
m("input.fill-absolute.no-hover", {
type: "range",
min: 0,
max: MAX_HUE_ANGLE,
step: a11yHueShiftStep,
tabIndex: TabIndex.Default,
role: "slider",
ariaLabel: lang.get("hue_label"),
ariaLive: "assertive",
style: {
opacity: 0,
},
value: `${this.selectedHueAngle}`,
oninput: (e: InputEvent) => {
this.selectedHueAngle = Number((e.target as HTMLInputElement).value)

this.postionSliderOnHue(assertNotNull(this.hueImgDom), assertNotNull(this.hueSliderDom))
this.generatePalette()
if (!this.isAdvanced || !isValidCSSHexColor(this.customColorHex)) {
onselect(assertNotNull(this.palette[this.fallbackVariantIndex], `no fallback color at ${this.fallbackVariantIndex}`))
}
},
}),
// hueGradient
m(
".full-width.border-radius.overflow-hidden",
Expand Down Expand Up @@ -331,7 +360,7 @@ export class ColorPickerView implements Component<ColorPickerAttrs> {
const endListener = () => {
abortController.abort()
this.generatePalette()
assertNotNull(this.huePickerDom).style.overflow = "hidden"
this.toggleHueWindow(false)

if (!this.isAdvanced || !isValidCSSHexColor(this.customColorHex)) {
onselect(assertNotNull(this.palette[this.fallbackVariantIndex], `no fallback color at ${this.fallbackVariantIndex}`))
Expand All @@ -345,7 +374,7 @@ export class ColorPickerView implements Component<ColorPickerAttrs> {
document.addEventListener(client.isTouchSupported() ? "touchend" : "pointerup", endListener, { signal: abortController.signal })

this.handleHueChange(e, hueImgDom)
assertNotNull(this.huePickerDom).style.overflow = "visible"
this.toggleHueWindow(true)
},
}),
),
Expand Down Expand Up @@ -389,9 +418,14 @@ export class ColorPickerView implements Component<ColorPickerAttrs> {
)
}

private toggleHueWindow(show: boolean) {
assertNotNull(this.huePickerDom).style.overflow = show ? "visible" : "hidden"
}

private postionSliderOnHue(hueImgDom: HTMLElement, hueSliderDom: HTMLElement) {
const hueGradientWidth = hueImgDom.getBoundingClientRect().width
hueSliderDom.style.left = `${Math.floor((this.selectedHueAngle / MAX_HUE_ANGLE) * hueGradientWidth) + HUE_GRADIENT_BORDER_WIDTH}px`
assertNotNull(this.hueWindowDom).style.backgroundColor = this.model.getHueWindowColor(this.selectedHueAngle)
}

private handleHueChange = (e: PointerEvent | TouchEvent, hueImgDom: HTMLElement) => {
Expand Down
3 changes: 2 additions & 1 deletion src/common/gui/main-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2324,7 +2324,8 @@ styles.registerStyle("main", () => {
width: "9ch",
},
".custom-color-container .inputWrapper:before": {
content: '"#"',
// slash in content is content alt. so it's ignored by screen readers
content: '"#" / ""',
color: theme.content_message_bg,
},
".calendar-invite-field": {
Expand Down
3 changes: 3 additions & 0 deletions src/common/misc/TranslationKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1794,3 +1794,6 @@ export type TranslationKeyType =
| "yourMessage_label"
| "you_label"
| "emptyString_msg"
| "customColor_label"
| "variant_label"
| "hue_label"
9 changes: 6 additions & 3 deletions src/mail-app/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default {
"plural_forms": [
"zero",
"one",
"other"
"other",
],
"created_at": "2015-01-13T20:10:13Z",
"updated_at": "2024-10-29T11:39:16Z",
Expand Down Expand Up @@ -1809,6 +1809,9 @@ export default {
"yourCalendars_label": "Your calendars",
"yourFolders_action": "YOUR FOLDERS",
"yourMessage_label": "Your message",
"you_label": "You"
}
"you_label": "You",
"customColor_label": "Custom color",
"variant_label": "Variant",
"hue_label": "Hue",
},
}

0 comments on commit 413733a

Please sign in to comment.