@ionic/core 8.7.17-dev.11771359170.1fda0949 → 8.7.17-dev.11771865171.14f4c2cf

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.
@@ -17,13 +17,30 @@ import { roundToMaxDecimalPlaces } from "../../utils/floating-point";
17
17
  * @slot start - Content is placed to the left of the range slider in LTR, and to the right in RTL.
18
18
  * @slot end - Content is placed to the right of the range slider in LTR, and to the left in RTL.
19
19
  *
20
+ * @part label - The label text describing the range.
20
21
  * @part tick - An inactive tick mark.
21
22
  * @part tick-active - An active tick mark.
22
- * @part pin - The counter that appears above a knob.
23
- * @part knob - The handle that is used to drag the range.
24
23
  * @part bar - The inactive part of the bar.
25
24
  * @part bar-active - The active part of the bar.
26
- * @part label - The label text describing the range.
25
+ * @part knob-handle - The container element that wraps the knob and handles drag interactions.
26
+ * @part knob-handle-a - The container element for the first knob. Only available when `dualKnobs` is `true`.
27
+ * @part knob-handle-b - The container element for the second knob. Only available when `dualKnobs` is `true`.
28
+ * @part knob-handle-lower - The container element for the lower knob. Only available when `dualKnobs` is `true`.
29
+ * @part knob-handle-upper - The container element for the upper knob. Only available when `dualKnobs` is `true`.
30
+ * @part pin - The counter that appears above a knob.
31
+ * @part pin-a - The counter that appears above the first knob. Only available when `dualKnobs` is `true`.
32
+ * @part pin-b - The counter that appears above the second knob. Only available when `dualKnobs` is `true`.
33
+ * @part pin-lower - The counter that appears above the lower knob. Only available when `dualKnobs` is `true`.
34
+ * @part pin-upper - The counter that appears above the upper knob. Only available when `dualKnobs` is `true`.
35
+ * @part knob - The visual knob element that appears on the range track.
36
+ * @part knob-a - The visual knob element for the first knob. Only available when `dualKnobs` is `true`.
37
+ * @part knob-b - The visual knob element for the second knob. Only available when `dualKnobs` is `true`.
38
+ * @part knob-lower - The visual knob element for the lower knob. Only available when `dualKnobs` is `true`.
39
+ * @part knob-upper - The visual knob element for the upper knob. Only available when `dualKnobs` is `true`.
40
+ * @part activated - Added to the knob-handle, knob, and pin when the knob is activated (has the `ion-activated` class). Only one set has this part at a time when `dualKnobs` is `true`.
41
+ * @part focused - Added to the knob-handle, knob, and pin that currently has focus. Only one set has this part at a time when `dualKnobs` is `true`.
42
+ * @part hover - Added to the knob-handle, knob, and pin when the knob has hover. Only one set has this part at a time when `dualKnobs` is `true`.
43
+ * @part pressed - Added to the knob-handle, knob, and pin that is currently being pressed to drag. Only one set has this part at a time when `dualKnobs` is `true`.
27
44
  */
