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

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.
@@ -94,7 +94,7 @@
94
94
  "kind": "field",
95
95
  "name": "#thumbInputState",
96
96
  "privacy": "private",
97
- "default": "new Map< ThumbFlag, { visible: boolean; focused: boolean; timer?: ReturnType<typeof setTimeout>; } >([ ['thumb', { visible: false, focused: false }], ['startThumb', { visible: false, focused: false }], ['endThumb', { visible: false, focused: false }], ])"
97
+ "default": "new Map< ThumbFlag, { visible: boolean; focused: boolean; committed: boolean; timer?: ReturnType<typeof setTimeout>; } >([ ['thumb', { visible: false, focused: false, committed: false }], ['startThumb', { visible: false, focused: false, committed: false }], ['endThumb', { visible: false, focused: false, committed: false }], ])"
98
98
  },
99
99
  {
100
100
  "kind": "field",
@@ -126,14 +126,19 @@
126
126
  "name": "#onRangeEndInput",
127
127
  "privacy": "private"
128
128
  },
129
+ {
130
+ "kind": "field",
131
+ "name": "#stopClickPropagation",
132
+ "privacy": "private"
133
+ },
129
134
  {
130
135
  "kind": "field",
131
136
  "name": "#h",
132
137
  "privacy": "private",
133
138
  "type": {
134
- "text": "Record<ThumbFlag, { show: () => void; hide: () => void; focus: () => void; blur: () => void }>"
139
+ "text": "Record<\n ThumbFlag,\n { show: () => void; hide: () => void; focus: () => void; input: () => void; blurCommit: (e: FocusEvent) => void }\n >"
135
140
  },
136
- "default": "{ thumb: { show: () => this.#showThumbInput('thumb'), hide: () => this.#scheduleHideThumbInput('thumb'), focus: () => this.#focusFloatingInput('thumb'), blur: () => this.#blurFloatingInput('thumb'), }, startThumb: { show: () => this.#showThumbInput('startThumb'), hide: () => this.#scheduleHideThumbInput('startThumb'), focus: () => this.#focusFloatingInput('startThumb'), blur: () => this.#blurFloatingInput('startThumb'), }, endThumb: { show: () => this.#showThumbInput('endThumb'), hide: () => this.#scheduleHideThumbInput('endThumb'), focus: () => this.#focusFloatingInput('endThumb'), blur: () => this.#blurFloatingInput('endThumb'), }, }"
141
+ "default": "{ thumb: this.#makeThumbHandlers('thumb'), startThumb: this.#makeThumbHandlers('startThumb'), endThumb: this.#makeThumbHandlers('endThumb'), }"
137
142
  },
138
143
  {
139
144
  "kind": "field",
@@ -673,6 +678,67 @@
673
678
  }
674
679
  ]
675
680
  },
681
+ {
682
+ "kind": "method",
683
+ "name": "#makeThumbHandlers",
684
+ "privacy": "private",
685
+ "parameters": [
686
+ {
687
+ "name": "flag",
688
+ "type": {
689
+ "text": "ThumbFlag"
690
+ }
691
+ }
692
+ ]
693
+ },
694
+ {
695
+ "kind": "method",
696
+ "name": "#nudgeStepsValue",
697
+ "privacy": "private",
698
+ "return": {
699
+ "type": {
700
+ "text": "string"
701
+ }
702
+ },
703
+ "parameters": [
704
+ {
705
+ "name": "flag",
706
+ "type": {
707
+ "text": "ThumbFlag"
708
+ }
709
+ },
710
+ {
711
+ "name": "resolved",
712
+ "type": {
713
+ "text": "string"
714
+ }
715
+ }
716
+ ]
717
+ },
718
+ {
719
+ "kind": "method",
720
+ "name": "#nudgeNumericValue",
721
+ "privacy": "private",
722
+ "return": {
723
+ "type": {
724
+ "text": "string"
725
+ }
726
+ },
727
+ "parameters": [
728
+ {
729
+ "name": "flag",
730
+ "type": {
731
+ "text": "ThumbFlag"
732
+ }
733
+ },
734
+ {
735
+ "name": "processed",
736
+ "type": {
737
+ "text": "string"
738
+ }
739
+ }
740
+ ]
741
+ },
676
742
  {
677
743
  "kind": "method",
678
744
  "name": "#makeFloatingChange",
@@ -65,11 +65,10 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
65
65
  this.#cachedNormalizedSteps = null;
66
66
  this.#cachedNumericValues = null;
67
67
  this.#thumbInputState = new Map([
68
- ['thumb', { visible: false, focused: false }],
69
- ['startThumb', { visible: false, focused: false }],
70
- ['endThumb', { visible: false, focused: false }],
68
+ ['thumb', { visible: false, focused: false, committed: false }],
69
+ ['startThumb', { visible: false, focused: false, committed: false }],
70
+ ['endThumb', { visible: false, focused: false, committed: false }],
71
71
  ]);
