Skip to content

Commit 2ef3606

Browse files
fix(slider): default values to between min and max to better match native input
PiperOrigin-RevId: 538701031
1 parent fbed917 commit 2ef3606

File tree

3 files changed

+92
-38
lines changed

3 files changed

+92
-38
lines changed

slider/demo/stories.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ const customStyling: MaterialStoryInit<StoryKnobs> = {
122122
const target = event.target as MdSlider;
123123
const {min, max, valueStart, valueEnd} = target;
124124
const range = max - min;
125-
const fractionStart = valueStart / range;
126-
const fractionEnd = valueEnd / range;
125+
const fractionStart = valueStart! / range;
126+
const fractionEnd = valueEnd! / range;
127127
target.valueStartLabel = labelFor(fractionStart);
128128
target.valueEndLabel = labelFor(fractionEnd);
129129
}

slider/lib/slider.ts

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,6 @@ import {MdRipple} from '../../ripple/ripple.js';
2222
// Disable warning for classMap with destructuring
2323
// tslint:disable:quoted-properties-on-dictionary
2424

25-
/** The default value for a continuous slider. */
26-
const DEFAULT_VALUE = 50;
27-
/** The default start value for a range slider. */
28-
const DEFAULT_VALUE_START = 25;
29-
/** The default end value for a range slider. */
30-
const DEFAULT_VALUE_END = 75;
3125

3226
/**
3327
* Slider component.
@@ -61,19 +55,17 @@ export class Slider extends LitElement {
6155
/**
6256
* The slider value displayed when range is false.
6357
*/
64-
@property({type: Number}) value = DEFAULT_VALUE;
58+
@property({type: Number}) value?: number;
6559

6660
/**
6761
* The slider start value displayed when range is true.
6862
*/
69-
@property({type: Number, attribute: 'value-start'})
70-
valueStart = DEFAULT_VALUE_START;
63+
@property({type: Number, attribute: 'value-start'}) valueStart?: number;
7164

7265
/**
7366
* The slider end value displayed when range is true.
7467
*/
75-
@property({type: Number, attribute: 'value-end'})
76-
valueEnd = DEFAULT_VALUE_END;
68+
@property({type: Number, attribute: 'value-end'}) valueEnd?: number;
7769

7870
/**
7971
* An optional label for the slider's value displayed when range is
@@ -195,8 +187,8 @@ export class Slider extends LitElement {
195187
@state() private startOnTop = false;
196188
@state() private handlesOverlapping = false;
197189

198-
@state() private renderValueStart = 0;
199-
@state() private renderValueEnd = 0;
190+
@state() private renderValueStart?: number;
191+
@state() private renderValueEnd?: number;
200192

201193
// used in synthetic events generated to control ripple hover state.
202194
private ripplePointerId = 1;
@@ -229,12 +221,12 @@ export class Slider extends LitElement {
229221
protected override willUpdate(changed: PropertyValues) {
230222
this.renderValueStart = changed.has('valueStart') ?
231223
this.valueStart :
232-
(this.inputStart?.valueAsNumber ?? 0);
224+
this.inputStart?.valueAsNumber;
233225
const endValueChanged =
234226
(changed.has('valueEnd') && this.range) || changed.has('value');
235227
this.renderValueEnd = endValueChanged ?
236228
(this.range ? this.valueEnd : this.value) :
237-
(this.inputEnd?.valueAsNumber ?? 0);
229+
this.inputEnd?.valueAsNumber;
238230
// manually handle ripple hover state since the handle is pointer events
239231
// none.
240232
if (changed.get('handleStartHover') !== undefined) {
@@ -269,6 +261,26 @@ export class Slider extends LitElement {
269261
this.renderValueStart = this.inputStart!.valueAsNumber;
270262
}
271263
this.renderValueEnd = this.inputEnd!.valueAsNumber;
264+
// update values if they are unset
265+
// when using a range, default to equi-distant between
266+
// min - valueStart - valueEnd - max
267+
if (this.range) {
268+
const segment = (this.max - this.min) / 3;
269+
if (this.valueStart === undefined) {
270+
this.inputStart!.valueAsNumber = this.min + segment;
271+
// read actual value from input
272+
const v = this.inputStart!.valueAsNumber;
273+
this.valueStart = this.renderValueStart = v;
274+
}
275+
if (this.valueEnd === undefined) {
276+
this.inputEnd!.valueAsNumber = this.min + 2 * segment;
277+
// read actual value from input
278+
const v = this.inputEnd!.valueAsNumber;
279+
this.valueEnd = this.renderValueEnd = v;
280+
}
281+
} else {
282+
this.value ??= this.renderValueEnd;
283+
}
272284
if (changed.has('range') || changed.has('renderValueStart') ||
273285
changed.has('renderValueEnd') || this.isUpdatePending) {
274286
this.handlesOverlapping = isOverlapping(this.handleStart, this.handleEnd);
@@ -281,9 +293,10 @@ export class Slider extends LitElement {
281293
protected override render() {
282294
const step = this.step === 0 ? 1 : this.step;
283295
const range = Math.max(this.max - this.min, step);
284-
const startFraction =
285-
this.range ? ((this.renderValueStart - this.min) / range) : 0;
286-
const endFraction = (this.renderValueEnd - this.min) / range;
296+
const startFraction = this.range ?
297+
(((this.renderValueStart ?? this.min) - this.min) / range) :
298+
0;
299+
const endFraction = ((this.renderValueEnd ?? this.min) - this.min) / range;
287300
const containerStyles = {
288301
// for clipping inputs and active track.
289302
'--slider-start-fraction': String(startFraction),
@@ -375,11 +388,8 @@ export class Slider extends LitElement {
375388
</div>`;
376389
}
377390

378-
private renderInput({start, value, label}: {
379-
start: boolean,
380-
value: number,
381-
label: string,
382-
}) {
391+
private renderInput({start, value, label}:
392+
{start: boolean; value?: number; label: string;}) {
383393
const name = start ? `start` : `end`;
384394
// when ranged, ensure announcement includes value info.
385395
// Needed for closure conformance
@@ -619,27 +629,27 @@ export class Slider extends LitElement {
619629
/** @private */
620630
formResetCallback() {
621631
if (this.range) {
622-
this.valueStart =
623-
Number(this.getAttribute('value-start') ?? DEFAULT_VALUE_START);
624-
this.valueEnd =
625-
Number(this.getAttribute('value-end') ?? DEFAULT_VALUE_END);
632+
const valueStart = this.getAttribute('value-start');
633+
this.valueStart = valueStart !== null ? Number(valueStart) : undefined;
634+
const valueEnd = this.getAttribute('value-end');
635+
this.valueEnd = valueEnd !== null ? Number(valueEnd) : undefined;
626636
return;
627637
}
628-
629-
this.value = Number(this.getAttribute('value') ?? DEFAULT_VALUE);
638+
const value = this.getAttribute('value');
639+
this.value = value !== null ? Number(value) : undefined;
630640
}
631641

