Skip to content
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

feat(slider): Use input with type="range" to back slider component. This ensures that sliders can be adjusted with touch-based assistive technologies, as the current ARIA spec for sliders is not compatible with e.g. TalkBack/Android. #6643

Merged
merged 1 commit into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 37 additions & 39 deletions packages/mdc-slider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ path: /catalog/input-controls/sliders/
selections from a range of values.

The MDC Slider implementation supports both single point sliders (one thumb)
and range sliders (two thumbs). It is modeled after the browser's
`<input type="range">` element.

Sliders follow accessibility best practices per the [WAI-ARIA spec](https://www.w3.org/TR/wai-aria-practices/#slider)
and are fully RTL-aware.
and range sliders (two thumbs). It is backed by the browser
`<input type="range">` element, is fully accessible, and is RTL-aware.

## Contents

Expand Down Expand Up @@ -56,22 +53,24 @@ information on how to import JavaScript.

### Making sliders accessible

Sliders follow the
[WAI-ARIA guidelines](https://www.w3.org/TR/wai-aria-practices/#slider).
Sliders are backed by an `<input>` element, meaning that they are fully
accessible. Unlike the [ARIA-based slider](https://www.w3.org/TR/wai-aria-practices/#slider),
MDC sliders are adjustable using touch-based assistive technologies such as
TalkBack on Android.

Per the spec, ensure that the following attributes are added to the
`mdc-slider__thumb` element(s):
`input` element(s):

* `role="slider"`
* `aria-valuenow`: Value representing the current value.
* `aria-valuemin`: Value representing the minimum allowed value.
* `aria-valuemax`: Value representing the maximum allowed value.
* `value`: Value representing the current value.
* `min`: Value representing the minimum allowed value.
* `max`: Value representing the maximum allowed value.
* `aria-label` or `aria-labelledby`: Accessible label for the slider.

If the value of `aria-valuenow` is not user-friendly (e.g. a number to
If the value is not user-friendly (e.g. a number to
represent the day of the week), also set the following:

* `aria-valuetext`: Set to a string that makes the slider value
understandable, e.g. 'Monday'.
* `aria-valuetext`: Set this input attribute to a string that makes the slider
value understandable, e.g. 'Monday'.
* Add a function to map the slider value to `aria-valuetext` via the
`MDCSlider#setValueToAriaValueTextFn` method.

Expand All @@ -95,15 +94,14 @@ element.

```html
<div class="mdc-slider">
<input class="mdc-slider__input" type="hidden" min="0" max="100" value="50" name="volume">
<input class="mdc-slider__input" type="range" min="0" max="100" value="50" name="volume" aria-label="Continuous slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Continuous slider demo" aria-valuemin="0"
aria-valuemax="100" aria-valuenow="50">
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
Expand All @@ -115,18 +113,18 @@ element.

```html
<div class="mdc-slider mdc-slider--range">
<input class="mdc-slider__input" type="hidden" min="0" max="70" value="30" name="rangeStart">
<input class="mdc-slider__input" type="hidden" min="30" max="100" value="70" name="rangeEnd">
<input class="mdc-slider__input" type="range" min="0" max="70" value="30" name="rangeStart" aria-label="Continuous range slider demo">
<input class="mdc-slider__input" type="range" min="30" max="100" value="70" name="rangeEnd" aria-label="Continuous range slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Continuous range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="30">
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Continuous range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="70">
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
Expand All @@ -147,14 +145,14 @@ To create a discrete slider, add the following:

```html
<div class="mdc-slider mdc-slider--discrete">
<input class="mdc-slider__input" type="hidden" min="0" max="100" value="50" name="volume" step="10">
<input class="mdc-slider__input" type="range" min="0" max="100" value="50" name="volume" step="10" aria-label="Discrete slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Discrete slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
<div class="mdc-slider__thumb">
<div class="mdc-slider__value-indicator-container">
<div class="mdc-slider__value-indicator">
<span class="mdc-slider__value-indicator-text">
Expand Down Expand Up @@ -184,7 +182,7 @@ To add tick marks to a discrete slider, add the following:

```html
<div class="mdc-slider mdc-slider--discrete mdc-slider--tick-marks">
<input class="mdc-slider__input" type="hidden" min="0" max="100" value="50" name="volume" step="10">
<input class="mdc-slider__input" type="range" min="0" max="100" value="50" name="volume" step="10" aria-label="Discrete slider with tick marks demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
Expand All @@ -204,7 +202,7 @@ To add tick marks to a discrete slider, add the following:
<div class="mdc-slider__tick-mark--inactive"></div>
</div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Discrete slider with tick marks demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
<div class="mdc-slider__thumb">
<div class="mdc-slider__value-indicator-container">
<div class="mdc-slider__value-indicator">
<span class="mdc-slider__value-indicator-text">
Expand All @@ -221,15 +219,15 @@ To add tick marks to a discrete slider, add the following:

```html
<div class="mdc-slider mdc-slider--range mdc-slider--discrete">
<input class="mdc-slider__input" type="hidden" min="0" max="50" value="20" step="10" name="rangeStart">
<input class="mdc-slider__input" type="hidden" min="20" max="100" value="50" step="10" name="rangeEnd">
<input class="mdc-slider__input" type="range" min="0" max="50" value="20" step="10" name="rangeStart" aria-label="Discrete range slider demo">
<input class="mdc-slider__input" type="range" min="20" max="100" value="50" step="10" name="rangeEnd" aria-label="Discrete range slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Discrete range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="20">
<div class="mdc-slider__thumb">
<div class="mdc-slider__value-indicator-container">
<div class="mdc-slider__value-indicator">
<span class="mdc-slider__value-indicator-text">
Expand All @@ -239,7 +237,7 @@ To add tick marks to a discrete slider, add the following:
</div>
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Discrete range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
<div class="mdc-slider__thumb">
<div class="mdc-slider__value-indicator-container">
<div class="mdc-slider__value-indicator">
<span class="mdc-slider__value-indicator-text">
Expand All @@ -264,14 +262,14 @@ To disable a slider, add the following:

```html
<div class="mdc-slider mdc-slider--disabled">
<input class="mdc-slider__input" type="hidden" min="0" max="100" value="50" step="10" disabled name="volume">
<input class="mdc-slider__input" type="range" min="0" max="100" value="50" step="10" disabled name="volume" aria-label="Disabled slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="-1" aria-label="Disabled slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50" aria-disabled="true">
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
Expand All @@ -281,19 +279,17 @@ To disable a slider, add the following:

### Initialization with custom ranges and values

When `MDCSlider` is initialized, it reads the thumb element's `aria-valuemin`,
`aria-valuemax`, and `aria-valuenow` attributes if present, using them to set
When `MDCSlider` is initialized, it reads the input element's `min`,
`max`, and `value` attributes if present, using them to set
the component's internal `min`, `max`, and `value` properties.

Use these attributes to initialize the slider with a custom range and values,
as shown below:

```html
<div class="mdc-slider">
<input class="mdc-slider__input" aria-label="Slider demo" min="0" max="100" value="75">
<!-- ... -->
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
```

Expand Down Expand Up @@ -329,17 +325,19 @@ This is an example of a range slider with internal values of

```html
<div class="mdc-slider mdc-slider--range">
<input class="mdc-slider__input" type="range" min="0" max="70" value="30" name="rangeStart" aria-label="Range slider demo">
<input class="mdc-slider__input" type="range" min="30" max="100" value="70" name="rangeEnd" aria-label="Range slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"
style="transform:scaleX(.4); left:30%"></div>
</div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="30" style="left:calc(30%-24px)">
<div class="mdc-slider__thumb" style="left:calc(30%-24px)">
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="70" style="left:calc(70%-24px)">
<div class="mdc-slider__thumb" style="left:calc(70%-24px)">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
Expand Down
14 changes: 13 additions & 1 deletion packages/mdc-slider/_slider.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ $_track-inactive-height: 4px;
height: $_thumb-ripple-size;
margin: 0 ($_thumb-ripple-size / 2);
position: relative;
touch-action: none;
touch-action: pan-y;
}

&.mdc-slider--disabled {
Expand All @@ -91,6 +91,18 @@ $_track-inactive-height: 4px;
}
}
}

.mdc-slider__input {
@include feature-targeting.targets($feat-structure) {
cursor: pointer;
left: 0;
margin: 0;
opacity: 0;
pointer-events: none;
position: absolute;
top: 0;
}
}
}

// This API is intended for use by frameworks that may want to separate the
Expand Down
42 changes: 20 additions & 22 deletions packages/mdc-slider/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,6 @@ export interface MDCSliderAdapter {
*/
removeThumbClass(className: string, thumb: Thumb): void;

/**
* - If thumb is `Thumb.START`, returns the value on the start thumb
* (for range slider variant).
* - If thumb is `Thumb.END`, returns the value on the end thumb (or
* only thumb for single point slider).
*/
getThumbAttribute(attribute: string, thumb: Thumb): string|null;

/**
* - If thumb is `Thumb.START`, sets the attribute on the start thumb
* (for range slider variant).
* - If thumb is `Thumb.END`, sets the attribute on the end thumb (or
* only thumb for single point slider).
*/
setThumbAttribute(attribute: string, value: string, thumb: Thumb): void;

/**
* - If thumb is `Thumb.START`, returns the value property on the start input
* (for range slider variant).
Expand Down Expand Up @@ -120,19 +104,21 @@ export interface MDCSliderAdapter {
removeInputAttribute(attribute: string, thumb: Thumb): void;

/**
* @return Returns the width of the given thumb knob.
* - If thumb is `Thumb.START`, focuses start input (range slider variant).
* - If thumb is `Thumb.END`, focuses end input (or only input for single
* point slider).
*/
getThumbKnobWidth(thumb: Thumb): number;
focusInput(thumb: Thumb): void;

/**
* @return Returns true if the given thumb is focused.
* @return Returns true if the given input is focused.
*/
isThumbFocused(thumb: Thumb): boolean;
isInputFocused(thumb: Thumb): boolean;

/**
* Adds browser focus to the given thumb.
* @return Returns the width of the given thumb knob.
*/
focusThumb(thumb: Thumb): void;
getThumbKnobWidth(thumb: Thumb): number;

/**
* @return Returns the bounding client rect of the given thumb.
Expand Down Expand Up @@ -260,6 +246,18 @@ export interface MDCSliderAdapter {
deregisterThumbEventHandler<K extends EventType>(
thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void;

/**
* Registers an event listener on the given input element.
*/
registerInputEventHandler<K extends EventType>(
thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void;

/**
* Deregisters an event listener on the given input element.
*/
deregisterInputEventHandler<K extends EventType>(
thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void;

/**
* Registers an event listener on the body element.
*/
Expand Down
Loading