fix(slider): fixes label focus and ranged handle dragging on Safari

PiperOrigin-RevId: 526016986
This commit is contained in:
Material Web Team 2023-04-21 06:27:47 -07:00 committed by Copybara-Service
parent 356d1bc9f8
commit 72b48da7cc
3 changed files with 36 additions and 48 deletions

View File

@ -70,4 +70,11 @@ export class SliderHarness extends Harness<Slider> {
}
super.simulateStartHover(element, init);
}
protected override simulateMousePress(
element: HTMLElement, init: PointerEventInit = {}) {
super.simulateMousePress(element, init);
// advance beyond RAF, which is used by the element's pointerDown handler.
jasmine.clock().tick(1);
}
}

View File

@ -266,17 +266,18 @@ $_md-sys-shape: tokens.md-sys-shape-values();
background: var(--_disabled-handle-color);
}
.input.b:focus ~ .handleContainer .handle.b > slot > .handleNub,
.input.a:focus ~ .handleContainer .handle.a > slot > .handleNub {
input.b:focus ~ .handleContainerPadded .handle.b > .handleNub,
input.a:focus ~ .handleContainerPadded .handle.a > .handleNub {
background: var(--_focus-handle-color);
}
// prefix classes exist to overcome specificity of focus styling.
.container > .handleContainer .handle.hover > slot > .handleNub {
.container > .handleContainerPadded .handle.hover > .handleNub {
background: var(--_hover-handle-color);
}
.container > .handleContainer .handle.pressed > slot > .handleNub {
input.b:active ~ .handleContainerPadded .handle.b > .handleNub,
input.a:active ~ .handleContainerPadded .handle.a > .handleNub {
background: var(--_pressed-handle-color);
}
@ -312,9 +313,8 @@ $_md-sys-shape: tokens.md-sys-shape-values();
min-inline-size: var(--_label-container-height);
min-block-size: var(--_label-container-height);
background: var(--_label-container-color);
transition-property: transform;
transition-duration: map.get($_md-sys-motion, 'duration-short2');
transition-timing-function: map.get($_md-sys-motion, 'easing-emphasized');
transition: transform map.get($_md-sys-motion, 'duration-short2')
map.get($_md-sys-motion, 'easing-emphasized');
transform-origin: center bottom;
transform: scale(0);
}
@ -387,8 +387,11 @@ $_md-sys-shape: tokens.md-sys-shape-values();
::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
// note, this is sized to align with thumb but is 0 width so that
// fine adjustments are possible
block-size: var(--_state-layer-size);
inline-size: var(--_state-layer-size);
transform: scaleX(0);
opacity: 0;
z-index: 2;
}
@ -397,6 +400,7 @@ $_md-sys-shape: tokens.md-sys-shape-values();
appearance: none;
block-size: var(--_state-layer-size);
inline-size: var(--_state-layer-size);
transform: scaleX(0);
opacity: 0;
z-index: 2;
}

View File

@ -201,8 +201,6 @@ export class Slider extends LitElement {
// interaction targets.
@state() private handleAHover = false;
@state() private handleBHover = false;
@state() private handleAPressed = false;
@state() private handleBPressed = false;
@state() private onTopId = 'b';
@state() private handlesOverlapping = false;
@ -323,7 +321,6 @@ export class Slider extends LitElement {
focusRequested: this.focusRingARequested,
showFocus: this.focusRingAShowing,
hover: this.handleAHover,
pressed: this.handleAPressed,
label: labelA
};
@ -334,7 +331,6 @@ export class Slider extends LitElement {
focusRequested: this.focusRingBRequested,
showFocus: this.focusRingBShowing,
hover: this.handleBHover,
pressed: this.handleBPressed,
label: labelB
};
@ -363,10 +359,7 @@ export class Slider extends LitElement {
protected renderTrack() {
const trackClasses = {'tickMarks': this.withTickMarks};
return html`
<slot name="track">
<div class="track ${classMap(trackClasses)}"></div>
</slot>`;
return html`<div class="track ${classMap(trackClasses)}"></div>`;
}
protected renderFocusRing(visible: boolean) {
@ -379,41 +372,27 @@ export class Slider extends LitElement {
</div>`;
}
protected renderHandle({
id,
lesser,
showRipple,
focusRequested,
showFocus,
hover,
pressed,
label
}: {
id: string,
lesser: boolean,
focusRequested: boolean,
showRipple: boolean,
showFocus: boolean,
hover: boolean,
pressed: boolean,
label: string
}) {
protected renderHandle(
{id, lesser, showRipple, focusRequested, showFocus, hover, label}: {
id: string,
lesser: boolean,
focusRequested: boolean,
showRipple: boolean,
showFocus: boolean,
hover: boolean,
label: string
}) {
const onTop = !this.disabled && id === this.onTopId;
const isOverlapping = !this.disabled && this.handlesOverlapping;
return html`<div class="handle ${classMap({
[id]: true,
lesser,
hover,
pressed,
onTop,
isOverlapping
})}">
<slot name="handle${
this.allowRange ? (lesser ? 'Lesser' : 'Greater') : ''}">
<div class="handleNub"><md-elevation></md-elevation></div>
${when(this.withLabel, () => this.renderLabel(label))}
</slot>
${when(showRipple, () => this.renderRipple(id))}
${when(focusRequested, () => this.renderFocusRing(showFocus))}
</div>`;
@ -437,7 +416,6 @@ export class Slider extends LitElement {
@focus=${this.handleFocus}
@blur=${this.handleBlur}
@pointerdown=${this.handleDown}
@pointerup=${this.handleUp}
@pointerenter=${this.handleEnter}
@pointermove=${this.handleMove}
@pointerleave=${this.handleLeave}
@ -517,18 +495,17 @@ export class Slider extends LitElement {
pointerPress();
this.ripplePointerId = e.pointerId;
const isA = this.isEventOnA(e);
const isPrimaryButton = Boolean(e.buttons & 1);
// Since handle moves to pointer on down and there may not be a move,
// it needs to be considered hovered..
this.handleAHover = !this.disabled && isA && Boolean(this.handleA);
this.handleAPressed = isPrimaryButton && this.handleAHover;
this.handleBHover = !this.disabled && !isA && Boolean(this.handleB);
this.handleBPressed = isPrimaryButton && this.handleBHover;
this.updateFocusVisible(e);
}
protected handleUp() {
this.handleAPressed = this.handleBPressed = false;
// Force Safari to focus input so the label stays displayed; note,
// Macs don't normally focus non-text type inputs.
const target = (e.target as HTMLElement);
requestAnimationFrame(() => {
target.focus();
});
}
/**
@ -568,7 +545,7 @@ export class Slider extends LitElement {
}
this.valueB = this.inputB.valueAsNumber;
this.updateOnTop(e);
// update value only on interaction
// update value only on interaction
const lower = Math.min(this.valueA, this.valueB);
const upper = Math.max(this.valueA, this.valueB);
this.value = this.allowRange ? [lower, upper] : this.valueB;