72
- // Cached floating input change handlers; commit value on Enter or blur
73
72
  this.#onThumbFloatingChange = this.#makeFloatingChange('thumb', (v) => (this.value = v), () => this.#onChange());
74
73
  this.#onStartThumbFloatingChange = this.#makeFloatingChange('startThumb', (v) => (this.valueStart = v), () => this.#onRangeChange());
75
74
  this.#onEndThumbFloatingChange = this.#makeFloatingChange('endThumb', (v) => (this.valueEnd = v), () => this.#onRangeChange());
@@ -79,32 +78,16 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
79
78
  e.preventDefault();
80
79
  const input = e.target;
81
80
  input.dispatchEvent(new Event('change'));
82
- input.blur();
83
81
  }
84
82
  };
85
- // Cached range drag input handlers
86
83
  this.#onRangeStartInput = this.#onRangeInput('start');
87
84
  this.#onRangeEndInput = this.#onRangeInput('end');
85
+ this.#stopClickPropagation = (e) => e.stopPropagation();
88
86
  // Cached pointer/focus handlers per thumb; prevents new closures on every render
89
87
  this.#h = {
90
- thumb: {
91
- show: () => this.#showThumbInput('thumb'),
92
- hide: () => this.#scheduleHideThumbInput('thumb'),
93
- focus: () => this.#focusFloatingInput('thumb'),
94
- blur: () => this.#blurFloatingInput('thumb'),
95
- },
96
- startThumb: {
97
- show: () => this.#showThumbInput('startThumb'),
98
- hide: () => this.#scheduleHideThumbInput('startThumb'),
99
- focus: () => this.#focusFloatingInput('startThumb'),
100
- blur: () => this.#blurFloatingInput('startThumb'),
101
- },
102
- endThumb: {
103
- show: () => this.#showThumbInput('endThumb'),
104
- hide: () => this.#scheduleHideThumbInput('endThumb'),
105
- focus: () => this.#focusFloatingInput('endThumb'),
106
- blur: () => this.#blurFloatingInput('endThumb'),
107
- },
88
+ thumb: this.#makeThumbHandlers('thumb'),
89
+ startThumb: this.#makeThumbHandlers('startThumb'),
90
+ endThumb: this.#makeThumbHandlers('endThumb'),
108
91
  };
109
92
  this.steps = [];
110
93
  this.stepParser = null;
@@ -124,15 +107,14 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
124
107
  #cachedNormalizedSteps;
125
108
  #cachedNumericValues;
126
109
  #thumbInputState;
127
- // Cached floating input change handlers; commit value on Enter or blur
128
110
  #onThumbFloatingChange;
129
111
  #onStartThumbFloatingChange;
130
112
  #onEndThumbFloatingChange;
131
113
  // Cached keydown handler: Enter commits the floating input value in both number and text modes.
132
114
  #onFloatingInputKeydown;
133
- // Cached range drag input handlers
134
115
  #onRangeStartInput;
135
116
  #onRangeEndInput;
117
+ #stopClickPropagation;
136
118
  // Cached pointer/focus handlers per thumb; prevents new closures on every render
137
119
  #h;
138
120
  static get styles() {
@@ -239,6 +221,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
239
221
  entry.timer = undefined;
240
222
  entry.visible = false;
241
223
  entry.focused = false;
224
+ entry.committed = false;
242
225
  }
243
226
  }
244
227
  #syncValuesToSteps() {
@@ -529,6 +512,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
529
512
  .step="${nativeStep}"
530
513
  .value="${live(nativeValue)}"
531
514
  ?disabled="${this.disabled || this.readOnly}"
515
+ @click="${this.#stopClickPropagation}"
532
516
  @input="${onInput}"
533
517
  @change="${this.#onRangeChange}"
534
518
  @pointerenter="${h.show}"
@@ -547,6 +531,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
547
531
  <div
548
532
  class=${classMap({ 'thumb-input': true, 'thumb-input--visible': visible })}
