Skip to content

Commit b969be8

Browse files
authored
Merge pull request #51 from TheCodeRaccoons/Adding-cache-layer
Adding cache layer
2 parents b3f4527 + 62faca8 commit b969be8

2 files changed

Lines changed: 220 additions & 62 deletions

File tree

Dist/Functional/RangeSlider.js

Lines changed: 104 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ constructor(wrapper) {
111111
try {
112112
this.wrapper = wrapper;
113113
this.slider = wrapper.querySelector('[wt-rangeslider-element="slider"]');
114+
// Guard flag to avoid recursive updates when syncing external inputs
115+
this.__suspendExternalSync = false;
114116

115117
if (!this.slider) {
116118
throw new Error('Slider element not found within wrapper');
@@ -140,7 +142,12 @@ constructor(wrapper) {
140142
* @private
141143
*/
142144
addStyles() {
145+
// Inject styles once per document
146+
const existing = document.getElementById('wt-rangeslider-styles');
147+
if (existing) return;
148+
143149
const style = document.createElement('style');
150+
style.id = 'wt-rangeslider-styles';
144151
style.textContent = `
145152
[wt-rangeslider-element="slider"] {
146153
position: relative;
@@ -266,12 +273,12 @@ initElements() {
266273
setupThumbStyles() {
267274
const setupThumb = (thumb, input) => {
268275
// Ensure the input's thumb aligns with our custom thumb
269-
const thumbWidth = thumb.offsetWidth;
276+
const thumbWidth = thumb.offsetWidth || parseInt(getComputedStyle(thumb).width) || 20;
270277
input.style.setProperty('--thumb-width', `${thumbWidth}px`);
271278

272279
// Apply styles to ensure proper positioning and hit areas
273280
thumb.style.position = 'absolute';
274-
thumb.style.pointerEvents = 'none'; // Let clicks pass through to input
281+
thumb.style.pointerEvents = 'none';
275282

276283
// Create a custom property for the thumb offset
277284
this.slider.style.setProperty('--thumb-offset', `${thumbWidth / 2}px`);
@@ -334,12 +341,17 @@ updateLeftValues(value) {
334341

335342
// Update form input
336343
if (this.rangeStart) {
337-
this.rangeStart.value = constrainedValue;
344+
// Avoid recursive reaction to our own programmatic updates
345+
const valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
346+
this.__suspendExternalSync = true;
347+
valueProp.set.call(this.rangeStart, constrainedValue);
348+
this.__suspendExternalSync = false;
338349
}
339350

340351
// Update display
341352
if (this.displayStart) {
342-
this.displayStart.innerHTML = this.shouldFormatNumber === 'true' ? this.formatNumber(constrainedValue) : constrainedValue;
353+
const displayLeft = this.shouldFormatNumber === 'true' ? this.formatNumber(constrainedValue) : constrainedValue;
354+
this.displayStart.textContent = String(displayLeft);
343355
}
344356

345357
// Update visual position
@@ -366,19 +378,23 @@ updateRightValues(value) {
366378

367379
// Update form input
368380
if (this.rangeEnd) {
369-
this.rangeEnd.value = constrainedValue;
381+
// Avoid recursive reaction to our own programmatic updates
382+
const valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
383+
this.__suspendExternalSync = true;
384+
valueProp.set.call(this.rangeEnd, constrainedValue);
385+
this.__suspendExternalSync = false;
370386
}
371387

372388
// Update display
373389
if (this.displayEnd) {
374390
let finalDisplay = this.shouldFormatNumber === 'true' ? this.formatNumber(constrainedValue) : constrainedValue;
375391

376392
if (this.rightSuffix && value >= this.sliderMax) {
377-
this.displayEnd.innerHTML = `${finalDisplay}${this.rightSuffix}`;
393+
this.displayEnd.textContent = `${finalDisplay}${this.rightSuffix}`;
378394
} else if (this.defaultSuffix) {
379-
this.displayEnd.innerHTML = `${finalDisplay}${this.defaultSuffix}`;
395+
this.displayEnd.textContent = `${finalDisplay}${this.defaultSuffix}`;
380396
} else {
381-
this.displayEnd.innerHTML = finalDisplay;
397+
this.displayEnd.textContent = String(finalDisplay);
382398
}
383399
}
384400

@@ -413,55 +429,31 @@ setupEventListeners() {
413429

414430
// Watch for changes to the form inputs
415431
if (this.rangeStart) {
432+
// React to user typing immediately
416433
this.rangeStart.addEventListener('input', (e) => {
417434
this.updateLeftValues(e.target.value);
418435
});
419-
420-
// Watch for programmatic changes
421-
const startObserver = new MutationObserver((mutations) => {
422-
mutations.forEach((mutation) => {
423-
if (
424-
mutation.type === 'attributes' &&
425-
mutation.attributeName === 'value'
426-
) {
427-
const value = parseInt(this.rangeStart.value);
428-
if (
429-
!isNaN(value) &&
430-
value >= this.sliderMin &&
431-
value <= this.sliderMax
432-
) {
433-
this.updateLeftValues(value);
434-
}
435-
}
436-
});
436+
this.rangeStart.addEventListener('change', (e) => {
437+
this.updateLeftValues(e.target.value);
438+
});
439+
// Hook into programmatic value assignments
440+
this.hookInputValueSync(this.rangeStart, (val) => {
441+
this.updateLeftValues(val);
437442
});
438-
startObserver.observe(this.rangeStart, { attributes: true });
439443
}
440444

441445
if (this.rangeEnd) {
446+
// React to user typing immediately
442447
this.rangeEnd.addEventListener('input', (e) => {
443448
this.updateRightValues(e.target.value);
444449
});
445-
446-
// Watch for programmatic changes
447-
const endObserver = new MutationObserver((mutations) => {
448-
mutations.forEach((mutation) => {
449-
if (
450-
mutation.type === 'attributes' &&
451-
mutation.attributeName === 'value'
452-
) {
453-
const value = parseInt(this.rangeEnd.value);
454-
if (
455-
!isNaN(value) &&
456-
value >= this.sliderMin &&
457-
value <= this.sliderMax
458-
) {
459-
this.updateRightValues(value);
460-
}
461-
}
462-
});
450+
this.rangeEnd.addEventListener('change', (e) => {
451+
this.updateRightValues(e.target.value);
452+
});
453+
// Hook into programmatic value assignments
454+
this.hookInputValueSync(this.rangeEnd, (val) => {
455+
this.updateRightValues(val);
463456
});
464-
endObserver.observe(this.rangeEnd, { attributes: true });
465457
}
466458
}
467459

@@ -476,11 +468,12 @@ setupEventListeners() {
476468
updateThumbPosition(input, thumb, range, side) {
477469
const min = parseInt(input.min);
478470
const max = parseInt(input.max);
479-
const percent = ((input.value - min) / (max - min)) * 100;
471+
const current = parseInt(input.value);
472+
const percent = ((current - min) / (max - min)) * 100;
480473

481474
// Get the thumb's width to account for its dimensions
482-
const thumbWidth = thumb.offsetWidth;
483-
const sliderWidth = this.slider.offsetWidth;
475+
const thumbWidth = thumb.offsetWidth || parseInt(getComputedStyle(thumb).width) || 20;
476+
const sliderWidth = this.slider.offsetWidth || parseInt(getComputedStyle(this.slider).width) || 1;
484477

485478
// Calculate the percentage that represents half the thumb width
486479
const thumbHalfPercent = (thumbWidth / sliderWidth) * 100;
@@ -521,6 +514,68 @@ triggerEvent(element) {
521514
}
522515
}
523516

517+
/**
518+
* Hook into a text input's value property to react to programmatic assignments
519+
* @param {HTMLInputElement} input - The input to hook
520+
* @param {(val: string|number) => void} handler - Handler to run on assignment
521+
* @private
522+
*/
523+
hookInputValueSync(input, handler) {
524+
if (!input) return;
525+
const valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
526+
const self = this;
527+
try {
528+
Object.defineProperty(input, 'value', {
529+
get() {
530+
return valueProp.get.call(this);
531+
},
532+
set(v) {
533+
valueProp.set.call(this, v);
534+
if (!self.__suspendExternalSync) {
535+
handler(v);
536+
}
537+
},
538+
configurable: true,
539+
enumerable: true,
540+
});
541+
} catch (err) {
542+
// Fallback: rely on 'input'/'change' listeners if defineProperty fails
543+
}
544+
}
545+
546+
/**
547+
* Public API: set left range value
548+
* @param {number|string} value
549+
*/
550+
setFrom(value) {
551+
this.updateLeftValues(value);
552+
}
553+
554+
/**
555+
* Public API: set right range value
556+
* @param {number|string} value
557+
*/
558+
setTo(value) {
559+
this.updateRightValues(value);
560+
}
561+
562+
/**
563+
* Public API: set both range values atomically and in correct order
564+
* @param {number|string} from
565+
* @param {number|string} to
566+
*/
567+
setRange(from, to) {
568+
this.updateLeftValues(from);
569+
this.updateRightValues(to);
570+
}
571+
572+
/**
573+
* Public API: reset slider to configured bounds
574+
*/
575+
reset() {
576+
this.setRange(this.sliderMin, this.sliderMax);
577+
}
578+
524579
/**
525580
* Validates that all required elements are present
526581
* @private

0 commit comments

Comments
 (0)