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 authored and wrdhub committed Nov 7, 2024
1 parent 1ff047b commit 4011f3e
Show file tree
Hide file tree
Showing 15 changed files with 143 additions and 47 deletions.
59 changes: 46 additions & 13 deletions src/common/gui/base/colorPicker/ColorPickerView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { hexToHSL, hslToHex, isColorLight, isValidCSSHexColor, MAX_HUE_ANGLE, no
import { ColorPickerModel } from "./ColorPickerModel.js"
import { client } from "../../../misc/ClientDetector.js"
import { theme } from "../../theme.js"
import { assertNotNull } from "@tutao/tutanota-utils"
import { assertNotNull, filterInt } from "@tutao/tutanota-utils"
import { Keys, TabIndex } from "../../../api/common/TutanotaConstants"
import { isKeyPressed } from "../../../misc/KeyManager"

Expand Down 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,64 @@ 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"),
style: {
opacity: 0,
},
value: `${this.selectedHueAngle}`,
oninput: (e: InputEvent) => {
this.selectedHueAngle = filterInt((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 +359,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 +373,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 +417,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 @@ -2329,7 +2329,8 @@ styles.registerStyle("main", () => {
width: "9ch",
},
".custom-color-container .inputWrapper:before": {
content: '"#"',
// slash in content is content alt. so that it's ignored by screen readers
content: '"#" / ""',
color: theme.content_message_bg,
},
".calendar-invite-field": {
Expand Down
5 changes: 4 additions & 1 deletion src/common/misc/TranslationKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export type TranslationKeyType =
| "archive_action"
| "archive_label"
| "assignAdminRightsToLocallyAdministratedUserError_msg"
| "assignLabel_action"
| "assistant_label"
| "attachFiles_action"
| "attachmentAmount_label"
Expand Down Expand Up @@ -391,6 +392,7 @@ export type TranslationKeyType =
| "currentPlanDiscontinued_msg"
| "customColorsInfo_msg"
| "customColors_label"
| "customColor_label"
| "customDomainDeletePreconditionFailed_msg"
| "customDomainDeletePreconditionWhitelabelFailed_msg"
| "customDomainErrorDnsLookupFailure_msg"
Expand Down Expand Up @@ -683,6 +685,7 @@ export type TranslationKeyType =
| "howCanWeHelp_title"
| "htmlSourceCode_label"
| "html_action"
| "hue_label"
| "iCalNotSync_msg"
| "iCalSync_error"
| "icsInSharingFiles_msg"
Expand Down Expand Up @@ -952,7 +955,6 @@ export type TranslationKeyType =
| "moveToTop_action"
| "moveUp_action"
| "move_action"
| "assignLabel_action"
| "nameSuffix_placeholder"
| "name_label"
| "nativeShareGiftCard_label"
Expand Down Expand Up @@ -1744,6 +1746,7 @@ export type TranslationKeyType =
| "useSecurityKey_action"
| "validInputFormat_msg"
| "value_label"
| "variant_label"
| "vcardInSharingFiles_msg"
| "verifyDNSRecords_msg"
| "verifyDNSRecords_title"
Expand Down
13 changes: 8 additions & 5 deletions src/mail-app/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ export default {
"plural_forms": [
"zero",
"one",
"other"
"other",
],
"created_at": "2015-01-13T20:40:31Z",
"updated_at": "2024-10-29T11:39:16Z",
"updated_at": "2024-11-01T14:39:16Z",
"source_locale": {
"id": "fcd7471b347c8e517663e194dcddf237",
"name": "en",
"code": "en"
"code": "en",
},
"fallback_locale": null,
"keys": {
Expand Down Expand Up @@ -143,6 +143,7 @@ export default {
"archive_action": "Archivieren",
"archive_label": "Archiv",
"assignAdminRightsToLocallyAdministratedUserError_msg": "Du kannst lokal administrierte Benutzer nicht zum globalen Admin machen.",
"assignLabel_action": "Labels zuweisen",
"assistant_label": "Assistent*in",
"attachFiles_action": "Dateien anhängen",
"attachmentAmount_label": "{amount} Anhänge",
Expand Down Expand Up @@ -412,6 +413,7 @@ export default {
"currentPlanDiscontinued_msg": "Dein aktuelles Abonnement ist nicht mehr verfügbar. Bitte wähle eines der unten angezeigten Abonnements.",
"customColorsInfo_msg": "Wenn du ein Feld leer lässt, wird die Standardfarbe aus dem hellen Farbschema verwendet.",
"customColors_label": "Eigene Farben",
"customColor_label": "Benutzerdefinierte Farbe",
"customDomainDeletePreconditionFailed_msg": "Bitte deaktiviere zunächst alle Benutzer*innen und alle E-Mail-Adressen mit der Domain: {domainName}.",
"customDomainDeletePreconditionWhitelabelFailed_msg": "Bitte deaktiviere zunächst alle Benutzer und alle E-Mail-Adressen mit der Domain: {domainName} und entferne die Domain als Registrierungs-Domain.",
"customDomainErrorDnsLookupFailure_msg": "Fehler beim Abfragen der DNS-Informationen.",
Expand Down Expand Up @@ -704,6 +706,7 @@ export default {
"howCanWeHelp_title": "Wie können wir helfen?",
"htmlSourceCode_label": "HTML-Quellcode",
"html_action": "HTML",
"hue_label": "Farbton",
"iCalNotSync_msg": "Nicht synchronisiert.",
"iCalSync_error": "Fehler beim Synchronisieren. Ein oder mehr Kalender sind ungültig.",
"icsInSharingFiles_msg": "Eine oder mehrere Kalenderdateien wurden erkannt. Möchtest du sie importieren oder anhängen?",
Expand Down Expand Up @@ -1764,6 +1767,7 @@ export default {
"useSecurityKey_action": "Hier klicken, um Sicherheitsschlüssel zu verwenden",
"validInputFormat_msg": "Format ok.",
"value_label": "Wert",
"variant_label": "Variante",
"vcardInSharingFiles_msg": "Es wurden eine oder mehrere Kontaktdateien gefunden. Möchtest du diese importieren oder anhängen?",
"verifyDNSRecords_msg": "Im letzten Schritt musst du die unten aufgelisteten DNS-Einträge einrichten, damit der Tuta Mailserver E-Mails mit deiner Domain empfangen und versenden kann.",
"verifyDNSRecords_title": "DNS-Einträge einrichten",
Expand Down Expand Up @@ -1815,6 +1819,5 @@ export default {
"yourMessage_label": "Deine Nachricht",
"you_label": "Du",
"editLabel_action": "Label bearbeiten",
"assignLabel_action": "Assign labels"
}
},
}
13 changes: 8 additions & 5 deletions src/mail-app/translations/de_sie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ export default {
"plural_forms": [
"zero",
"one",
"other"
"other",
],
"created_at": "2018-01-12T10:51:54Z",
"updated_at": "2024-10-29T11:39:16Z",
"updated_at": "2024-11-01T14:39:16Z",
"source_locale": {
"id": "2001c6fdcc9cd338c1d600cb2636918b",
"name": "de",
"code": "de"
"code": "de",
},
"fallback_locale": null,
"keys": {
Expand Down Expand Up @@ -143,6 +143,7 @@ export default {
"archive_action": "Archivieren",
"archive_label": "Archiv",
"assignAdminRightsToLocallyAdministratedUserError_msg": "Sie können lokal administrierte Benutzer nicht zum globalen Admin machen.",
"assignLabel_action": "Labels zuweisen",
"assistant_label": "Assistent*in",
"attachFiles_action": "Dateien anhängen",
"attachmentAmount_label": "{amount} Anhänge",
Expand Down Expand Up @@ -412,6 +413,7 @@ export default {
"currentPlanDiscontinued_msg": "Ihr aktuelles Abonnement ist nicht mehr verfügbar. Bitte wählen Sie eines der unten angezeigten Abonnements.",
"customColorsInfo_msg": "Wenn Sie ein Feld leer lassen, wird die Standardfarbe aus dem hellen Farbschema verwendet.",
"customColors_label": "Eigene Farben",
"customColor_label": "Benutzerdefinierte Farbe",
"customDomainDeletePreconditionFailed_msg": "Bitte deaktivieren Sie zunächst alle Benutzer*innen und alle E-Mail-Adressen mit der Domain: {domainName}.",
"customDomainDeletePreconditionWhitelabelFailed_msg": "Bitte deaktivieren Sie zunächst alle Benutzer und alle E-Mail-Adressen mit der Domain: {domainName} und entfernen Sie die Domain als Registrierungs-Domain.",
"customDomainErrorDnsLookupFailure_msg": "Fehler beim Abfragen der DNS-Informationen.",
Expand Down Expand Up @@ -704,6 +706,7 @@ export default {
"howCanWeHelp_title": "Wie können wir helfen?",
"htmlSourceCode_label": "HTML-Quellcode",
"html_action": "HTML",
"hue_label": "Farbton",
"iCalNotSync_msg": "Nicht synchronisiert.",
"iCalSync_error": "Fehler beim Synchronisieren. Ein oder mehr Kalender sind ungültig.",
"icsInSharingFiles_msg": "Eine oder mehrere Kalenderdateien wurden erkannt. Möchten Sie sie importieren oder anhängen?",
Expand Down Expand Up @@ -1764,6 +1767,7 @@ export default {
"useSecurityKey_action": "Hier klicken, um Sicherheitsschlüssel zu verwenden",
"validInputFormat_msg": "Format ok.",
"value_label": "Wert",
"variant_label": "Variante",
"vcardInSharingFiles_msg": "Es wurden eine oder mehrere Kontaktdateien gefunden. Möchten Sie diese importieren oder anhängen?",
"verifyDNSRecords_msg": "Im letzten Schritt müssen Sie die unten aufgelisteten DNS-Einträge einrichten, damit der Tuta Mailserver E-Mails mit Ihrer Domain empfangen und versenden kann.",
"verifyDNSRecords_title": "DNS-Einträge einrichten",
Expand Down Expand Up @@ -1815,6 +1819,5 @@ export default {
"yourMessage_label": "Ihre Nachricht",
"you_label": "Sie",
"editLabel_action": "Label bearbeiten",
"assignLabel_action": "Assign labels"
}
},
}
11 changes: 7 additions & 4 deletions src/mail-app/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export default {
"plural_forms": [
"zero",
"one",
"other"
"other",
],
"created_at": "2015-01-13T20:10:13Z",
"updated_at": "2024-10-29T11:39:16Z",
"updated_at": "2024-11-01T14:39:15Z",
"source_locale": null,
"fallback_locale": null,
"keys": {
Expand Down Expand Up @@ -139,6 +139,7 @@ export default {
"archive_action": "Archive",
"archive_label": "Archive",
"assignAdminRightsToLocallyAdministratedUserError_msg": "You can't assign global admin rights to a locally administrated user.",
"assignLabel_action": "Assign labels",
"assistant_label": "Assistant",
"attachFiles_action": "Attach files",
"attachmentAmount_label": "{amount} attachments",
Expand Down Expand Up @@ -408,6 +409,7 @@ export default {
"currentPlanDiscontinued_msg": "Your current plan is no longer available. Please choose between the plans displayed below.",
"customColorsInfo_msg": "If you leave a blank field the color from the default light theme is used instead.",
"customColors_label": "Custom colors",
"customColor_label": "Custom color",
"customDomainDeletePreconditionFailed_msg": "Please deactivate all users and email addresses containing the domain: {domainName}.",
"customDomainDeletePreconditionWhitelabelFailed_msg": "Please deactivate all users and email addresses containing the domain: {domainName} and remove the domain as registration email domain.\n",
"customDomainErrorDnsLookupFailure_msg": "DNS lookup failed.",
Expand Down Expand Up @@ -700,6 +702,7 @@ export default {
"howCanWeHelp_title": "How can we help you?",
"htmlSourceCode_label": "HTML source code",
"html_action": "HTML",
"hue_label": "Hue",
"iCalNotSync_msg": "Not synced.",
"iCalSync_error": "Error during synchronization, one or more calendars were invalid.",
"icsInSharingFiles_msg": "One or more calendar files were detected. Would you like to import or attach them?",
Expand Down Expand Up @@ -1760,6 +1763,7 @@ export default {
"useSecurityKey_action": "Click to use security key",
"validInputFormat_msg": "Format ok.",
"value_label": "Value",
"variant_label": "Variant",
"vcardInSharingFiles_msg": "One or more contact files were detected. Would you like to import or attach them?",
"verifyDNSRecords_msg": "Finally, you have to configure the DNS records listed below to enable mail delivery to and from the Tuta mail server.",
"verifyDNSRecords_title": "Setup DNS records",
Expand Down Expand Up @@ -1811,6 +1815,5 @@ export default {
"yourMessage_label": "Your message",
"you_label": "You",
"editLabel_action": "Edit label",
"assignLabel_action": "Assign labels"
}
},
}
Loading

0 comments on commit 4011f3e

Please sign in to comment.