Skip to content

Commit

Permalink
feat(slider): Add support for styling initial thumb/track before comp…
Browse files Browse the repository at this point in the history
…onent JS initialization.

PiperOrigin-RevId: 325048567
  • Loading branch information
joyzhong authored and copybara-github committed Aug 5, 2020
1 parent e056052 commit 08ca4d0
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 7 deletions.
45 changes: 45 additions & 0 deletions packages/mdc-slider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,51 @@ as shown below:
</div>
```

### Setting slider position before component initialization

When `MDCSlider` is initialized, it updates the slider track and thumb
positions based on the internal value(s). To set the correct track and thumb
positions before component initialization, mark up the DOM as follows:

- Calculate `rangePercentDecimal`, the active track range as a percentage of
the entire track, i.e. `(valueEnd - valueStart) / (max - min)`.
Set `transform:scaleX(<rangePercentDecimal>)` as an inline style on the
`mdc-slider__track--active_fill` element.
- Calculate `thumbEndPercent`, the initial position of the end thumb as a
percentage of the entire track. Set `left:calc(<thumbEndPercent>% - 24px)`
as an inline style on the end thumb (`mdc-slider__thumb`) element
(or `right` for RTL layouts).
- *[Range sliders only]* Calculate `thumbStartPercent`, the initial position
of the start thumb as a percentage of the entire track. Set
`left:calc(<thumbStartPercent>% - 24px` as an inline style on the
start thumb (`mdc-slider__thumb`) element (or `right` for RTL layouts).
- *[Range sliders only]* Using the previously calculated `thumbStartPercent`,
set `left:<thumbStartPercent>%` as an inline style on the
`mdc-slider__track--active_fill` element (or `right` for RTL layouts).

#### Range slider example

This is an example of a range slider with internal values of
`[min, max] = [0, 100]` and `[start, end] = [30, 70]`.

```html
<div class="mdc-slider mdc-slider--range">
<div class="mdc-slider__track">
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"
style="transform:scaleX(.4); left:30%"></div>
</div>
<div class="mdc-slider__track--inactive"></div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-valuemin="0" aria-valuemax="100" aria-valuenow="30" style="left:calc(30%-24px)">
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb" role="slider" tabindex="0" aria-valuemin="0" aria-valuemax="100" aria-valuenow="70" style="left:calc(70%-24px)">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
```

## API

### Sass mixins
Expand Down
9 changes: 9 additions & 0 deletions packages/mdc-slider/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ export interface MDCSliderAdapter {
setThumbStyleProperty(propertyName: string, value: string, thumb: Thumb):
void;

/**
* Removes the given style property from the thumb element.
* - If thumb is `Thumb.START`, removes style from the start thumb (for
* range slider variant).
* - If thumb is `Thumb.END`, removes style from the end thumb (or only thumb
* for single point slider).
*/
removeThumbStyleProperty(propertyName: string, thumb: Thumb): void;

/**
* Sets a style property of the active track element to the passed value.
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/mdc-slider/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export class MDCSlider extends MDCComponent<MDCSliderFoundation> {
setThumbStyleProperty: (propertyName, value, thumb: Thumb) => {
this.getThumbEl(thumb).style.setProperty(propertyName, value);
},
removeThumbStyleProperty: (propertyName, thumb: Thumb) => {
this.getThumbEl(thumb).style.removeProperty(propertyName);
},
setTrackActiveStyleProperty: (propertyName, value) => {
this.trackActive.style.setProperty(propertyName, value);
},
Expand Down
40 changes: 33 additions & 7 deletions packages/mdc-slider/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const HAS_WINDOW = typeof window !== 'undefined';
export class MDCSliderFoundation extends MDCFoundation<MDCSliderAdapter> {
static SUPPORTS_POINTER_EVENTS = HAS_WINDOW && Boolean(window.PointerEvent);

// Whether the initial styles (to position the thumb, before component
// initialization) have been removed.
private initialStylesRemoved = false;

private min!: number; // Assigned in init()
private max!: number; // Assigned in init()
// If `isRange`, this is the value of Thumb.START. Otherwise, defaults to min.
Expand Down Expand Up @@ -125,6 +129,7 @@ export class MDCSliderFoundation extends MDCFoundation<MDCSliderAdapter> {
({top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0}),
isRTL: () => false,
setThumbStyleProperty: () => undefined,
removeThumbStyleProperty: () => undefined,
setTrackActiveStyleProperty: () => undefined,
setValueIndicatorText: () => undefined,
updateTickMarks: () => undefined,
Expand Down Expand Up @@ -685,6 +690,7 @@ export class MDCSliderFoundation extends MDCFoundation<MDCSliderAdapter> {
const {max, min} = this;
const pctComplete = (this.value - this.valueStart) / (max - min);
const rangePx = pctComplete * this.rect.width;
const isRtl = this.adapter.isRTL();

const transformProp =
HAS_WINDOW ? getCorrectPropertyName(window, 'transform') : 'transform';
Expand All @@ -697,9 +703,8 @@ export class MDCSliderFoundation extends MDCFoundation<MDCSliderAdapter> {
requestAnimationFrame(() => {
// Set active track styles, accounting for animation direction by
// setting `transform-origin`.
const trackAnimatesFromRight =
(!this.adapter.isRTL() && thumb === Thumb.START) ||
(this.adapter.isRTL() && thumb !== Thumb.START);
const trackAnimatesFromRight = (!isRtl && thumb === Thumb.START) ||
(isRtl && thumb !== Thumb.START);
if (trackAnimatesFromRight) {
this.adapter.setTrackActiveStyleProperty('transform-origin', 'right');
this.adapter.setTrackActiveStyleProperty('left', 'unset');
Expand All @@ -714,8 +719,7 @@ export class MDCSliderFoundation extends MDCFoundation<MDCSliderAdapter> {
transformProp, `scaleX(${pctComplete})`);

// Set thumb styles.
const thumbStartPos =
this.adapter.isRTL() ? thumbRightPos : thumbLeftPos;
const thumbStartPos = isRtl ? thumbRightPos : thumbLeftPos;
const thumbEndPos = this.adapter.isRTL() ? thumbLeftPos : thumbRightPos;
if (thumb === Thumb.START || !thumb) {
this.adapter.setThumbStyleProperty(
Expand All @@ -726,23 +730,45 @@ export class MDCSliderFoundation extends MDCFoundation<MDCSliderAdapter> {
transformProp, `translateX(${thumbEndPos}px)`, Thumb.END);
}

this.removeInitialStyles(isRtl);
this.updateOverlappingThumbsUI(thumbStartPos, thumbEndPos, thumb);
this.focusThumbIfDragging(thumb);
});
} else {
requestAnimationFrame(() => {
const thumbStartPos =
this.adapter.isRTL() ? this.rect.width - rangePx : rangePx;
const thumbStartPos = isRtl ? this.rect.width - rangePx : rangePx;
this.adapter.setThumbStyleProperty(
transformProp, `translateX(${thumbStartPos}px)`, Thumb.END);
this.adapter.setTrackActiveStyleProperty(
transformProp, `scaleX(${pctComplete})`);

this.removeInitialStyles(isRtl);
this.focusThumbIfDragging(thumb);
});
}
}

/**
* Removes initial inline styles if not already removed. `left:<...>%`
* inline styles can be added to position the thumb correctly before JS
* initialization. However, they need to be removed before the JS starts
* positioning the thumb. This is because the JS uses
* `transform:translateX(<...>)px` (for performance reasons) to position
* the thumb (which is not possible for initial styles since we need the
* bounding rect measurements).
*/
private removeInitialStyles(isRtl: boolean) {
if (this.initialStylesRemoved) return;

const position = isRtl ? 'right' : 'left';
this.adapter.removeThumbStyleProperty(position, Thumb.END);
if (this.isRange) {
this.adapter.removeThumbStyleProperty(position, Thumb.START);
}

this.initialStylesRemoved = true;
}

/**
* Adds THUMB_TOP class to active thumb if thumb knobs overlap; otherwise
* removes THUMB_TOP class from both thumbs.
Expand Down
25 changes: 25 additions & 0 deletions packages/mdc-slider/test/foundation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,31 @@ describe('MDCSliderFoundation', () => {
});
});

describe('removing initial thumb styles', () => {
it('initial layout removes thumb styles, and subsequent layouts ' +
'do not',
() => {
const {foundation, mockAdapter} = setUpAndInit({isRange: true});
expect(mockAdapter.removeThumbStyleProperty)
.toHaveBeenCalledWith('left', Thumb.END);
expect(mockAdapter.removeThumbStyleProperty)
.toHaveBeenCalledWith('left', Thumb.START);

mockAdapter.removeThumbStyleProperty.calls.reset();
foundation.layout();
jasmine.clock().tick(1);
expect(mockAdapter.removeThumbStyleProperty).not.toHaveBeenCalled();
});

it('RTL: initial layout removes thumb styles', () => {
const {mockAdapter} = setUpAndInit({isRange: true, isRTL: true});
expect(mockAdapter.removeThumbStyleProperty)
.toHaveBeenCalledWith('right', Thumb.END);
expect(mockAdapter.removeThumbStyleProperty)
.toHaveBeenCalledWith('right', Thumb.START);
});
});

describe('#destroy', () => {
it('Pointer events: Event listeners are deregistered when foundation is ' +
'destroyed.',
Expand Down

0 comments on commit 08ca4d0

Please sign in to comment.