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

Commit 9083b7d

Browse files
joyzhongcopybara-github
authored andcommitted
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.
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
1 parent b349b51 commit 9083b7d

File tree

7 files changed

+348
-688
lines changed

7 files changed

+348
-688
lines changed

packages/mdc-slider/README.md

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ path: /catalog/input-controls/sliders/
1313
selections from a range of values.
1414

1515
The MDC Slider implementation supports both single point sliders (one thumb)
16-
and range sliders (two thumbs). It is modeled after the browser's
17-
`<input type="range">` element.
18-
19-
Sliders follow accessibility best practices per the [WAI-ARIA spec](https://www.w3.org/TR/wai-aria-practices/#slider)
20-
and are fully RTL-aware.
16+
and range sliders (two thumbs). It is backed by the browser
17+
`<input type="range">` element, is fully accessible, and is RTL-aware.
2118

2219
## Contents
2320

@@ -56,22 +53,24 @@ information on how to import JavaScript.
5653

5754
### Making sliders accessible
5855

59-
Sliders follow the
60-
[WAI-ARIA guidelines](https://www.w3.org/TR/wai-aria-practices/#slider).
56+
Sliders are backed by an `<input>` element, meaning that they are fully
57+
accessible. Unlike the [ARIA-based slider](https://www.w3.org/TR/wai-aria-practices/#slider),
58+
MDC sliders are adjustable using touch-based assistive technologies such as
59+
TalkBack on Android.
60+
6161
Per the spec, ensure that the following attributes are added to the
62-
`mdc-slider__thumb` element(s):
62+
`input` element(s):
6363

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

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

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

@@ -95,15 +94,14 @@ element.
9594

9695
```html
9796
<div class="mdc-slider">
98-
<input class="mdc-slider__input" type="hidden" min="0" max="100" value="50" name="volume">
97+
<input class="mdc-slider__input" type="range" min="0" max="100" value="50" name="volume" aria-label="Continuous slider demo">
9998
<div class="mdc-slider__track">
10099
<div class="mdc-slider__track--inactive"></div>
101100
<div class="mdc-slider__track--active">
102101
<div class="mdc-slider__track--active_fill"></div>
103102
</div>
104103
</div>
105-
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Continuous slider demo" aria-valuemin="0"
106-
aria-valuemax="100" aria-valuenow="50">
104+
<div class="mdc-slider__thumb">
107105
<div class="mdc-slider__thumb-knob"></div>
108106
</div>
109107
</div>
@@ -115,18 +113,18 @@ element.
115113

116114
```html
117115
<div class="mdc-slider mdc-slider--range">
118-
<input class="mdc-slider__input" type="hidden" min="0" max="70" value="30" name="rangeStart">
119-
<input class="mdc-slider__input" type="hidden" min="30" max="100" value="70" name="rangeEnd">
116+
<input class="mdc-slider__input" type="range" min="0" max="70" value="30" name="rangeStart" aria-label="Continuous range slider demo">
117+
<input class="mdc-slider__input" type="range" min="30" max="100" value="70" name="rangeEnd" aria-label="Continuous range slider demo">
120118
<div class="mdc-slider__track">
121119
<div class="mdc-slider__track--inactive"></div>
122120
<div class="mdc-slider__track--active">
123121
<div class="mdc-slider__track--active_fill"></div>
124122
</div>
125123
</div>
126-
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Continuous range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="30">
124+
<div class="mdc-slider__thumb">
127125
<div class="mdc-slider__thumb-knob"></div>
128126
</div>
129-
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Continuous range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="70">
127+
<div class="mdc-slider__thumb">
130128
<div class="mdc-slider__thumb-knob"></div>
131129
</div>
132130
</div>
@@ -147,14 +145,14 @@ To create a discrete slider, add the following:
147145

148146
```html
149147
<div class="mdc-slider mdc-slider--discrete">
150-
<input class="mdc-slider__input" type="hidden" min="0" max="100" value="50" name="volume" step="10">
148+
<input class="mdc-slider__input" type="range" min="0" max="100" value="50" name="volume" step="10" aria-label="Discrete slider demo">
151149
<div class="mdc-slider__track">
152150
<div class="mdc-slider__track--inactive"></div>
153151
<div class="mdc-slider__track--active">
154152
<div class="mdc-slider__track--active_fill"></div>
155153
</div>
156154
</div>
157-
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Discrete slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
155+
<div class="mdc-slider__thumb">
158156
<div class="mdc-slider__value-indicator-container">
159157
<div class="mdc-slider__value-indicator">
160158
<span class="mdc-slider__value-indicator-text">
@@ -184,7 +182,7 @@ To add tick marks to a discrete slider, add the following:
184182

185183
```html
186184
<div class="mdc-slider mdc-slider--discrete mdc-slider--tick-marks">
187-
<input class="mdc-slider__input" type="hidden" min="0" max="100" value="50" name="volume" step="10">
185+
<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">
188186
<div class="mdc-slider__track">
189187
<div class="mdc-slider__track--inactive"></div>
190188
<div class="mdc-slider__track--active">
@@ -204,7 +202,7 @@ To add tick marks to a discrete slider, add the following:
204202
<div class="mdc-slider__tick-mark--inactive"></div>
205203
</div>
206204
</div>
207-
<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">
205+
<div class="mdc-slider__thumb">
208206
<div class="mdc-slider__value-indicator-container">
209207
<div class="mdc-slider__value-indicator">
210208
<span class="mdc-slider__value-indicator-text">
@@ -221,15 +219,15 @@ To add tick marks to a discrete slider, add the following:
221219

222220
```html
223221
<div class="mdc-slider mdc-slider--range mdc-slider--discrete">
224-
<input class="mdc-slider__input" type="hidden" min="0" max="50" value="20" step="10" name="rangeStart">
225-
<input class="mdc-slider__input" type="hidden" min="20" max="100" value="50" step="10" name="rangeEnd">
222+
<input class="mdc-slider__input" type="range" min="0" max="50" value="20" step="10" name="rangeStart" aria-label="Discrete range slider demo">
223+
<input class="mdc-slider__input" type="range" min="20" max="100" value="50" step="10" name="rangeEnd" aria-label="Discrete range slider demo">
226224
<div class="mdc-slider__track">
227225
<div class="mdc-slider__track--inactive"></div>
228226
<div class="mdc-slider__track--active">
229227
<div class="mdc-slider__track--active_fill"></div>
230228
</div>
231229
</div>
232-
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Discrete range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="20">
230+
<div class="mdc-slider__thumb">
233231
<div class="mdc-slider__value-indicator-container">
234232
<div class="mdc-slider__value-indicator">
235233
<span class="mdc-slider__value-indicator-text">
@@ -239,7 +237,7 @@ To add tick marks to a discrete slider, add the following:
239237
</div>
240238
<div class="mdc-slider__thumb-knob"></div>
241239
</div>
242-
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Discrete range slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
240+
<div class="mdc-slider__thumb">
243241
<div class="mdc-slider__value-indicator-container">
244242
<div class="mdc-slider__value-indicator">
245243
<span class="mdc-slider__value-indicator-text">
@@ -264,14 +262,14 @@ To disable a slider, add the following:
264262

265263
```html
266264
<div class="mdc-slider mdc-slider--disabled">
267-
<input class="mdc-slider__input" type="hidden" min="0" max="100" value="50" step="10" disabled name="volume">
265+
<input class="mdc-slider__input" type="range" min="0" max="100" value="50" step="10" disabled name="volume" aria-label="Disabled slider demo">
268266
<div class="mdc-slider__track">
269267
<div class="mdc-slider__track--inactive"></div>
270268
<div class="mdc-slider__track--active">
271269
<div class="mdc-slider__track--active_fill"></div>
272270
</div>
273271
</div>
274-
<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">
272+
<div class="mdc-slider__thumb">
275273
<div class="mdc-slider__thumb-knob"></div>
276274
</div>
277275
</div>
@@ -281,19 +279,17 @@ To disable a slider, add the following:
281279

282280
### Initialization with custom ranges and values
283281

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

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

291289
```html
292290
<div class="mdc-slider">
291+
<input class="mdc-slider__input" aria-label="Slider demo" min="0" max="100" value="75">
293292
<!-- ... -->
294-
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-label="Slider demo" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75">
295-
<div class="mdc-slider__thumb-knob"></div>
296-
</div>
297293
</div>
298294
```
299295

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

330326
```html
331327
<div class="mdc-slider mdc-slider--range">
328+
<input class="mdc-slider__input" type="range" min="0" max="70" value="30" name="rangeStart" aria-label="Range slider demo">
329+
<input class="mdc-slider__input" type="range" min="30" max="100" value="70" name="rangeEnd" aria-label="Range slider demo">
332330
<div class="mdc-slider__track">
333331
<div class="mdc-slider__track--inactive"></div>
334332
<div class="mdc-slider__track--active">
335333
<div class="mdc-slider__track--active_fill"
336334
style="transform:scaleX(.4); left:30%"></div>
337335
</div>
338336
</div>
339-
<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)">
337+
<div class="mdc-slider__thumb" style="left:calc(30%-24px)">
340338
<div class="mdc-slider__thumb-knob"></div>
341339
</div>
342-
<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)">
340+
<div class="mdc-slider__thumb" style="left:calc(70%-24px)">
343341
<div class="mdc-slider__thumb-knob"></div>
344342
</div>
345343
</div>

packages/mdc-slider/_slider.scss

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ $_track-inactive-height: 4px;
6666
height: $_thumb-ripple-size;
6767
margin: 0 ($_thumb-ripple-size / 2);
6868
position: relative;
69-
touch-action: none;
69+
touch-action: pan-y;
7070
}
7171

7272
&.mdc-slider--disabled {
@@ -91,6 +91,18 @@ $_track-inactive-height: 4px;
9191
}
9292
}
9393
}
94+
95+
.mdc-slider__input {
96+
@include feature-targeting.targets($feat-structure) {
97+
cursor: pointer;
98+
left: 0;
99+
margin: 0;
100+
opacity: 0;
101+
pointer-events: none;
102+
position: absolute;
103+
top: 0;
104+
}
105+
}
94106
}
95107

96108
// This API is intended for use by frameworks that may want to separate the

packages/mdc-slider/adapter.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,6 @@ export interface MDCSliderAdapter {
6363
*/
6464
removeThumbClass(className: string, thumb: Thumb): void;
6565

66-
/**
67-
* - If thumb is `Thumb.START`, returns the value on the start thumb
68-
* (for range slider variant).
69-
* - If thumb is `Thumb.END`, returns the value on the end thumb (or
70-
* only thumb for single point slider).
71-
*/
72-
getThumbAttribute(attribute: string, thumb: Thumb): string|null;
73-
74-
/**
75-
* - If thumb is `Thumb.START`, sets the attribute on the start thumb
76-
* (for range slider variant).
77-
* - If thumb is `Thumb.END`, sets the attribute on the end thumb (or
78-
* only thumb for single point slider).
79-
*/
80-
setThumbAttribute(attribute: string, value: string, thumb: Thumb): void;
81-
8266
/**
8367
* - If thumb is `Thumb.START`, returns the value property on the start input
8468
* (for range slider variant).
@@ -120,19 +104,21 @@ export interface MDCSliderAdapter {
120104
removeInputAttribute(attribute: string, thumb: Thumb): void;
121105

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

127113
/**
128-
* @return Returns true if the given thumb is focused.
114+
* @return Returns true if the given input is focused.
129115
*/
130-
isThumbFocused(thumb: Thumb): boolean;
116+
isInputFocused(thumb: Thumb): boolean;
131117

132118
/**
133-
* Adds browser focus to the given thumb.
119+
* @return Returns the width of the given thumb knob.
134120
*/
135-
focusThumb(thumb: Thumb): void;
121+
getThumbKnobWidth(thumb: Thumb): number;
136122

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

249+
/**
250+
* Registers an event listener on the given input element.
251+
*/
252+
registerInputEventHandler<K extends EventType>(
253+
thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void;
254+
255+
/**
256+
* Deregisters an event listener on the given input element.
257+
*/
258+
deregisterInputEventHandler<K extends EventType>(
259+
thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void;
260+
263261
/**
264262
* Registers an event listener on the body element.
265263
*/

0 commit comments

Comments
 (0)