@zywave/zui-slider 4.4.0-pre.4 → 4.4.0-pre.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/zui-slider.ts CHANGED
@@ -70,7 +70,6 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
70
70
  visible: boolean;
71
71
  focused: boolean;
72
72
  timer?: ReturnType<typeof setTimeout>;
73
- debounceTimer?: ReturnType<typeof setTimeout>;
74
73
  }
75
74
  >([
76
75
  ['thumb', { visible: false, focused: false }],
@@ -78,12 +77,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
78
77
  ['endThumb', { visible: false, focused: false }],
79
78
  ]);
80
79
 
81
- // Pre-bound floating input handlers cached to avoid new function references on every render
82
- #onThumbFloatingInput = this.#onFloatingInput('thumb', (v) => (this.value = v));
83
- #onStartThumbFloatingInput = this.#onFloatingInput('startThumb', (v) => (this.valueStart = v));
84
- #onEndThumbFloatingInput = this.#onFloatingInput('endThumb', (v) => (this.valueEnd = v));
85
-
86
- // Cached floating input change handlers; flush debounce and dispatch immediately on commit (Enter/blur)
80
+ // Cached floating input change handlers; commit value on Enter or blur
87
81
  #onThumbFloatingChange = this.#makeFloatingChange(
88
82
  'thumb',
89
83
  (v) => (this.value = v),
@@ -100,6 +94,16 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
100
94
  () => this.#onRangeChange()
101
95
  );
102
96
 
97
+ // Cached keydown handler: Enter commits the floating input value in both number and text modes.
98
+ #onFloatingInputKeydown = (e: KeyboardEvent) => {
99
+ if (e.key === 'Enter') {
100
+ e.preventDefault();
101
+ const input = e.target as HTMLInputElement;
102
+ input.dispatchEvent(new Event('change'));
103
+ input.blur();
104
+ }
105
+ };
106
+
103
107
  // Cached range drag input handlers
104
108
  #onRangeStartInput = this.#onRangeInput('start');
105
109
  #onRangeEndInput = this.#onRangeInput('end');
@@ -243,9 +247,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
243
247
  #clearAllThumbInputState() {
244
248
  for (const entry of this.#thumbInputState.values()) {
245
249
  clearTimeout(entry.timer);
246
- clearTimeout(entry.debounceTimer);
247
250
  entry.timer = undefined;
248
- entry.debounceTimer = undefined;
249
251
  entry.visible = false;
250
252
  entry.focused = false;
251
253
  }
@@ -524,6 +526,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
524
526
  #renderSingle() {
525
527
  const progress = this.progress;
526
528
  const { nativeMin, nativeMax, nativeStep } = this.#nativeRangeAttrs;
529
+ // live() required: direct DOM writes during drag don't trigger a state change, so Lit won't re-sync without it.
527
530
  const nativeValue = this.#stepsMode ? String(Math.max(0, this.#stepsIndexOf(this.#value))) : this.#value;
