Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
feat(slider): Use input with type="range" to back slider component. T…
Browse files Browse the repository at this point in the history
…his 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.

This PR does the following:
- When input is focused, adds focused style to slider thumb, and for discrete sliders, shows value indicator
- On input `change` event (e.g. key [LEFT/RIGHT/UP/DOWN] events on input that adjust value), change internal slider value
- On internal slider value change (e.g. via pointer events), sync input value attribute and property
- On thumb drag start, focus input (such that users can use a mix of pointer and key events)

BREAKING CHANGE: Slider is now backed by an input of type="range". Additionally, adapter methods (focusInput, isInputFocused, registerInputEventHandler, deregisterInputEventHandler) were added.

PiperOrigin-RevId: 344116908
  • Loading branch information
joyzhong authored and copybara-github committed Nov 24, 2020
1 parent b349b51 commit 9083b7d
Show file tree
Hide file tree
Showing 7 changed files with 348 additions and 688 deletions.
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

0 comments on commit 9083b7d

Please sign in to comment.