fix(a11y): Datei-Feld nativ „required“, ungültige Felder verweisen auf die Fehlerzeile
- logo-dialog.ts: das Datei-Feld trägt `required` statt `aria-required` (für ein Datei-Feld gilt aria-required nicht; das Formular hat novalidate und prüft selbst, es ändert sich nichts am Ablauf). - logo-dialog.ts und publish-dialog.ts: die Fehlerzeile hat eine ID (logo-upload-error, publish-error); solange ein Feld ungültig ist, steht sie in seinem aria-describedby, hinter dem Hinweis des Feldes (Datei, Anzeigename, Tags). Ein Screenreader liest den Fehler so mit dem Feld vor. Beim nächsten Senden fällt der Verweis mit aria-invalid weg, der Hinweis bleibt. Im Dialog „Logo hochladen“ löscht auch eine neue Dateiwahl die Markierung aller Felder (sie löscht ja den Fehlertext, auf den sie verwiesen). - editor/dom.ts: setFieldInvalid(control, errorId, invalid) für beide Dialoge; eigene Tests (dom.test.ts). - Tests: required statt aria-required; ID einmalig; je Feld Verweis samt Hinweis in Reihenfolge, Rücknahme, neue Dateiwahl; der vorhandene Hilfsfall `invalid` beider Dialoge prüft den Verweis für jedes Feld mit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
29c9b2efaf
commit
8167a18857
6 files changed
+216
-10
No files matched your search
@@ -5,7 +5,7 @@ import './community.css';
|
||||
import { rememberDisplayName, rememberedDisplayName } from '../account/display-name';
|
||||
import { errorMessage } from '../account/errors';
|
||||
import { LOGIN_NEEDED, LoginCancelled } from '../account/with-login';
|
||||
import { el } from '../editor/dom';
|
||||
import { el, setFieldInvalid } from '../editor/dom';
|
||||
import { PIXELS_MESSAGE, READ_MESSAGE } from '../editor/upload';
|
||||
import { msg, t } from '../i18n/i18n';
|
||||
import { ApiError, apiErrorText, isAlreadyPublished } from './api';
|
||||
@@ -24,6 +24,8 @@ const ISSUER_MAX = 40;
|
||||
export const DISPLAY_NAME_MAX = 30;
|
||||
const TAGS_MAX = 5;
|
||||
const TAG_MAX = 20;
|
||||
/** Die Fehlerzeile des Dialogs: ein ungültiges Feld verweist mit `aria-describedby` auf sie. */
|
||||
const ERROR_ID = 'publish-error';
|
||||
/**
|
||||
* Steuerzeichen (C0, DEL, C1), Nullbreiten- und Bidi-Zeichen (U+200B-200F, U+202A-202E, U+2060-206F) und das BOM (U+FEFF):
|
||||
* unsichtbar oder umordnend. Dasselbe Muster prüft der Server (deploy/api/app.js, CONTROL_CHARACTERS).
|
||||
@@ -160,7 +162,7 @@ export function publishDialog(opts: PublishDialogOptions): Promise<PublishOutcom
|
||||
field(t('Anzeigename (optional)'), nameInput, t('Leer = „Anonym“. Deine E-Mail-Adresse wird nie angezeigt.')),
|
||||
el('label', { class: 'publish-consent' }, consentInput, el('span', {}, t(CONSENT_TEXT))),
|
||||
);
|
||||
const errorEl = el('p', { class: 'auth-msg auth-msg-error', role: 'alert' });
|
||||
const errorEl = el('p', { class: 'auth-msg auth-msg-error', id: ERROR_ID, role: 'alert' });
|
||||
const statusEl = el('p', { class: 'auth-msg', role: 'status' });
|
||||
const cancel = el('button', { type: 'button', class: 'auth-btn' }, t('Abbrechen'));
|
||||
const send = el('button', { type: 'submit', class: 'auth-btn auth-btn-primary' }, t('Veröffentlichen'));
|
||||
@@ -205,7 +207,7 @@ export function publishDialog(opts: PublishDialogOptions): Promise<PublishOutcom
|
||||
}
|
||||
|
||||
function clearInvalid(): void {
|
||||
for (const control of Object.values(inputs)) control.removeAttribute('aria-invalid');
|
||||
for (const control of Object.values(inputs)) setFieldInvalid(control, ERROR_ID, false);
|
||||
}
|
||||
|
||||
function showAlready(): void {
|
||||
@@ -233,7 +235,7 @@ export function publishDialog(opts: PublishDialogOptions): Promise<PublishOutcom
|
||||
const problem = validatePublishForm(values);
|
||||
if (problem) {
|
||||
errorEl.textContent = problem.message;
|
||||
inputs[problem.field].setAttribute('aria-invalid', 'true');
|
||||
setFieldInvalid(inputs[problem.field], ERROR_ID, true);
|
||||
inputs[problem.field].focus();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user