Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: "input" and "change" events #133

Merged
merged 2 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/range-slider-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default class RangeSliderElement extends HTMLElement {
static formAssociated = true;

#internals;
#startValue;
#value = [];
#valuePercent = [];
#thumbIndex = 0;
Expand Down Expand Up @@ -203,6 +204,9 @@ export default class RangeSliderElement extends HTMLElement {
window.addEventListener('pointerup', this.#endHandler);
window.addEventListener('pointercancel', this.#endHandler);

// Update value change reference
this.#startValue = this.value;

// Thumb click
if (event.target.dataset.thumb !== undefined) {
this.#thumbIndex = Number(event.target.dataset.thumb);
Expand All @@ -227,8 +231,10 @@ export default class RangeSliderElement extends HTMLElement {
window.removeEventListener('pointerup', this.#endHandler);
window.removeEventListener('pointercancel', this.#endHandler);

// TODO: check if value changed
this.dispatchEvent(new Event('change', { bubbles: true }));
// Trigger change event
if (this.#startValue !== this.value) {
this.dispatchEvent(new Event('change', { bubbles: true }));
}
};

#keyboardHandler = (event) => {
Expand All @@ -253,7 +259,7 @@ export default class RangeSliderElement extends HTMLElement {
const safeOffset = Math.min(Math.max(offset, 0), this.#size);
const percent = safeOffset / this.#size;
const computedValue = this.#getValueFromPercent(this.#isRTL ? 1 - percent : percent);
this.#updateValue(this.#thumbIndex, computedValue);
this.#updateValue(this.#thumbIndex, computedValue, ['input']);
};

#getDefaultValue() {
Expand Down Expand Up @@ -321,9 +327,9 @@ export default class RangeSliderElement extends HTMLElement {
*
* @param {number} index
* @param {number} value
* @param {Array} dispatchEvents
* @param {string[]} dispatchEvents
*/
#updateValue(index, value, dispatchEvents = ['input']) {
#updateValue(index, value, dispatchEvents = []) {
const oldValue = this.#value[index];
const valuePrecision = Number(this.valuePrecision) || getPrescision(this.step) || 0;
const thumbMinValue = this.#value[index - 1] || this.min;
Expand Down
46 changes: 42 additions & 4 deletions test/range-slider.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { userEvent } from '@vitest/browser/context';
import { expect, test } from 'vitest';
import { expect, test, vi } from 'vitest';
import '../src/index.js';

const render = (html) => {
Expand Down Expand Up @@ -39,16 +39,39 @@ test('HTML form support', async () => {
expect(element).toHaveValue('50');
});

test('value attribute', async () => {
render('<range-slider value="20"></range-slider>');

const element = document.querySelector('range-slider');
const handleEvent = vi.fn();

element.addEventListener('input', handleEvent);
element.addEventListener('change', handleEvent);

expect(element).toHaveValue('20');

// Make sure that no events have been dispatched for initial value attribute
expect(handleEvent).not.toHaveBeenCalled();
});

test('programmatic value changes', async () => {
render('<range-slider></range-slider>');

const element = document.querySelector('range-slider');
const handleEvent = vi.fn();

element.addEventListener('input', handleEvent);
element.addEventListener('change', handleEvent);

element.value = 20;
expect(element).toHaveValue('20');

element.setAttribute('value', 10);
expect(element).toHaveValue('10');

// Ensure that no events are dispatched for programmatic value changes.
// Matching the default browser behavior.
expect(handleEvent).not.toHaveBeenCalled();
});

test('disabled attribute', async () => {
Expand Down Expand Up @@ -114,19 +137,34 @@ test('thumb click does not update the value', async () => {
const element = document.querySelector('range-slider');
const thumb = element.querySelector('[data-runnable-track] [data-thumb]');
const value = element.value;
const handleEvent = vi.fn();

element.addEventListener('input', handleEvent);
element.addEventListener('change', handleEvent);

await userEvent.click(thumb);
expect(element).toHaveValue(String(value));

// Make sure that no events have been dispatched
expect(handleEvent).not.toHaveBeenCalled();
});

test('track click updates the value', async () => {
test('track click updates the value and sends events', async () => {
render('<range-slider max="42"></range-slider>');

const element = document.querySelector('range-slider');
const handleInputEvent = vi.fn();
const handleChangeEvent = vi.fn();

element.addEventListener('input', handleInputEvent);
element.addEventListener('change', handleChangeEvent);

await userEvent.click(element, { position: { x: element.offsetWidth - 1, y: 5 } });
expect(element).toHaveValue(String(42));

await userEvent.click(element, { position: { x: 1, y: 5 } });
expect(element).toHaveValue(String(0));
// Should dispatch "input" event
expect(handleInputEvent).toHaveBeenCalled();

// Should dispatch "change" event
expect(handleChangeEvent).toHaveBeenCalled();
});
Loading