28
45
  export class Range {
29
46
  constructor() {
@@ -34,6 +51,7 @@ export class Range {
34
51
  this.inheritedAttributes = {};
35
52
  this.contentEl = null;
36
53
  this.initialContentScrollY = true;
54
+ this.focusFromPointer = false;
37
55
  this.ratioA = 0;
38
56
  this.ratioB = 0;
39
57
  /**
@@ -143,6 +161,29 @@ export class Range {
143
161
  this.gesture.enable(!this.disabled);
144
162
  }
145
163
  };
164
+ /**
165
+ * Observes the knob handles for the ion-activated class and syncs
166
+ * activatedKnob so the activated part is correctly set on the handle,
167
+ * knob, and pin.
168
+ */
169
+ this.setupActivatedObserver = () => {
170
+ const knobHandleA = this.el.shadowRoot.querySelector('.range-knob-handle-a');
171
+ const knobHandleB = this.el.shadowRoot.querySelector('.range-knob-handle-b');
172
+ const syncActivated = () => {
173
+ this.activatedKnob = (knobHandleA === null || knobHandleA === void 0 ? void 0 : knobHandleA.classList.contains('ion-activated'))
174
+ ? 'A'
175
+ : (knobHandleB === null || knobHandleB === void 0 ? void 0 : knobHandleB.classList.contains('ion-activated'))
176
+ ? 'B'
177
+ : undefined;
178
+ };
179
+ this.activatedObserver = new MutationObserver(syncActivated);
180
+ this.activatedObserver.observe(this.el.shadowRoot, {
181
+ attributes: true,
182
+ attributeFilter: ['class'],
183
+ subtree: true,
184
+ });
185
+ syncActivated();
186
+ };
146
187
  this.handleKeyboard = (knob, isIncrease) => {
147
188
  const { ensureValueInBounds } = this;
148
189
  let step = this.step;
@@ -165,6 +206,7 @@ export class Range {
165
206
  this.onBlur = () => {
166
207
  if (this.hasFocus) {
167
208
  this.hasFocus = false;
209
+ this.focusedKnob = undefined;
168
210
  this.ionBlur.emit();
169
211
  }
170
212
  };
@@ -175,21 +217,20 @@ export class Range {
175
217
  }
176
218
  };
177
219
  this.onKnobFocus = (knob) => {
220
+ // Clicking focuses the range which is needed for the keyboard,
221
+ // but we only want to add the ion-focused class when focused via Tab.
222
+ if (!this.focusFromPointer) {
223
+ this.focusedKnob = knob;
224
+ }
225
+ else {
226
+ this.focusFromPointer = false;
227
+ this.focusedKnob = undefined;
228
+ }
229
+ // If the knob was not already focused, emit the focus event
178
230
  if (!this.hasFocus) {
179
231
  this.hasFocus = true;
180
232
  this.ionFocus.emit();
181
233
  }
182
- // Manually manage ion-focused class for dual knobs
183
- if (this.dualKnobs && this.el.shadowRoot) {
184
- const knobA = this.el.shadowRoot.querySelector('.range-knob-a');
185
- const knobB = this.el.shadowRoot.querySelector('.range-knob-b');
186
- // Remove ion-focused from both knobs first
187
- knobA === null || knobA === void 0 ? void 0 : knobA.classList.remove('ion-focused');
188
- knobB === null || knobB === void 0 ? void 0 : knobB.classList.remove('ion-focused');
189
- // Add ion-focused only to the focused knob
190
- const focusedKnobEl = knob === 'A' ? knobA : knobB;
191
- focusedKnobEl === null || focusedKnobEl === void 0 ? void 0 : focusedKnobEl.classList.add('ion-focused');
192
- }
193
234
  };
194
235
  this.onKnobBlur = () => {
195
236
  // Check if focus is moving to another knob within the same range
@@ -201,18 +242,18 @@ export class Range {
201
242
  if (!isStillFocusedOnKnob) {
202
243
  if (this.hasFocus) {
203
244
  this.hasFocus = false;
245
+ this.focusedKnob = undefined;
204
246
  this.ionBlur.emit();
205
247
  }
206
- // Remove ion-focused from both knobs when focus leaves the range
207
- if (this.dualKnobs && this.el.shadowRoot) {
208
- const knobA = this.el.shadowRoot.querySelector('.range-knob-a');
209
- const knobB = this.el.shadowRoot.querySelector('.range-knob-b');
210
- knobA === null || knobA === void 0 ? void 0 : knobA.classList.remove('ion-focused');
211
- knobB === null || knobB === void 0 ? void 0 : knobB.classList.remove('ion-focused');
212
- }
213
248
  }
214
249
  }, 0);
215
250
  };
251
+ this.onKnobMouseEnter = (knob) => {
252
+ this.hoveredKnob = knob;
253
+ };
254
+ this.onKnobMouseLeave = () => {
255
+ this.hoveredKnob = undefined;
256
+ };
216
257
  }
217
258
  debounceChanged() {
218
259
  const { ionInput, debounce, originalIonInput } = this;
@@ -289,6 +330,7 @@ export class Range {
289
330
  this.originalIonInput = this.ionInput;
290
331
  this.setupGesture();
291
332
  this.updateRatio();
333
+ this.setupActivatedObserver();
292
334
  this.didLoad = true;
293
335
  }
294
336
  connectedCallback() {
@@ -314,6 +356,10 @@ export class Range {
314
356
  this.gesture.destroy();
315
357
  this.gesture = undefined;
316
358
  }
359
+ if (this.activatedObserver) {
360
+ this.activatedObserver.disconnect();
361
+ this.activatedObserver = undefined;
362
+ }
317
363
  }
318
364
  getValue() {
319
365
  var _a;
@@ -466,7 +512,6 @@ export class Range {
466
512
  ratio = 1 - ratio;
467
513
  }
468
514
  this.pressedKnob = !this.dualKnobs || Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio) ? 'A' : 'B';
469
- this.setFocus(this.pressedKnob);
470
515
  }
471
516
  get valA() {
472
517
  return ratioToValue(this.ratioA, this.min, this.max, this.step);
@@ -493,9 +538,23 @@ export class Range {
493
538
  updateRatio() {
494
539
  const value = this.getValue();
495
540
  const { min, max } = this;
541
+ /**
542
+ * For dual knobs, value gives lower/upper but not which is A vs B.
543
+ * Assign (lowerRatio, upperRatio) to (ratioA, ratioB) in the way that
544
+ * minimizes change from the current ratios so the knobs don't swap.
545
+ */
496
546
  if (this.dualKnobs) {
497
- this.ratioA = valueToRatio(value.lower, min, max);
498
- this.ratioB = valueToRatio(value.upper, min, max);
547
+ const lowerRatio = valueToRatio(value.lower, min, max);
548
+ const upperRatio = valueToRatio(value.upper, min, max);
549
+ if (Math.abs(this.ratioA - lowerRatio) + Math.abs(this.ratioB - upperRatio) <=
550
+ Math.abs(this.ratioA - upperRatio) + Math.abs(this.ratioB - lowerRatio)) {
551
+ this.ratioA = lowerRatio;
552
+ this.ratioB = upperRatio;
553
+ }
554
+ else {
555
+ this.ratioA = upperRatio;
556
+ this.ratioB = lowerRatio;
557
+ }
499
558
  }
500
559
  else {
501
560
  this.ratioA = valueToRatio(value, min, max);
@@ -512,14 +571,6 @@ export class Range {
512
571
  };
513
572
  this.noUpdate = false;
514
573
  }
515
- setFocus(knob) {
516
- if (this.el.shadowRoot) {
517
- const knobEl = this.el.shadowRoot.querySelector(knob === 'A' ? '.range-knob-a' : '.range-knob-b');
518
- if (knobEl) {
519
- knobEl.focus();
520
- }
521
- }
522
- }
523
574
  /**
524
575
  * Returns true if content was passed to the "start" slot
525
576
  */
@@ -537,7 +588,7 @@ export class Range {
537
588
  }
538
589
  renderRangeSlider() {
539
590
  var _a;
540
- const { min, max, step, handleKeyboard, pressedKnob, disabled, pin, ratioLower, ratioUpper, pinFormatter, inheritedAttributes, } = this;
591
+ const { min, max, step, handleKeyboard, activatedKnob, focusedKnob, hoveredKnob, pressedKnob, disabled, pin, ratioLower, ratioUpper, pinFormatter, inheritedAttributes, } = this;
541
592
  let barStart = `${ratioLower * 100}%`;
542
593
  let barEnd = `${100 - ratioUpper * 100}%`;
543
594
  const rtl = isRTL(this.el);
@@ -597,7 +648,9 @@ export class Range {
597
648
  ticks.push(tick);
598
649
  }
599
650
  }
600
- return (h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl),
651
+ return (h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl), onPointerDown: () => {
652
+ this.focusFromPointer = true;
653
+ },
601
654
  /**
602
655
  * Since the gesture has a threshold, the value
603
656
  * won't change until the user has dragged past
@@ -610,6 +663,7 @@ export class Range {
610
663
  * we need to listen for the "pointerUp" event.
611
664
  */
612
665
  onPointerUp: (ev) => {
666
+ this.focusFromPointer = false;
613
667
  /**
614
668
  * If the user drags the knob on the web
615
669
  * version (does not occur on mobile),
@@ -635,6 +689,11 @@ export class Range {
635
689
  'has-ticks': ticks.length > 0,
636
690
  }, role: "presentation", style: barStyle, part: "bar-active" })), renderKnob(rtl, {
637
691
  knob: 'A',
692
+ position: getKnobPosition('A', this.ratioA, this.ratioB, this.dualKnobs),
693
+ dualKnobs: this.dualKnobs,
694
+ activated: activatedKnob === 'A',
695
+ focused: focusedKnob === 'A',
696
+ hovered: hoveredKnob === 'A',
638
697
  pressed: pressedKnob === 'A',
639
698
  value: this.valA,
640
699
  ratio: this.ratioA,
@@ -647,9 +706,16 @@ export class Range {
647
706
  inheritedAttributes,
648
707
  onKnobFocus: this.onKnobFocus,
649
708
  onKnobBlur: this.onKnobBlur,
709
+ onKnobMouseEnter: this.onKnobMouseEnter,
710
+ onKnobMouseLeave: this.onKnobMouseLeave,
650
711
  }), this.dualKnobs &&
651
712
  renderKnob(rtl, {
652
713
  knob: 'B',
714
+ position: getKnobPosition('B', this.ratioA, this.ratioB, this.dualKnobs),
715
+ dualKnobs: this.dualKnobs,
716
+ activated: activatedKnob === 'B',
717
+ focused: focusedKnob === 'B',
718
+ hovered: hoveredKnob === 'B',
653
719
  pressed: pressedKnob === 'B',
654
720
  value: this.valB,
655
721
  ratio: this.ratioB,
@@ -662,6 +728,8 @@ export class Range {
662
728
  inheritedAttributes,
663
729
  onKnobFocus: this.onKnobFocus,
664
730
  onKnobBlur: this.onKnobBlur,
731
+ onKnobMouseEnter: this.onKnobMouseEnter,
732
+ onKnobMouseLeave: this.onKnobMouseLeave,
665
733
  })));
666
734
  }
667
735
  render() {
@@ -680,6 +748,12 @@ export class Range {
680
748
  const hasEndContent = (hasLabel && labelPlacement === 'end') || this.hasEndSlotContent;
681
749
  const needsEndAdjustment = inItem && !hasEndContent;
682
750
  const mode = getIonMode(this);
751
+ /**
752
+ * Determine the name and position of the pressed knob to apply
753
+ * Host classes for styling.
754
+ */
755
+ const pressedKnobName = dualKnobs ? pressedKnob === null || pressedKnob === void 0 ? void 0 : pressedKnob.toLowerCase() : undefined;
756
+ const pressedKnobPosition = dualKnobs && pressedKnob ? getKnobPosition(pressedKnob, this.ratioA, this.ratioB, dualKnobs) : undefined;
683
757
  /**
684
758
  * Determine if any knob is at the min or max value to
685
759
  * apply Host classes for styling.
@@ -687,21 +761,24 @@ export class Range {
687
761
  const valueAtMin = dualKnobs ? this.valA === min || this.valB === min : this.valA === min;
688
762
  const valueAtMax = dualKnobs ? this.valA === max || this.valB === max : this.valA === max;
689
763
  renderHiddenInput(true, el, this.name, JSON.stringify(this.getValue()), disabled);
690
- return (h(Host, { key: 'ed646a42d51b8fe22012198c354cbcf5a389c108', onFocusin: this.onFocus, onFocusout: this.onBlur, id: rangeId, class: createColorClasses(this.color, {
764
+ return (h(Host, { key: '8e439db9836fdefdc779573082c788e3ddb3ef73', onFocusin: this.onFocus, onFocusout: this.onBlur, id: rangeId, class: createColorClasses(this.color, {
691
765
  [mode]: true,
692
766
  'in-item': inItem,
693
767
  'range-disabled': disabled,
768
+ 'range-dual-knobs': dualKnobs,
694
769
  'range-pressed': pressedKnob !== undefined,
770
+ [`range-pressed-${pressedKnobName}`]: pressedKnob !== undefined && pressedKnobName !== undefined,
771
+ [`range-pressed-${pressedKnobPosition}`]: pressedKnob !== undefined && pressedKnobPosition !== undefined,
695
772
  'range-has-pin': pin,
696
773
  [`range-label-placement-${labelPlacement}`]: true,
697
774
  'range-item-start-adjustment': needsStartAdjustment,
698
775
  'range-item-end-adjustment': needsEndAdjustment,
699
776
  'range-value-min': valueAtMin,
700
777
  'range-value-max': valueAtMax,
701
- }) }, h("label", { key: '3083e4f2a624e3b268396acb4415f7c6ac44d851', class: "range-wrapper", id: "range-label" }, h("div", { key: '47b92f94d2a0381dd7c5cd3dda54ed2942096715', class: {
778
+ }) }, h("label", { key: 'e14c56db4d1eef11a3a0639c4bbed368041fc3eb', class: "range-wrapper", id: "range-label" }, h("div", { key: '16d125b0af19fdfa3663731657a7cca51a7ed442', class: {
702
779
  'label-text-wrapper': true,
703
780
  'label-text-wrapper-hidden': !hasLabel,
704
- }, part: "label" }, label !== undefined ? h("div", { class: "label-text" }, label) : h("slot", { name: "label" })), h("div", { key: '5341da8d19eb29091df680978a0e20cc8f2eec65', class: "native-wrapper" }, h("slot", { key: '09f1437078032676695442d8c827a16faa7dffe2', name: "start" }), this.renderRangeSlider(), h("slot", { key: '02b7781970ea4d44f10b5f4627a2ca36eca45f85', name: "end" })))));
781
+ }, part: "label" }, label !== undefined ? h("div", { class: "label-text" }, label) : h("slot", { name: "label" })), h("div", { key: '545cc52a3e675e9df328dab537a4e44b7f3c59f8', class: "native-wrapper" }, h("slot", { key: 'ac54b45a21c9db231ebe11498d508d05864b56fb', name: "start" }), this.renderRangeSlider(), h("slot", { key: '7182dba53df2ff878d572b30afda64b37082c82e', name: "end" })))));
705
782
  }
706
783
  static get is() { return "ion-range"; }
707
784
  static get encapsulation() { return "shadow"; }
@@ -1057,6 +1134,9 @@ export class Range {
1057
1134
  return {
1058
1135
  "ratioA": {},
1059
1136
  "ratioB": {},
1137
+ "activatedKnob": {},
1138
+ "focusedKnob": {},
1139
+ "hoveredKnob": {},
1060
1140
  "pressedKnob": {}
1061
1141
  };
1062
1142
  }
@@ -1203,7 +1283,7 @@ export class Range {
1203
1283
  }];
1204
1284
  }
1205
1285
  }
1206
- const renderKnob = (rtl, { knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard, pinFormatter, inheritedAttributes, onKnobFocus, onKnobBlur, }) => {
1286
+ const renderKnob = (rtl, { knob, position, dualKnobs, value, ratio, min, max, disabled, activated, focused, hovered, pressed, pin, handleKeyboard, pinFormatter, inheritedAttributes, onKnobFocus, onKnobBlur, onKnobMouseEnter, onKnobMouseLeave, }) => {
1207
1287
  const start = rtl ? 'right' : 'left';
1208
1288
  const knobStyle = () => {
1209
1289
  const style = {};
@@ -1224,16 +1304,66 @@ const renderKnob = (rtl, { knob, value, ratio, min, max, disabled, pressed, pin,
1224
1304
  ev.preventDefault();
1225
1305
  ev.stopPropagation();
1226
1306
  }
1227
- }, onFocus: () => onKnobFocus(knob), onBlur: onKnobBlur, class: {
1307
+ }, onFocus: () => onKnobFocus(knob), onBlur: onKnobBlur, onMouseEnter: () => onKnobMouseEnter(knob), onMouseLeave: onKnobMouseLeave, class: {
1228
1308
  'range-knob-handle': true,
1229
- 'range-knob-a': knob === 'A',
1230
- 'range-knob-b': knob === 'B',
1309
+ 'range-knob-handle-a': knob === 'A',
1310
+ 'range-knob-handle-b': knob === 'B',
1231
1311
  'range-knob-pressed': pressed,
1232
1312
  'range-knob-min': value === min,
1233
1313
  'range-knob-max': value === max,
1234
1314
  'ion-activatable': true,
1235
1315
  'ion-focusable': true,
1236
- }, style: knobStyle(), role: "slider", tabindex: disabled ? -1 : 0, "aria-label": ariaLabel !== undefined ? ariaLabel : null, "aria-labelledby": ariaLabel === undefined ? 'range-label' : null, "aria-valuemin": min, "aria-valuemax": max, "aria-disabled": disabled ? 'true' : null, "aria-valuenow": value }, pin && (h("div", { class: "range-pin", role: "presentation", part: "pin" }, pinFormatter(value))), h("div", { class: "range-knob", role: "presentation", part: "knob" })));
1316
+ 'ion-focused': focused,
1317
+ }, part: [
1318
+ 'knob-handle',
1319
+ dualKnobs && knob === 'A' && 'knob-handle-a',
1320
+ dualKnobs && knob === 'B' && 'knob-handle-b',
1321
+ dualKnobs && position === 'lower' && 'knob-handle-lower',
1322
+ dualKnobs && position === 'upper' && 'knob-handle-upper',
1323
+ pressed && 'pressed',
1324
+ focused && 'focused',
1325
+ hovered && 'hover',
1326
+ activated && 'activated',
1327
+ ]
1328
+ .filter(Boolean)
1329
+ .join(' '), style: knobStyle(), role: "slider", tabindex: disabled ? -1 : 0, "aria-label": ariaLabel !== undefined ? ariaLabel : null, "aria-labelledby": ariaLabel === undefined ? 'range-label' : null, "aria-valuemin": min, "aria-valuemax": max, "aria-disabled": disabled ? 'true' : null, "aria-valuenow": value }, pin && (h("div", { class: "range-pin", role: "presentation", part: [
1330
+ 'pin',
1331
+ dualKnobs && knob === 'A' && 'pin-a',
1332
+ dualKnobs && knob === 'B' && 'pin-b',
1333
+ dualKnobs && position === 'lower' && 'pin-lower',
1334
+ dualKnobs && position === 'upper' && 'pin-upper',
1335
+ pressed && 'pressed',
1336
+ focused && 'focused',
1337
+ hovered && 'hover',
1338
+ activated && 'activated',
1339
+ ]
1340
+ .filter(Boolean)
1341
+ .join(' ') }, pinFormatter(value))), h("div", { class: "range-knob", role: "presentation", part: [
1342
+ 'knob',
1343
+ dualKnobs && knob === 'A' && 'knob-a',
1344
+ dualKnobs && knob === 'B' && 'knob-b',
1345
+ dualKnobs && position === 'lower' && 'knob-lower',
1346
+ dualKnobs && position === 'upper' && 'knob-upper',
1347
+ pressed && 'pressed',
1348
+ focused && 'focused',
1349
+ hovered && 'hover',
1350
+ activated && 'activated',
1351
+ ]
1352
+ .filter(Boolean)
1353
+ .join(' ') })));
1354
+ };
1355
+ /**
1356
+ * Returns whether the given knob is at the lower or upper position based
1357
+ * on current ratios for the given knob.
1358
+ */
1359
+ const getKnobPosition = (knob, ratioA, ratioB, dualKnobs) => {
1360
+ if (dualKnobs) {
1361
+ if (knob === 'A') {
1362
+ return ratioA <= ratioB ? 'lower' : 'upper';
1363
+ }
1364
+ return ratioB <= ratioA ? 'lower' : 'upper';
1365
+ }
1366
+ return 'lower';
1237
1367
  };
1238
1368
  const ratioToValue = (ratio, min, max, step) => {
1239
1369
  let value = (max - min) * ratio;