Repository navigation
fix(ValidationMessage): only set aria-invalid if danger color #5149
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
Changes from all commits
35357da
c025e1d
ad65c64
64dac75
2cc3b72
5a0da80
bcdad7f
cf8eb18
389cca7
586e69a
d9e92a1
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 |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| "@digdir/designsystemet-web": patch | ||
| "@digdir/designsystemet-react": patch | ||
| --- | ||
|
|
||
| **ValidationMessage:** Now correctly sets `aria-invalid="true"` only if `danger` color | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,9 +20,12 @@ declare global { | |
| } | ||
| } | ||
|
|
||
| const ATTR_FIELD = 'data-field'; | ||
| const ATTR_LIMIT = 'data-limit'; | ||
| const ATTR_INVALID = 'aria-invalid'; | ||
| const ATTR_DESCRIBEDBY = 'aria-describedby'; | ||
| const ATTR_INDETERMINATE = 'data-indeterminate'; | ||
| const SELECTOR_VALIDATION = `:scope > [${ATTR_FIELD}="validation"]`; | ||
| const COUNTER_DEBOUNCE = isWindows() ? 800 : 200; // Longer debounce on Windows due to NVDA performance | ||
| const WARNING_MULTIPLE_INPUTS = `Fields should only have one input element. Use <fieldset> to group multiple fields:`; | ||
|
|
||
|
|
@@ -43,12 +46,12 @@ const handleFieldMutation = (field: DSFieldElement) => { | |
| warn(WARNING_MULTIPLE_INPUTS, field); | ||
| else field._input = el; // Only register if visible input | ||
| } else { | ||
| const type = el.getAttribute('data-field'); // Using getAttribute instead of attr for best performance | ||
| const type = el.getAttribute(ATTR_FIELD); // Using getAttribute instead of attr for best performance | ||
| if (type === 'counter') field._counter = el; | ||
| if (type === 'validation') { | ||
| nextDescs.unshift(useId(el)); | ||
| hasValidation = true; | ||
| invalid = invalid || isInvalid(el); | ||
| invalid = invalid || isInvalidColor(el); | ||
| } else if (type) nextDescs.push(useId(el)); // Adds both counter and descriptions | ||
| } | ||
| } | ||
|
|
@@ -60,12 +63,12 @@ const handleFieldMutation = (field: DSFieldElement) => { | |
|
|
||
| const fieldsetValidation = field | ||
| .closest('fieldset') | ||
| ?.querySelector<HTMLElement>(':scope > [data-field="validation"]'); | ||
| ?.querySelector<HTMLElement>(SELECTOR_VALIDATION); | ||
|
|
||
| // Connect fieldset validation to inputs | ||
| if (fieldsetValidation && !fieldsetValidation?.hidden) { | ||
| hasValidation = true; | ||
| invalid = invalid || isInvalid(fieldsetValidation); | ||
| invalid = invalid || isInvalidColor(fieldsetValidation); | ||
| nextDescs.unshift(useId(fieldsetValidation)); | ||
| } | ||
|
|
||
|
|
@@ -87,7 +90,7 @@ const handleFieldMutation = (field: DSFieldElement) => { | |
| // Only manage aria-invalid when field has validation elements, and aria-invalid does not already exist | ||
| const hadValidation = field._validation; | ||
| if (hasValidation && !hadValidation && !input.hasAttribute(ATTR_INVALID)) { | ||
| attr(input, ATTR_INVALID, 'true'); | ||
| attr(input, ATTR_INVALID, invalid ? 'true' : null); | ||
| field._validation = true; | ||
| } else if (!hasValidation && hadValidation) { | ||
| attr(input, ATTR_INVALID, null); // Remove aria-invalid | ||
|
|
@@ -107,7 +110,10 @@ const debouncedCounterLiveRegion = debounce((input: Element, text: string) => { | |
| if (document.activeElement === input) announce(text); // Only announce if input is still focused | ||
| }, COUNTER_DEBOUNCE); | ||
|
|
||
| const isInvalid = (el: Element) => el.getAttribute('data-color') !== 'success'; | ||
| const isInvalidColor = (el: Element) => { | ||
| const color = el.getAttribute('data-color'); | ||
| return !color || color === 'danger'; | ||
|
Collaborator
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. Might be to early for me but is this first check correct? If no data-color is defined, then its an invalid color?
Contributor
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. Yes, default color of validation is danger, so no color = danger
Member
Author
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. If no color is set, every
Contributor
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. yes, this is how it works now |
||
| }; | ||
| const isInputLike = (el: unknown): el is HTMLInputElement => | ||
| el instanceof HTMLElement && | ||
| 'validity' in el && // Adds support for custom elements implemeted with attachInternals() | ||
|
|
@@ -127,8 +133,8 @@ export class DSFieldElement extends DSElement { | |
| this._unevents = on(this, 'input', this, QUICK_EVENT); | ||
| this._unmutate = onMutation(this, () => handleFieldMutation(this), { | ||
| attributeFilter: [ | ||
| 'data-field', | ||
| 'data-limit', | ||
| ATTR_FIELD, | ||
| ATTR_LIMIT, | ||
| 'hidden', // Needed to check validation visibility | ||
| 'id', // Needed to sync label "for" when ID of input/selec/textarea changes | ||
| 'value', // Needed to detect changes in controlled React inputs as they do not trigger input events | ||
|
|
@@ -143,7 +149,7 @@ export class DSFieldElement extends DSElement { | |
| const { _counter, _input } = this; | ||
|
|
||
| if (_counter?.isConnected && _input) { | ||
| const limit = Number(attr(_counter, 'data-limit')) || 0; | ||
| const limit = Number(attr(_counter, ATTR_LIMIT)) || 0; | ||
| const count = limit - _input.value.length; | ||
| const state = count < 0 ? 'over' : 'under'; | ||
| const label = (attrOrCSS(_counter, `data-${state}`) || TEXTS[state]) // Browser translation tools will not pick up dynamic text anyway, so no need to use aria-labelledby here | ||
|
|
@@ -153,6 +159,7 @@ export class DSFieldElement extends DSElement { | |
| attr(_counter, 'data-label', label); // Using attribute to prevent hydration errors, not using aria-label to make axe tests happy | ||
| attr(_counter, 'data-state', state); | ||
| attr(_counter, 'data-color', count < 0 ? 'danger' : null); | ||
| attr(_input, 'aria-invalid', count < 0 ? 'true' : null); // Toggle aria-invalid of textarea | ||
|
|
||
| // Only update live region when user is actually typing | ||
| if (event?.type === 'input' && label) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.