diff --git a/packages/slider/src/vaadin-range-slider.js b/packages/slider/src/vaadin-range-slider.js
index 131b690cc40..6cc0a27fbba 100644
--- a/packages/slider/src/vaadin-range-slider.js
+++ b/packages/slider/src/vaadin-range-slider.js
@@ -101,8 +101,6 @@ class RangeSlider extends SliderMixin(
this.__value = [...this.value];
this.__inputId0 = `slider-${generateUniqueId()}`;
this.__inputId1 = `slider-${generateUniqueId()}`;
-
- this.addEventListener('mousedown', (e) => this._onMouseDown(e));
}
/** @protected */
@@ -163,9 +161,9 @@ class RangeSlider extends SliderMixin(
super.updated(props);
if (props.has('value') || props.has('min') || props.has('max')) {
- const value = Array.isArray(this.value) ? this.value : [];
- value.forEach((value, idx) => {
- this.__updateValue(value, idx);
+ const value = [...this.value];
+ value.forEach((v, idx) => {
+ this.__updateValue(v, idx, value);
});
}
}
@@ -200,14 +198,11 @@ class RangeSlider extends SliderMixin(
/**
* @param {PointerEvent} event
- * @protected
+ * @private
*/
- _onMouseDown(event) {
- // Prevent blur if already focused
- event.preventDefault();
-
- // Focus the input to allow modifying value using keyboard
- this.focus({ focusVisible: false });
+ __focusInput(event) {
+ const index = this.__getThumbIndex(event);
+ this._inputElements[index].focus();
}
/** @private */
@@ -215,9 +210,69 @@ class RangeSlider extends SliderMixin(
this.value = [...this.__value];
}
+ /**
+ * @param {Event} event
+ * @return {number}
+ */
+ __getThumbIndex(event) {
+ if (event.type === 'input') {
+ return this._inputElements.indexOf(event.target);
+ }
+
+ return this.__getClosestThumb(event);
+ }
+
+ /**
+ * @param {PointerEvent} event
+ * @return {number}
+ * @private
+ */
+ __getClosestThumb(event) {
+ let closestThumb;
+
+ // If both thumbs are at the start, use the second thumb,
+ // and if both are at tne end, use the first one instead.
+ if (this.__value[0] === this.__value[1]) {
+ const { min, max } = this.__getConstraints();
+ if (this.__value[0] === min) {
+ return 1;
+ }
+
+ if (this.__value[0] === max) {
+ return 0;
+ }
+ }
+
+ const percent = this.__getEventPercent(event);
+ const value = this.__getValueFromPercent(percent);
+
+ // First thumb position from the "end"
+ const index = this.__value.findIndex((v) => value - v < 0);
+
+ // Pick the first one
+ if (index === 0) {
+ closestThumb = index;
+ } else if (index === -1) {
+ // Pick the last one (position is past all the thumbs)
+ closestThumb = this.__value.length - 1;
+ } else {
+ const lastStart = this.__value[index - 1];
+ const firstEnd = this.__value[index];
+ // Pick the first one from the "start" unless thumbs are stacked on top of each other
+ if (Math.abs(lastStart - value) < Math.abs(firstEnd - value)) {
+ closestThumb = index - 1;
+ } else {
+ // Pick the last one from the "end"
+ closestThumb = index;
+ }
+ }
+
+ return closestThumb;
+ }
+
/** @private */
__onKeyDown(event) {
- this.__thumbIndex = this._inputElements.indexOf(event.target);
+ const index = this._inputElements.indexOf(event.target);
const prevKeys = ['ArrowLeft', 'ArrowDown'];
const nextKeys = ['ArrowRight', 'ArrowUp'];
@@ -226,8 +281,7 @@ class RangeSlider extends SliderMixin(
// to prevent the case where slotted range inputs would end up in broken state.
if (
this.__value[0] === this.__value[1] &&
- ((this.__thumbIndex === 0 && nextKeys.includes(event.key)) ||
- (this.__thumbIndex === 1 && prevKeys.includes(event.key)))
+ ((index === 0 && nextKeys.includes(event.key)) || (index === 1 && prevKeys.includes(event.key)))
) {
event.preventDefault();
}
diff --git a/packages/slider/src/vaadin-slider-mixin.js b/packages/slider/src/vaadin-slider-mixin.js
index 1474e5f51cd..4bc4c9c1f36 100644
--- a/packages/slider/src/vaadin-slider-mixin.js
+++ b/packages/slider/src/vaadin-slider-mixin.js
@@ -46,33 +46,56 @@ export const SliderMixin = (superClass) =>
constructor() {
super();
- this.__thumbIndex = 0;
+ this.__onPointerMove = this.__onPointerMove.bind(this);
+ this.__onPointerUp = this.__onPointerUp.bind(this);
+
+ // Use separate mousedown listener for focusing the input, as
+ // pointerdown fires too early and the global `keyboardActive`
+ // flag isn't updated yet, which incorrectly shows focus-ring
+ this.addEventListener('mousedown', (e) => this.__onMouseDown(e));
+ this.addEventListener('pointerdown', (e) => this.__onPointerDown(e));
+ }
+
+ /** @protected */
+ firstUpdated() {
+ super.firstUpdated();
+
+ this.__lastCommittedValue = this.value;
+ }
+
+ /**
+ * @param {Event} event
+ * @return {number}
+ */
+ __getThumbIndex(_event) {
+ return 0;
}
/**
* @param {number} value
* @param {number} index
+ * @param {number[]} fullValue
* @private
*/
- __updateValue(value, index = this.__thumbIndex) {
+ __updateValue(value, index, fullValue = this.__value) {
const { min, max, step } = this.__getConstraints();
- const minValue = this.__value[index - 1] || min;
- const maxValue = this.__value[index + 1] || max;
+ const minValue = fullValue[index - 1] !== undefined ? fullValue[index - 1] : min;
+ const maxValue = fullValue[index + 1] !== undefined ? fullValue[index + 1] : max;
const safeValue = Math.min(Math.max(value, minValue), maxValue);
- const offset = safeValue - min;
+ const offset = safeValue - minValue;
const nearestOffset = Math.round(offset / step) * step;
- const nearestValue = min + nearestOffset;
+ const nearestValue = minValue + nearestOffset;
const newValue = Math.round(nearestValue);
- this.__value = this.__value.with(index, newValue);
+ this.__value = fullValue.with(index, newValue);
}
/**
- * @return {{ min: number, max: number}}
+ * @return {{ min: number, max: number, step: number}}
* @private
*/
__getConstraints() {
@@ -93,24 +116,141 @@ export const SliderMixin = (superClass) =>
return (100 * (value - min)) / (max - min);
}
+ /**
+ * @param {number} percent
+ * @return {number}
+ * @private
+ */
+ __getValueFromPercent(percent) {
+ const { min, max } = this.__getConstraints();
+ return min + percent * (max - min);
+ }
+
+ /**
+ * @param {PointerEvent} event
+ * @return {number}
+ * @private
+ */
+ __getEventPercent(event) {
+ const offset = event.offsetX;
+ const size = this.offsetWidth;
+ const safeOffset = Math.min(Math.max(offset, 0), size);
+ return safeOffset / size;
+ }
+
+ /**
+ * @param {PointerEvent} event
+ * @return {number}
+ * @private
+ */
+ __getEventValue(event) {
+ const percent = this.__getEventPercent(event);
+ return this.__getValueFromPercent(percent);
+ }
+
+ /**
+ * @param {PointerEvent} event
+ * @private
+ */
+ __onMouseDown(event) {
+ const part = event.composedPath()[0].getAttribute('part');
+ if (!part || (!part.startsWith('track') && !part.startsWith('thumb'))) {
+ return;
+ }
+
+ // Prevent losing focus
+ event.preventDefault();
+
+ this.__focusInput(event);
+ }
+
+ /**
+ * @param {PointerEvent} event
+ * @private
+ */
+ __onPointerDown(event) {
+ if (event.button !== 0) {
+ return;
+ }
+
+ const part = event.composedPath()[0].getAttribute('part');
+ if (!part || (!part.startsWith('track') && !part.startsWith('thumb'))) {
+ return;
+ }
+
+ this.setPointerCapture(event.pointerId);
+ this.addEventListener('pointermove', this.__onPointerMove);
+ this.addEventListener('pointerup', this.__onPointerUp);
+ this.addEventListener('pointercancel', this.__onPointerUp);
+
+ this.__thumbIndex = this.__getThumbIndex(event);
+
+ // Update value on track click
+ if (part.startsWith('track')) {
+ const newValue = this.__getEventValue(event);
+ this.__updateValue(newValue, this.__thumbIndex);
+ this.__commitValue();
+ }
+ }
+
+ /**
+ * @param {PointerEvent} event
+ * @private
+ */
+ __onPointerMove(event) {
+ const newValue = this.__getEventValue(event);
+ this.__updateValue(newValue, this.__thumbIndex);
+ this.__commitValue();
+ }
+
+ /**
+ * @param {PointerEvent} event
+ * @private
+ */
+ __onPointerUp(event) {
+ this.__thumbIndex = null;
+
+ this.releasePointerCapture(event.pointerId);
+ this.removeEventListener('pointermove', this.__onPointerMove);
+ this.removeEventListener('pointerup', this.__onPointerUp);
+ this.removeEventListener('pointercancel', this.__onPointerUp);
+
+ this.__detectAndDispatchChange();
+ }
+
+ /**
+ * @param {Event} event
+ * @private
+ */
+ __focusInput(_event) {
+ this.focus({ focusVisible: false });
+ }
+
/** @private */
__detectAndDispatchChange() {
- if (this.__lastCommittedValue !== this.value) {
+ if (JSON.stringify(this.__lastCommittedValue) !== JSON.stringify(this.value)) {
this.__lastCommittedValue = this.value;
this.dispatchEvent(new Event('change', { bubbles: true }));
}
}
- /** @private */
+ /**
+ * @param {Event} event
+ * @private
+ */
__onInput(event) {
- this.__updateValue(event.target.value);
+ const index = this.__getThumbIndex(event);
+ this.__updateValue(event.target.value, index);
this.__commitValue();
- this.__detectAndDispatchChange();
}
- /** @private */
+ /**
+ * @param {Event} event
+ * @private
+ */
__onChange(event) {
event.stopPropagation();
+ this.__detectAndDispatchChange();
}
/**
diff --git a/packages/slider/src/vaadin-slider.js b/packages/slider/src/vaadin-slider.js
index e1b365abe0d..44eaad6f11b 100644
--- a/packages/slider/src/vaadin-slider.js
+++ b/packages/slider/src/vaadin-slider.js
@@ -95,8 +95,6 @@ class Slider extends SliderMixin(
this.__value = [this.value];
this.__inputId = `slider-${generateUniqueId()}`;
-
- this.addEventListener('mousedown', (e) => this._onMouseDown(e));
}
/** @protected */
@@ -167,18 +165,6 @@ class Slider extends SliderMixin(
__commitValue() {
this.value = this.__value[0];
}
-
- /**
- * @param {PointerEvent} event
- * @protected
- */
- _onMouseDown(event) {
- // Prevent blur if already focused
- event.preventDefault();
-
- // Focus the input to allow modifying value using keyboard
- this.focus({ focusVisible: false });
- }
}
defineCustomElement(Slider);
diff --git a/packages/slider/test/range-slider.test.ts b/packages/slider/test/range-slider.test.ts
index 77d0e40c911..659ee825399 100644
--- a/packages/slider/test/range-slider.test.ts
+++ b/packages/slider/test/range-slider.test.ts
@@ -1,6 +1,6 @@
import { expect } from '@vaadin/chai-plugins';
-import { sendKeys } from '@vaadin/test-runner-commands';
-import { fixtureSync, nextRender } from '@vaadin/testing-helpers';
+import { resetMouse, sendKeys, sendMouse, sendMouseToElement } from '@vaadin/test-runner-commands';
+import { fixtureSync, isFirefox, middleOfNode, nextRender } from '@vaadin/testing-helpers';
import sinon from 'sinon';
import '../vaadin-range-slider.js';
import type { RangeSlider } from '../vaadin-range-slider.js';
@@ -249,4 +249,275 @@ describe('vaadin-range-slider', () => {
});
});
});
+
+ // Pointer tests randomly fail in Firefox
+ (isFirefox ? describe.skip : describe)('pointer', () => {
+ let thumbs: Element[];
+ let inputs: HTMLInputElement[];
+
+ function middleOfThumb(idx: number) {
+ const { x, y } = middleOfNode(thumbs[idx]);
+ return {
+ x: Math.round(x),
+ y: Math.round(y),
+ };
+ }
+
+ beforeEach(async () => {
+ slider = fixtureSync('');
+ await nextRender();
+ thumbs = [...slider.shadowRoot!.querySelectorAll('[part~="thumb"]')];
+ inputs = [...slider.querySelectorAll('input')];
+ });
+
+ afterEach(async () => {
+ await resetMouse();
+ });
+
+ describe('individual thumbs', () => {
+ it('should update slider value property on first thumb pointermove', async () => {
+ const { x, y } = middleOfThumb(0);
+
+ await sendMouseToElement({ type: 'move', element: thumbs[0] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x + 20, y] });
+
+ expect(slider.value).to.deep.equal([10, 100]);
+ });
+
+ it('should update slider value property on second thumb pointermove', async () => {
+ const { x, y } = middleOfThumb(1);
+
+ await sendMouseToElement({ type: 'move', element: thumbs[1] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x - 20, y] });
+
+ expect(slider.value).to.deep.equal([0, 90]);
+ });
+
+ it('should only fire change event on thumb pointerup but not pointermove', async () => {
+ const { x, y } = middleOfThumb(0);
+
+ const spy = sinon.spy();
+ slider.addEventListener('change', spy);
+
+ await sendMouseToElement({ type: 'move', element: thumbs[0] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x + 20, y] });
+
+ expect(spy).to.be.not.called;
+
+ await sendMouse({ type: 'up' });
+ expect(spy).to.be.calledOnce;
+ });
+
+ it('should fire change event on pointerup outside of the element', async () => {
+ const spy = sinon.spy();
+ slider.addEventListener('change', spy);
+
+ await sendMouseToElement({ type: 'move', element: thumbs[0] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [20, 100] });
+ await sendMouse({ type: 'up' });
+
+ expect(spy).to.be.calledOnce;
+ });
+
+ it('should not fire change event on pointerup if value remains the same', async () => {
+ const { x, y } = middleOfThumb(0);
+
+ const spy = sinon.spy();
+ slider.addEventListener('change', spy);
+
+ await sendMouseToElement({ type: 'move', element: thumbs[0] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x + 20, y] });
+
+ await sendMouse({ type: 'move', position: [x, y] });
+ await sendMouse({ type: 'up' });
+
+ expect(spy).to.be.not.called;
+ });
+ });
+
+ describe('track', () => {
+ beforeEach(() => {
+ slider.value = [20, 80];
+ });
+
+ it('should focus first input on track pointerdown before the first thumb', async () => {
+ const { x, y } = middleOfThumb(0);
+
+ await sendMouse({ type: 'move', position: [x - 20, y] });
+ await sendMouse({ type: 'down' });
+
+ expect(slider.value).to.deep.equal([10, 80]);
+ expect(document.activeElement).to.equal(inputs[0]);
+ });
+
+ it('should focus second input on track pointerdown after the second thumb', async () => {
+ const { x, y } = middleOfThumb(1);
+
+ await sendMouse({ type: 'move', position: [x + 20, y] });
+ await sendMouse({ type: 'down' });
+
+ expect(slider.value).to.deep.equal([20, 90]);
+ expect(document.activeElement).to.equal(inputs[1]);
+ });
+
+ it('should focus first input on track pointerdown between thumbs closer to the first one', async () => {
+ const { x, y } = middleOfThumb(0);
+
+ await sendMouse({ type: 'move', position: [x + 40, y] });
+ await sendMouse({ type: 'down' });
+
+ expect(slider.value).to.deep.equal([40, 80]);
+ expect(document.activeElement).to.equal(inputs[0]);
+ });
+
+ it('should focus second input on track pointerdown between thumbs closer to the second one', async () => {
+ const { x, y } = middleOfThumb(1);
+
+ await sendMouse({ type: 'move', position: [x - 40, y] });
+ await sendMouse({ type: 'down' });
+
+ expect(slider.value).to.deep.equal([20, 60]);
+ expect(document.activeElement).to.equal(inputs[1]);
+ });
+
+ it('should not focus any of inputs on pointerdown below the track', async () => {
+ const { y } = middleOfThumb(0);
+
+ await sendMouse({ type: 'move', position: [50, y + 10] });
+ await sendMouse({ type: 'down' });
+ inputs.forEach((input) => {
+ expect(document.activeElement).to.not.equal(input);
+ });
+ });
+
+ it('should only fire change event on track pointerup', async () => {
+ const { x, y } = middleOfThumb(0);
+
+ const spy = sinon.spy();
+ slider.addEventListener('change', spy);
+
+ await sendMouse({ type: 'move', position: [x - 20, y] });
+ await sendMouse({ type: 'down' });
+
+ expect(spy).to.be.not.called;
+ await sendMouse({ type: 'up' });
+
+ expect(spy).to.be.calledOnce;
+ });
+ });
+
+ describe('thumbs limits', () => {
+ beforeEach(() => {
+ slider.value = [40, 60];
+ });
+
+ it('should use the first thumb position as a min limit on second thumb pointermove', async () => {
+ const { x, y } = middleOfThumb(1);
+
+ await sendMouseToElement({ type: 'move', element: thumbs[0] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x + 20, y] });
+ await sendMouse({ type: 'up' });
+
+ expect(slider.value).to.deep.equal([60, 60]);
+ });
+
+ it('should use the second thumb position as a max limit on first thumb pointermove', async () => {
+ const { x, y } = middleOfThumb(0);
+
+ await sendMouseToElement({ type: 'move', element: thumbs[1] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x - 20, y] });
+ await sendMouse({ type: 'up' });
+
+ expect(slider.value).to.deep.equal([40, 40]);
+ });
+
+ it('should use the first thumb position as a min limit on when min is a negative value', async () => {
+ slider.min = -10;
+ slider.max = 10;
+ slider.value = [0, 1];
+
+ const { x, y } = middleOfThumb(1);
+
+ await sendMouseToElement({ type: 'move', element: thumbs[1] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x + -40, y] });
+ await sendMouse({ type: 'up' });
+
+ expect(slider.value).to.deep.equal([0, 0]);
+ });
+ });
+
+ describe('thumbs on top of each other', () => {
+ let x: number, y: number;
+
+ beforeEach(() => {
+ slider.value = [50, 50];
+ ({ x, y } = middleOfThumb(0));
+ });
+
+ it('should focus first input and move first thumb on pointerdown closer to the left', async () => {
+ await sendMouse({ type: 'move', position: [x - 5, y] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x - 20, y] });
+
+ expect(document.activeElement).to.equal(inputs[0]);
+ expect(slider.value).to.deep.equal([40, 50]);
+ });
+
+ it('should not move first thumb to the right on pointerdown closer to the left', async () => {
+ await sendMouse({ type: 'move', position: [x - 5, y] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x + 20, y] });
+
+ expect(slider.value).to.deep.equal([50, 50]);
+ });
+
+ it('should focus second input and move second thumb on pointerdown closer to the right', async () => {
+ await sendMouse({ type: 'move', position: [x + 5, y] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x + 20, y] });
+
+ expect(slider.value).to.deep.equal([50, 60]);
+ expect(document.activeElement).to.equal(inputs[1]);
+ });
+
+ it('should not move second thumb to the left on pointerdown closer to the right', async () => {
+ await sendMouse({ type: 'move', position: [x + 5, y] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x - 20, y] });
+
+ expect(slider.value).to.deep.equal([50, 50]);
+ });
+
+ it('should always move second thumb when both thumbs are at the start', async () => {
+ slider.value = [0, 0];
+ x = middleOfThumb(0).x;
+
+ await sendMouse({ type: 'move', position: [x - 5, y] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x + 20, y] });
+
+ expect(slider.value).to.deep.equal([0, 10]);
+ });
+
+ it('should always move first thumb when both thumbs are at the end', async () => {
+ slider.value = [100, 100];
+ x = middleOfThumb(1).x;
+ debugger;
+
+ await sendMouse({ type: 'move', position: [x + 5, y] });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [x - 20, y] });
+
+ expect(slider.value).to.deep.equal([90, 100]);
+ });
+ });
+ });
});
diff --git a/packages/slider/test/slider.test.ts b/packages/slider/test/slider.test.ts
index 7dfd418f5e9..5bec70927dc 100644
--- a/packages/slider/test/slider.test.ts
+++ b/packages/slider/test/slider.test.ts
@@ -1,6 +1,6 @@
import { expect } from '@vaadin/chai-plugins';
-import { sendKeys } from '@vaadin/test-runner-commands';
-import { fixtureSync, nextRender } from '@vaadin/testing-helpers';
+import { resetMouse, sendKeys, sendMouse, sendMouseToElement } from '@vaadin/test-runner-commands';
+import { fixtureSync, middleOfNode, nextRender } from '@vaadin/testing-helpers';
import sinon from 'sinon';
import '../vaadin-slider.js';
import type { Slider } from '../vaadin-slider.js';
@@ -119,4 +119,109 @@ describe('vaadin-slider', () => {
expect(spy).to.be.calledOnce;
});
});
+
+ describe('pointer', () => {
+ let thumb: Element;
+ let y: number;
+
+ beforeEach(async () => {
+ slider = fixtureSync('');
+ await nextRender();
+ thumb = slider.shadowRoot!.querySelector('[part="thumb"]')!;
+ y = Math.round(middleOfNode(thumb).y);
+ });
+
+ afterEach(async () => {
+ await resetMouse();
+ });
+
+ it('should update slider value property on thumb pointermove', async () => {
+ await sendMouseToElement({ type: 'move', element: thumb });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [20, y] });
+
+ expect(slider.value).to.equal(10);
+ });
+
+ it('should only fire change event on thumb pointerup but not pointermove', async () => {
+ const spy = sinon.spy();
+ slider.addEventListener('change', spy);
+
+ await sendMouseToElement({ type: 'move', element: thumb });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [20, y] });
+
+ expect(spy).to.be.not.called;
+
+ await sendMouse({ type: 'up' });
+ expect(spy).to.be.calledOnce;
+ });
+
+ it('should fire change event on pointerup outside of the element', async () => {
+ const spy = sinon.spy();
+ slider.addEventListener('change', spy);
+
+ await sendMouseToElement({ type: 'move', element: thumb });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [20, y + 100] });
+ await sendMouse({ type: 'up' });
+
+ expect(spy).to.be.calledOnce;
+ });
+
+ it('should not fire change event on pointerup if value remains the same', async () => {
+ const spy = sinon.spy();
+ slider.addEventListener('change', spy);
+
+ await sendMouseToElement({ type: 'move', element: thumb });
+ await sendMouse({ type: 'down' });
+ await sendMouse({ type: 'move', position: [20, y] });
+
+ await sendMouse({ type: 'move', position: [0, y] });
+ await sendMouse({ type: 'up' });
+
+ expect(spy).to.be.not.called;
+ });
+
+ it('should update slider value property on track pointerdown', async () => {
+ const track = slider.shadowRoot!.querySelector('[part="track"]')!;
+
+ await sendMouseToElement({ type: 'move', element: track });
+ await sendMouse({ type: 'down' });
+
+ expect(slider.value).to.equal(50);
+ });
+
+ it('should only fire change event on track pointerup', async () => {
+ const track = slider.shadowRoot!.querySelector('[part="track"]')!;
+
+ const spy = sinon.spy();
+ slider.addEventListener('change', spy);
+
+ await sendMouseToElement({ type: 'move', element: track });
+ await sendMouse({ type: 'down' });
+ expect(spy).to.be.not.called;
+
+ await sendMouse({ type: 'up' });
+ expect(spy).to.be.calledOnce;
+ });
+
+ it('should focus slotted range input on thumb pointerdown', async () => {
+ await sendMouseToElement({ type: 'move', element: thumb });
+ await sendMouse({ type: 'down' });
+ expect(document.activeElement).to.equal(slider.querySelector('input'));
+ });
+
+ it('should focus slotted range input on track pointerdown', async () => {
+ await sendMouse({ type: 'move', position: [50, y] });
+ await sendMouse({ type: 'down' });
+ expect(document.activeElement).to.equal(slider.querySelector('input'));
+ });
+
+ it('should not focus slotted range input on pointerdown below the track', async () => {
+ await sendMouse({ type: 'move', position: [50, y + 5] });
+ await sendMouse({ type: 'down' });
+ expect(document.activeElement).to.not.equal(slider.querySelector('input'));
+ });
+ });
});