549
533
  style=${styleMap({ left: _a.#thumbPositionCSS(progress) })}
534
+ @click="${this.#stopClickPropagation}"
550
535
  @pointerenter="${h.show}"
551
536
  @pointerleave="${h.hide}"
552
537
  >
@@ -560,9 +545,10 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
560
545
  ?disabled="${this.disabled}"
561
546
  ?readonly="${this.readOnly}"
562
547
  @keydown="${this.#onFloatingInputKeydown}"
548
+ @input="${h.input}"
563
549
  @change="${onFloatingChange}"
564
550
  @focus="${h.focus}"
565
- @blur="${h.blur}"
551
+ @blur="${h.blurCommit}"
566
552
  />
567
553
  </div>
568
554
  `;
@@ -618,7 +604,7 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
618
604
  const normalized = this.#normalizedSteps;
619
605
  const minLabel = this.#stepsMode ? normalized[0]?.label ?? '' : String(this.min);
620
606
  const maxLabel = this.#stepsMode ? normalized[normalized.length - 1]?.label ?? '' : String(this.max);
621
- const hidden = this.showStepLabels && this.#stepsMode;
607
+ const hidden = this.showStepLabels;
622
608
  return html `
623
609
  <div
624
610
  class="min-max-labels"
@@ -661,6 +647,62 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
661
647
  }
662
648
  return this.#value;
663
649
  }
650
+ #makeThumbHandlers(flag) {
651
+ return {
652
+ show: () => this.#showThumbInput(flag),
653
+ hide: () => this.#scheduleHideThumbInput(flag),
654
+ focus: () => this.#focusFloatingInput(flag),
655
+ input: () => {
656
+ this.#thumbInputState.get(flag).committed = false;
657
+ },
658
+ blurCommit: (e) => {
659
+ const entry = this.#thumbInputState.get(flag);
660
+ if (!entry.committed) {
661
+ e.target.dispatchEvent(new Event('change'));
662
+ }
663
+ entry.committed = false;
664
+ this.#blurFloatingInput(flag);
665
+ },
666
+ };
667
+ }
668
+ #nudgeStepsValue(flag, resolved) {
669
+ if (flag === 'thumb') {
670
+ return resolved;
671
+ }
672
+ const resolvedIdx = this.#stepsIndexOf(resolved);
673
+ const endIdx = Math.max(0, this.#stepsIndexOf(this.#valueEnd));
674
+ const startIdx = Math.max(0, this.#stepsIndexOf(this.#valueStart));
675
+ if (flag === 'startThumb' && resolvedIdx >= endIdx) {
676
+ const nudgedIdx = endIdx - 1;
677
+ return nudgedIdx >= 0 ? this.#stepAt(nudgedIdx) : this.#currentValueForFlag(flag);
678
+ }
679
+ if (flag === 'endThumb' && resolvedIdx <= startIdx) {
680
+ const nudgedIdx = startIdx + 1;
681
+ return nudgedIdx < this.#normalizedSteps.length ? this.#stepAt(nudgedIdx) : this.#currentValueForFlag(flag);
682
+ }
683
+ return resolved;
684
+ }
685
+ #nudgeNumericValue(flag, processed) {
686
+ if (flag === 'thumb') {
687
+ return processed;
688
+ }
689
+ const effectiveStep = this.step > 0 ? this.step : 1;
690
+ if (flag === 'startThumb') {
691
+ const endNum = parseFloat(this.#valueEnd);
692
+ if (parseFloat(processed) >= endNum) {
693
+ const nudged = this.#processFloatingValue(endNum - effectiveStep);
694
+ return parseFloat(nudged) < endNum ? nudged : this.#currentValueForFlag(flag);
695
+ }
696
+ }
697
+ else {
698
+ const startNum = parseFloat(this.#valueStart);
699
+ if (parseFloat(processed) <= startNum) {
700
+ const nudged = this.#processFloatingValue(startNum + effectiveStep);
701
+ return parseFloat(nudged) > startNum ? nudged : this.#currentValueForFlag(flag);
702
+ }
703
+ }
704
+ return processed;
705
+ }
664
706
  #makeFloatingChange(flag, setter, dispatch) {
665
707
  return (e) => {
666
708
  if (this.readOnly) {
@@ -672,25 +714,25 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
672
714
  return;
673
715
  }
674
716
  const before = this.#currentValueForFlag(flag);
717
+ const commit = (value) => {
718
+ setter(value);
719
+ this.#thumbInputState.get(flag).committed = true;
720
+ if (this.#currentValueForFlag(flag) !== before) {
721
+ dispatch();
722
+ }
723
+ // Force re-render so live() corrects the DOM when the nudged/clamped value equals the stored value and the setter no-ops.
724
+ this.requestUpdate();
725
+ };
675
726
  if (this.#stepsMode) {
676
727
  const resolved = this.#resolveFloatingInput(input.value);
677
- if (resolved !== null) {
678
- setter(resolved);
679
- this.requestUpdate();
680
- if (this.#currentValueForFlag(flag) !== before) {
681
- dispatch();
682
- }
683
- }
684
- else {
728
+ if (resolved === null) {
685
729
  input.value = this.#currentValueForFlag(flag);
730
+ return;
686
731
  }
732
+ commit(this.#nudgeStepsValue(flag, resolved));
687
733
  }
688
734
  else {
689
- setter(this.#processFloatingValue(parseFloat(input.value)));
690
- this.requestUpdate();
691
- if (this.#currentValueForFlag(flag) !== before) {
692
- dispatch();
693
- }
735
+ commit(this.#nudgeNumericValue(flag, this.#processFloatingValue(parseFloat(input.value))));
694
736
  }
695
737
  };
696
738
  }
@@ -833,7 +875,9 @@ export class ZuiSlider extends ZuiFormAssociatedElement {
833
875
  return;
834
876
  }
835
877
  this.#showThumbInput(flag);
836
- this.#thumbInputState.get(flag).focused = true;
878
+ const entry = this.#thumbInputState.get(flag);
879
+ entry.focused = true;
880
+ entry.committed = false;
837
881
  }
838
882
  #blurFloatingInput(flag) {
839
883
  const entry = this.#thumbInputState.get(flag);