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

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
@@ -69,15 +69,15 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
69
69
  {
70
70
  visible: boolean;
71
71
  focused: boolean;
72
+ committed: boolean;
72
73
  timer?: ReturnType<typeof setTimeout>;
73
74
  }
74
75
  >([
75
- ['thumb', { visible: false, focused: false }],
76
- ['startThumb', { visible: false, focused: false }],
77
- ['endThumb', { visible: false, focused: false }],
76
+ ['thumb', { visible: false, focused: false, committed: false }],
77
+ ['startThumb', { visible: false, focused: false, committed: false }],
78
+ ['endThumb', { visible: false, focused: false, committed: false }],
78
79
  ]);
79
80
 
80
- // Cached floating input change handlers; commit value on Enter or blur
81
81
  #onThumbFloatingChange = this.#makeFloatingChange(
82
82
  'thumb',
83
83
  (v) => (this.value = v),
@@ -100,34 +100,21 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
100
100
  e.preventDefault();
101
101
  const input = e.target as HTMLInputElement;
102
102
  input.dispatchEvent(new Event('change'));
103
- input.blur();
104
103
  }
105
104
  };
106
105
 
107
- // Cached range drag input handlers
108
106
  #onRangeStartInput = this.#onRangeInput('start');
109
107
  #onRangeEndInput = this.#onRangeInput('end');
108
+ #stopClickPropagation = (e: Event) => e.stopPropagation();
110
109
 
111
110
  // Cached pointer/focus handlers per thumb; prevents new closures on every render
112
- #h: Record<ThumbFlag, { show: () => void; hide: () => void; focus: () => void; blur: () => void }> = {
113
- thumb: {
114
- show: () => this.#showThumbInput('thumb'),
115
- hide: () => this.#scheduleHideThumbInput('thumb'),
116
- focus: () => this.#focusFloatingInput('thumb'),
117
- blur: () => this.#blurFloatingInput('thumb'),
118
- },
119
- startThumb: {
120
- show: () => this.#showThumbInput('startThumb'),
121
- hide: () => this.#scheduleHideThumbInput('startThumb'),
122
- focus: () => this.#focusFloatingInput('startThumb'),
123
- blur: () => this.#blurFloatingInput('startThumb'),
124
- },
125
- endThumb: {
126
- show: () => this.#showThumbInput('endThumb'),
127
- hide: () => this.#scheduleHideThumbInput('endThumb'),
128
- focus: () => this.#focusFloatingInput('endThumb'),
129
- blur: () => this.#blurFloatingInput('endThumb'),
130
- },
111
+ #h: Record<
112
+ ThumbFlag,
113
+ { show: () => void; hide: () => void; focus: () => void; input: () => void; blurCommit: (e: FocusEvent) => void }
114
+ > = {
115
+ thumb: this.#makeThumbHandlers('thumb'),
116
+ startThumb: this.#makeThumbHandlers('startThumb'),
117
+ endThumb: this.#makeThumbHandlers('endThumb'),
131
118
  };
132
119
 
133
120
  static get styles() {
@@ -250,6 +237,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
250
237
  entry.timer = undefined;
251
238
  entry.visible = false;
252
239
  entry.focused = false;
240
+ entry.committed = false;
253
241
  }
254
242
  }
255
243
 
@@ -603,6 +591,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
603
591
  .step="${nativeStep}"
604
592
  .value="${live(nativeValue)}"
605
593
  ?disabled="${this.disabled || this.readOnly}"
594
+ @click="${this.#stopClickPropagation}"
606
595
  @input="${onInput}"
607
596
  @change="${this.#onRangeChange}"
608
597
  @pointerenter="${h.show}"
@@ -629,6 +618,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
629
618
  <div
630
619
  class=${classMap({ 'thumb-input': true, 'thumb-input--visible': visible })}