632642
/** @private */
633643
formStateRestoreCallback(state: string|Array<[string, string]>|null) {
634644
if (Array.isArray(state)) {
635645
const [[, valueStart], [, valueEnd]] = state;
636-
this.valueStart = Number(valueStart ?? DEFAULT_VALUE_START);
637-
this.valueEnd = Number(valueEnd ?? DEFAULT_VALUE_START);
646+
this.valueStart = Number(valueStart);
647+
this.valueEnd = Number(valueEnd);
638648
this.range = true;
639649
return;
640650
}
641651

642-
this.value = Number(state ?? DEFAULT_VALUE);
652+
this.value = Number(state);
643653
this.range = false;
644654
}
645655
}

slider/slider_test.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ function getSliderTemplate(props?: SliderTestProps) {
2727
return html`
2828
<md-slider
2929
.range=${props?.range ?? false}
30-
.value=${props?.value ?? 0}
31-
.valueStart=${props?.valueStart ?? 0}
32-
.valueEnd=${props?.valueEnd ?? 0}
30+
.value=${props?.value}
31+
.valueStart=${props?.valueStart}
32+
.valueEnd=${props?.valueEnd}
3333
.step=${props?.step ?? 1}
3434
.min=${props?.min ?? 0}
3535
.max=${props?.max ?? 100}
@@ -147,7 +147,6 @@ describe('<md-slider>', () => {
147147
it('update via interaction', async () => {
148148
const props = {range: true, valueStart: 2, valueEnd: 6};
149149
const {harness} = await setupTest(props);
150-
expect(harness.element.value).toEqual(0);
151150
const [endInput, startInput] = harness.getInputs();
152151
await harness.simulateValueInteraction(7, endInput);
153152
expect(harness.element.valueStart).toEqual(2);
@@ -346,6 +345,21 @@ describe('<md-slider>', () => {
346345
});
347346
});
348347

348+
describe('default values', () => {
349+
it('defaults value to midway between min/max', async () => {
350+
const {harness} = await setupTest({min: -100, max: -40});
351+
await harness.element.updateComplete;
352+
expect(harness.element.value).toBe(-70);
353+
});
354+
355+
it('defaults valueStart/End to equidistant between min/max', async () => {
356+
const {harness} = await setupTest({range: true, min: 80, max: 100});
357+
await harness.element.updateComplete;
358+
expect(harness.element.valueStart).toBe(87);
359+
expect(harness.element.valueEnd).toBe(93);
360+
});
361+
});
362+
349363
describe('forms', () => {
350364
createFormTests({
351365
queryControl: root => root.querySelector('md-slider'),
@@ -383,6 +397,36 @@ describe('<md-slider>', () => {
383397
expect(formData.get('slider-end')).toBe('10');
384398
}
385399
},
400+
{
401+
name: 'single default value',
402+
render: () => html`<md-slider name="slider"></md-slider>`,
403+
assertValue(formData) {
404+
expect(formData.get('slider')).toBe('50');
405+
}
406+
},
407+
{
408+
name: 'single default value with min/max',
409+
render: () =>
410+
html`<md-slider name="slider" min="100" max="300"></md-slider>`,
411+
assertValue(formData) {
412+
expect(formData.get('slider')).toBe('200');
413+
}
414+
},
415+
{
416+
name: 'multiple default values',
417+
render: () => html`<md-slider range name="slider"></md-slider>`,
418+
assertValue(formData) {
419+
expect(formData.getAll('slider')).toEqual(['33', '67']);
420+
}
421+
},
422+
{
423+
name: 'multiple default values with min/max',
424+
render: () =>
425+
html`<md-slider range name="slider" min="100" max="300"></md-slider>`,
426+
assertValue(formData) {
427+
expect(formData.getAll('slider')).toEqual(['167', '233']);
428+
}
429+
},
386430
{
387431
name: 'disabled',
388432
render: () =>

0 commit comments

Comments
 (0)