528
531
  return html`
529
532
  <div class="single-wrapper">
@@ -534,7 +537,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
534
537
  .min="${nativeMin}"
535
538
  .max="${nativeMax}"
536
539
  .step="${nativeStep}"
537
- .value="${nativeValue}"
540
+ .value="${live(nativeValue)}"
538
541
  ?disabled="${this.disabled || this.readOnly}"
539
542
  @input="${this.#onInput}"
540
543
  @change="${this.#onChange}"
@@ -545,7 +548,6 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
545
548
  />
546
549
  ${this.#renderFloatingInput(
547
550
  this.#value,
548
- this.#onThumbFloatingInput,
549
551
  this.#onThumbFloatingChange,
550
552
  'thumb',
551
553
  this.#isVisible('thumb'),
@@ -564,7 +566,6 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
564
566
  ${this.#renderRangeInput('start', this.#rangeTrackBackground(progressStart, progressEnd))}
565
567
  ${this.#renderFloatingInput(
566
568
  this.#valueStart,
567
- this.#onStartThumbFloatingInput,
568
569
  this.#onStartThumbFloatingChange,
569
570
  'startThumb',
570
571
  this.#isVisible('startThumb'),
@@ -573,7 +574,6 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
573
574
  ${this.#renderRangeInput('end')}
574
575
  ${this.#renderFloatingInput(
575
576
  this.#valueEnd,
576
- this.#onEndThumbFloatingInput,
577
577
  this.#onEndThumbFloatingChange,
578
578
  'endThumb',
579
579
  this.#isVisible('endThumb'),
@@ -615,7 +615,6 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
615
615
 
616
616
  #renderFloatingInput(
617
617
  val: string,
618
- onInput: (e: Event) => void,
619
618
  onFloatingChange: (e: Event) => void,
620
619
  flag: ThumbFlag,
621
620
  visible: boolean,
@@ -623,7 +622,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
623
622
  ) {
624
623
  const h = this.#h[flag];
625
624
  // type="text" in steps mode to allow label and stepParser input.
626
- // live() required: same-value debounce resolutions skip reactive updates, so Lit won't re-sync without it.
625
+ // live() required: commits that snap/clamp to the current value skip reactive updates, so Lit won't re-sync without it.
627
626
  const ariaLabel =
628
627
  flag === 'startThumb' ? 'Range start value' : flag === 'endThumb' ? 'Range end value' : 'Slider value';
629
628
  return html`
@@ -642,7 +641,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
642
641
  .step="${this.#stepsMode ? '' : this.step > 0 ? String(this.step) : '1'}"
643
642
  ?disabled="${this.disabled}"
644
643
  ?readonly="${this.readOnly}"
645
- @input="${onInput}"
644
+ @keydown="${this.#onFloatingInputKeydown}"
646
645
  @change="${onFloatingChange}"
647
646
  @focus="${h.focus}"
648
647
  @blur="${h.blur}"
@@ -756,58 +755,28 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
756
755
  return;
757
756
  }
758
757
  const input = e.target as HTMLInputElement;
759
- const entry = this.#thumbInputState.get(flag)!;
760
- clearTimeout(entry.debounceTimer);
761
- entry.debounceTimer = undefined;
762
758
  if (input.value === '') {
763
759
  input.value = this.#currentValueForFlag(flag);
764
760
  return;
765
761
  }
762
+ const before = this.#currentValueForFlag(flag);
766
763
  if (this.#stepsMode) {
767
764
  const resolved = this.#resolveFloatingInput(input.value);
768
765
  if (resolved !== null) {
769
766
  setter(resolved);
770
767
  this.requestUpdate();
771
- dispatch();
768
+ if (this.#currentValueForFlag(flag) !== before) {
769
+ dispatch();
770
+ }
772
771
  } else {
773
772
  input.value = this.#currentValueForFlag(flag);
774
773
  }
775
774
  } else {
776
775
  setter(this.#processFloatingValue(parseFloat(input.value)));
777
776
  this.requestUpdate();
778
- dispatch();
779
- }
780
- };
781
- }
782
-
783
- #onFloatingInput(flag: ThumbFlag, setter: (val: string) => void) {
784
- return (e: Event) => {
785
- if (this.readOnly) {
786
- return;
787
- }
788
- const input = e.target as HTMLInputElement;
789
- const entry = this.#thumbInputState.get(flag)!;
790
- clearTimeout(entry.debounceTimer);
791
- entry.debounceTimer = undefined;
792
- if (input.value === '') {
793
- return;
794
- }
795
- if (this.#stepsMode) {
796
- // Capture raw string at event time so the debounce closure reads the right value
797
- const raw = input.value;
798
- entry.debounceTimer = setTimeout(() => {
799
- const resolved = this.#resolveFloatingInput(raw);
800
- if (resolved !== null) {
801
- setter(resolved);
802
- this.requestUpdate();
803
- }
804
- }, 500);
805
- } else {
806
- const raw = parseFloat(input.value);
807
- entry.debounceTimer = setTimeout(() => {
808
- setter(this.#processFloatingValue(raw));
809
- this.requestUpdate();
810
- }, 500);
777
+ if (this.#currentValueForFlag(flag) !== before) {
778
+ dispatch();
779
+ }
811
780
  }
812
781
  };
813
782
  }