631
620
  style=${styleMap({ left: ZuiSlider.#thumbPositionCSS(progress) })}
621
+ @click="${this.#stopClickPropagation}"
632
622
  @pointerenter="${h.show}"
633
623
  @pointerleave="${h.hide}"
634
624
  >
@@ -642,9 +632,10 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
642
632
  ?disabled="${this.disabled}"
643
633
  ?readonly="${this.readOnly}"
644
634
  @keydown="${this.#onFloatingInputKeydown}"
635
+ @input="${h.input}"
645
636
  @change="${onFloatingChange}"
646
637
  @focus="${h.focus}"
647
- @blur="${h.blur}"
638
+ @blur="${h.blurCommit}"
648
639
  />
649
640
  </div>
650
641
  `;
@@ -701,7 +692,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
701
692
  const normalized = this.#normalizedSteps;
702
693
  const minLabel = this.#stepsMode ? normalized[0]?.label ?? '' : String(this.min);
703
694
  const maxLabel = this.#stepsMode ? normalized[normalized.length - 1]?.label ?? '' : String(this.max);
704
- const hidden = this.showStepLabels && this.#stepsMode;
695
+ const hidden = this.showStepLabels;
705
696
  return html`
706
697
  <div
707
698
  class="min-max-labels"
@@ -749,6 +740,25 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
749
740
  return this.#value;
750
741
  }
751
742
 
743
+ #makeThumbHandlers(flag: ThumbFlag) {
744
+ return {
745
+ show: () => this.#showThumbInput(flag),
746
+ hide: () => this.#scheduleHideThumbInput(flag),
747
+ focus: () => this.#focusFloatingInput(flag),
748
+ input: () => {
749
+ this.#thumbInputState.get(flag)!.committed = false;
750
+ },
751
+ blurCommit: (e: FocusEvent) => {
752
+ const entry = this.#thumbInputState.get(flag)!;
753
+ if (!entry.committed) {
754
+ (e.target as HTMLInputElement).dispatchEvent(new Event('change'));
755
+ }
756
+ entry.committed = false;
757
+ this.#blurFloatingInput(flag);
758
+ },
759
+ };
760
+ }
761
+
752
762
  #makeFloatingChange(flag: ThumbFlag, setter: (val: string) => void, dispatch: () => void) {
753
763
  return (e: Event) => {
754
764
  if (this.readOnly) {
@@ -760,23 +770,53 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
760
770
  return;
761
771
  }
762
772
  const before = this.#currentValueForFlag(flag);
773
+ const commit = (value: string) => {
774
+ setter(value);
775
+ this.#thumbInputState.get(flag)!.committed = true;
776
+ if (this.#currentValueForFlag(flag) !== before) {
777
+ dispatch();
778
+ }
779
+ };
763
780
  if (this.#stepsMode) {
764
781
  const resolved = this.#resolveFloatingInput(input.value);
765
782
  if (resolved !== null) {
766
- setter(resolved);
767
- this.requestUpdate();
768
- if (this.#currentValueForFlag(flag) !== before) {
769
- dispatch();
783
+ let finalResolved = resolved;
784
+ if (flag !== 'thumb') {
785
+ const resolvedIdx = this.#stepsIndexOf(resolved);
786
+ const endIdx = Math.max(0, this.#stepsIndexOf(this.#valueEnd));
787
+ const startIdx = Math.max(0, this.#stepsIndexOf(this.#valueStart));
788
+ if (flag === 'startThumb' && resolvedIdx >= endIdx) {
789
+ const nudgedIdx = endIdx - 1;
790
+ finalResolved = nudgedIdx >= 0 ? this.#stepAt(nudgedIdx) : this.#currentValueForFlag(flag);
791
+ } else if (flag === 'endThumb' && resolvedIdx <= startIdx) {
792
+ const nudgedIdx = startIdx + 1;
793
+ finalResolved =
794
+ nudgedIdx < this.#normalizedSteps.length ? this.#stepAt(nudgedIdx) : this.#currentValueForFlag(flag);
795
+ }
770
796
  }
797
+ commit(finalResolved);
771
798
  } else {
772
799
  input.value = this.#currentValueForFlag(flag);
773
800
  }
774
801
  } else {
775
- setter(this.#processFloatingValue(parseFloat(input.value)));
776
- this.requestUpdate();
777
- if (this.#currentValueForFlag(flag) !== before) {
778
- dispatch();
802
+ let processed = this.#processFloatingValue(parseFloat(input.value));
803
+ if (flag !== 'thumb') {
804
+ const effectiveStep = this.step > 0 ? this.step : 1;
805
+ if (flag === 'startThumb') {
806
+ const endNum = parseFloat(this.#valueEnd);
807
+ if (parseFloat(processed) >= endNum) {
808
+ const nudged = this.#processFloatingValue(endNum - effectiveStep);
809
+ processed = parseFloat(nudged) < endNum ? nudged : this.#currentValueForFlag(flag);
810
+ }
811
+ } else {
812
+ const startNum = parseFloat(this.#valueStart);
813
+ if (parseFloat(processed) <= startNum) {
814
+ const nudged = this.#processFloatingValue(startNum + effectiveStep);
815
+ processed = parseFloat(nudged) > startNum ? nudged : this.#currentValueForFlag(flag);
816
+ }
817
+ }
779
818
  }
819
+ commit(processed);
780
820
  }
781
821
  };
782
822
  }
@@ -835,7 +875,6 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
835
875
  if (this.disabled || this.readOnly) {
836
876
  return;
837
877
  }
838
-
839
878
  const wrapper = e.currentTarget as HTMLElement;
840
879
  const rect = wrapper.getBoundingClientRect();
841
880
  // Thumb diameter is 3× the custom property; radius is 1.5×. Track is inset by one radius each side.
@@ -930,7 +969,9 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
930
969
  return;
931
970
  }
932
971
  this.#showThumbInput(flag);
933
- this.#thumbInputState.get(flag)!.focused = true;
972
+ const entry = this.#thumbInputState.get(flag)!;
973
+ entry.focused = true;
974
+ entry.committed = false;
934
975
  }
935
976
 
936
977
  #blurFloatingInput(flag: ThumbFlag) {