Skip to content

Commit

Permalink
[Numeric Input] - Link tooltip content to input field for assistive t…
Browse files Browse the repository at this point in the history
…echnologies (#1891)

## Summary:
Currently, the format options tooltip for the input field is only available to sighted users. People using assistive technologies (like braille or screen readers) are unaware of any formatting options for their answers. This change will add an element adjacent to the input field that includes the content of the tooltip, and an association between the two so that assistive technologies can make use of the format options.

Issue: LEMS-2458

## Test plan:
1. Launch Storybook
1. Navigate to Perseus Editor => Editor => [Demo](http://localhost:6006/?path=/story/perseuseditor-editorpage--demo)
1. Add a Numeric Input widget
1. Configure the widget to have any number of format options
![Storybook - 1 Format Option](https://github.com/user-attachments/assets/a46af08c-cc6d-48a6-b20f-4b48c560023d)
1. Using the keyboard, tab to the close icon in the top right of the preview area
![Storybook - Close Icon](https://github.com/user-attachments/assets/a75b4447-4015-460f-8e91-76a71d46de6f)
1. Launch VoiceOver (or your preferred screen reader)
1. Tab to the input field in the preview
1. The screen reader should read the same content as what shows in the tooltip
![VoiceOver - 1 Format Option](https://github.com/user-attachments/assets/fa6bed95-5446-44f7-8cd5-7ec68b860495)
1. Multiple format options should be read with an "or" conjunction in the phrase
![VoiceOver - 3 Format Option](https://github.com/user-attachments/assets/16fc3ef9-bd53-4b65-a355-777919779600)

## Affected behavior:
### Before
![VoiceOver - Before](https://github.com/user-attachments/assets/12ee2a44-ef61-4d80-8120-600d4aa143f4)

### After
![VoiceOver - 1 Format Option](https://github.com/user-attachments/assets/911d1b53-9ea3-4758-bbd5-4a6f04aae901)

Author: mark-fitzgerald

Reviewers: SonicScrewdriver

Required Reviewers:

Approved By: SonicScrewdriver

Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ gerald

Pull Request URL: #1891
  • Loading branch information
mark-fitzgerald authored Dec 3, 2024
1 parent be8c06c commit ef819ea
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/odd-moons-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": patch
---

[Numeric Input] - Associate format options tooltip content with input field for assistive technologies
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ exports[`server item renderer should snapshot: initial render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-aW5wdXQtbnVtYmVyIDE"
aria-invalid="false"
autocapitalize="off"
autocomplete="off"
Expand All @@ -50,12 +51,24 @@ exports[`server item renderer should snapshot: initial render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-aW5wdXQtbnVtYmVyIDE"
style="display: none;"
>
Your answer should be
an integer, like 6, or
an exact decimal, like 0.75, or
a simplified proper fraction, like 3/5, or
a simplified improper fraction, like 7/4, or
a mixed number, like 1 and 3/4
</span>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
23 changes: 21 additions & 2 deletions packages/perseus/src/components/input-with-examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,21 @@ class InputWithExamples extends React.Component<Props, State> {

_renderInput: () => any = () => {
const id = this._getUniqueId();
const ariaId = `aria-for-${id}`;
// Generate text from a known set of format options that will read well in a screen reader
const examplesAria =
this.props.examples.length === 7
? ""
: `${this.props.examples[0]}
${this.props.examples.slice(1).join(", or\n")}`
// @ts-expect-error TS2550: Property replaceAll does not exist on type string.
.replaceAll("*", "")
.replaceAll("$", "")
.replaceAll("\\ \\text{pi}", " pi")
.replaceAll("\\ ", " and ");
const inputProps = {
id: id,
"aria-describedby": id,
"aria-describedby": ariaId,
ref: "input",
className: this._getInputClassName(),
labelText: this.props.labelText,
Expand All @@ -101,7 +113,14 @@ class InputWithExamples extends React.Component<Props, State> {
autoCorrect: "off",
spellCheck: "false",
};
return <TextInput {...inputProps} />;
return (
<>
<TextInput {...inputProps} />
<span id={ariaId} style={{display: "none"}}>
{examplesAria}
</span>
</>
);
};

_handleFocus: () => void = () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/perseus/src/components/text-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Props = {
placeholder?: string;
onKeyDown?: () => void;
style?: StyleType;
"aria-describedby"?: string;
};

type DefaultProps = {
Expand Down Expand Up @@ -113,6 +114,7 @@ class TextInput extends React.Component<Props> {
value={formattedValue}
type="text"
aria-label={labelText}
aria-describedby={this.props["aria-describedby"]}
onChange={(value) => this.props.onChange(value)}
placeholder={placeholder}
onFocus={onFocus}
Expand Down
1 change: 1 addition & 0 deletions packages/perseus/src/components/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ class Tooltip extends React.Component<Props, State> {
<div
ref={this.tooltipContainerRef}
className="tooltipContainer"
role="tooltip"
style={{
position: "absolute",
// height must start out undefined, not null, so that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-aW5wdXQtbnVtYmVyIDE"
aria-invalid="false"
autocapitalize="off"
autocomplete="off"
Expand All @@ -984,12 +985,24 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-aW5wdXQtbnVtYmVyIDE"
style="display: none;"
>
Your answer should be
an integer, like 6, or
an exact decimal, like 0.75, or
a simplified proper fraction, like 3/5, or
a simplified improper fraction, like 7/4, or
a mixed number, like 1 and 3/4
</span>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ exports[`graded-group-set should render all graded groups 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -72,12 +73,17 @@ exports[`graded-group-set should render all graded groups 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down Expand Up @@ -297,6 +303,7 @@ exports[`graded-group-set should render all graded groups 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -307,12 +314,17 @@ exports[`graded-group-set should render all graded groups 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down Expand Up @@ -532,6 +544,7 @@ exports[`graded-group-set should render all graded groups 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -542,12 +555,17 @@ exports[`graded-group-set should render all graded groups 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ exports[`graded group widget should snapshot 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -138,12 +139,17 @@ exports[`graded group widget should snapshot 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ exports[`group widget should snapshot: initial render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="value rounded to the nearest ten"
autocapitalize="off"
Expand All @@ -765,12 +766,17 @@ exports[`group widget should snapshot: initial render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down Expand Up @@ -968,6 +974,7 @@ exports[`group widget should snapshot: initial render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAy"
aria-invalid="false"
aria-label="value rounded to the nearest hundred"
autocapitalize="off"
Expand All @@ -978,12 +985,17 @@ exports[`group widget should snapshot: initial render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAy"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
Loading

0 comments on commit ef819ea

Please sign in to comment.