@ionic/core 8.7.17-dev.11771865171.14f4c2cf → 8.7.17-dev.11771870864.19b2cde1

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.
Files changed (52) hide show
  1. package/components/ion-range.js +39 -152
  2. package/components/ion-segment-view.js +11 -5
  3. package/components/ion-select.js +3 -3
  4. package/components/ion-textarea.js +4 -4
  5. package/components/modal.js +206 -43
  6. package/dist/cjs/ion-modal.cjs.entry.js +206 -43
  7. package/dist/cjs/ion-range.cjs.entry.js +39 -149
  8. package/dist/cjs/ion-segment-view.cjs.entry.js +10 -5
  9. package/dist/cjs/ion-select_3.cjs.entry.js +3 -3
  10. package/dist/cjs/ion-textarea.cjs.entry.js +2 -2
  11. package/dist/cjs/ionic.cjs.js +1 -1
  12. package/dist/cjs/loader.cjs.js +1 -1
  13. package/dist/collection/components/modal/gestures/sheet.js +108 -11
  14. package/dist/collection/components/modal/gestures/swipe-to-close.js +54 -1
  15. package/dist/collection/components/modal/modal.js +98 -31
  16. package/dist/collection/components/range/range.js +42 -172
  17. package/dist/collection/components/segment-view/segment-view.ios.css +2 -1
  18. package/dist/collection/components/segment-view/segment-view.js +28 -3
  19. package/dist/collection/components/segment-view/segment-view.md.css +2 -1
  20. package/dist/collection/components/select/select.js +6 -3
  21. package/dist/collection/components/textarea/textarea.js +4 -4
  22. package/dist/docs.json +120 -144
  23. package/dist/esm/ion-modal.entry.js +206 -43
  24. package/dist/esm/ion-range.entry.js +39 -149
  25. package/dist/esm/ion-segment-view.entry.js +10 -5
  26. package/dist/esm/ion-select_3.entry.js +3 -3
  27. package/dist/esm/ion-textarea.entry.js +2 -2
  28. package/dist/esm/ionic.js +1 -1
  29. package/dist/esm/loader.js +1 -1
  30. package/dist/html.html-data.json +4 -0
  31. package/dist/ionic/ionic.esm.js +1 -1
  32. package/dist/ionic/p-012212e4.entry.js +4 -0
  33. package/dist/ionic/p-0f418a37.entry.js +4 -0
  34. package/dist/ionic/p-1838bed5.entry.js +4 -0
  35. package/dist/ionic/p-9a2d390d.entry.js +4 -0
  36. package/dist/ionic/{p-c19f63d0.entry.js → p-e1d1f3cc.entry.js} +1 -1
  37. package/dist/types/components/modal/gestures/sheet.d.ts +2 -2
  38. package/dist/types/components/modal/gestures/swipe-to-close.d.ts +2 -2
  39. package/dist/types/components/modal/modal-interface.d.ts +7 -0
  40. package/dist/types/components/modal/modal.d.ts +17 -1
  41. package/dist/types/components/range/range-interface.d.ts +0 -1
  42. package/dist/types/components/range/range.d.ts +4 -33
  43. package/dist/types/components/segment-view/segment-view.d.ts +4 -0
  44. package/dist/types/components/select/select.d.ts +3 -0
  45. package/dist/types/components.d.ts +27 -2
  46. package/hydrate/index.js +270 -228
  47. package/hydrate/index.mjs +270 -228
  48. package/package.json +1 -1
  49. package/dist/ionic/p-020af078.entry.js +0 -4
  50. package/dist/ionic/p-88367362.entry.js +0 -4
  51. package/dist/ionic/p-91840a80.entry.js +0 -4
  52. package/dist/ionic/p-991a3dd9.entry.js +0 -4
@@ -12,7 +12,7 @@ import { calculateSpringStep, handleCanDismiss } from "./utils";
12
12
  export const SwipeToCloseDefaults = {
13
13
  MIN_PRESENTING_SCALE: 0.915,
14
14
  };
15
- export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) => {
15
+ export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss, onDragStart, onDragMove, onDragEnd) => {
16
16
  /**
17
17
  * The step value at which a card modal
18
18
  * is eligible for dismissing via gesture.
@@ -122,6 +122,7 @@ export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismi
122
122
  disableContentScrollY(contentEl);
123
123
  }
124
124
  animation.progressStart(true, isOpen ? 1 : 0);
125
+ onDragStart();
125
126
  };
126
127
  const onMove = (detail) => {
127
128
  const { deltaY } = detail;
@@ -189,6 +190,13 @@ export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismi
189
190
  setCardStatusBarDark();
190
191
  }
191
192
  lastStep = clampedStep;
193
+ const eventDetail = {
194
+ currentY: detail.currentY,
195
+ deltaY: detail.deltaY,
196
+ velocityY: detail.velocityY,
197
+ progress: calculateProgress(el, detail.deltaY),
198
+ };
199
+ onDragMove(eventDetail);
192
200
  };
193
201
  const onEnd = (detail) => {
194
202
  const velocity = detail.velocityY;
@@ -247,6 +255,13 @@ export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismi
247
255
  else if (shouldComplete) {
248
256
  onDismiss();
249
257
  }
258
+ const eventDetail = {
259
+ currentY: detail.currentY,
260
+ deltaY: detail.deltaY,
261
+ velocityY: detail.velocityY,
262
+ progress: calculateProgress(el, detail.deltaY),
263
+ };
264
+ onDragEnd(eventDetail);
250
265
  };
251
266
  const gesture = createGesture({
252
267
  el,
@@ -264,3 +279,41 @@ export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismi
264
279
  const computeDuration = (remaining, velocity) => {
265
280
  return clamp(400, remaining / Math.abs(velocity * 1.1), 500);
266
281
  };
282
+ /**
283
+ * Calculates the progress of the swipe gesture.
284
+ *
285
+ * The progress is a value between 0 and 1 that represents how far
286
+ * the swipe has progressed towards closing the modal.
287
+ *
288
+ * A value closer to 1 means the modal is closer to being opened,
289
+ * while a value closer to 0 means the modal is closer to being closed.
290
+ *
291
+ * @param el The modal
292
+ * @param deltaY The change in Y position (positive when dragging down, negative when dragging up)
293
+ * @returns The progress of the swipe gesture
294
+ */
295
+ const calculateProgress = (el, deltaY) => {
296
+ const windowHeight = window.innerHeight;
297
+ // Position when fully open
298
+ const modalTop = el.getBoundingClientRect().top;
299
+ /**
300
+ * The distance between the top of the modal and the bottom of the screen
301
+ * is the total distance the modal needs to travel to be fully closed.
302
+ */
303
+ const totalDistance = windowHeight - modalTop;
304
+ /**
305
+ * The pull percentage is how far the user has swiped compared to the total
306
+ * distance needed to close the modal.
307
+ */
308
+ const pullPercentage = deltaY / totalDistance;
309
+ /**
310
+ * The progress is the inverse of the pull percentage because
311
+ * when the user starts swiping up, the progress should be close to 1,
312
+ * and when the user has swiped all the way down, the progress should be
313
+ * close to 0.
314
+ */
315
+ const progress = 1 - pullPercentage;
316
+ // Round to the nearest thousandth to avoid returning very small decimal
317
+ const roundedProgress = Math.round(progress * 1000) / 1000;
318
+ return Math.max(0, Math.min(1, roundedProgress));
319
+ };
@@ -501,32 +501,7 @@ export class Modal {
501
501
  return;
502
502
  }
503
503
  const statusBarStyle = (_a = this.statusBarStyle) !== null && _a !== void 0 ? _a : StatusBarStyle.Default;
504
- this.gesture = createSwipeToCloseGesture(el, ani, statusBarStyle, () => {
505
- /**
506
- * While the gesture animation is finishing
507
- * it is possible for a user to tap the backdrop.
508
- * This would result in the dismiss animation
509
- * being played again. Typically this is avoided
510
- * by setting `presented = false` on the overlay
511
- * component; however, we cannot do that here as
512
- * that would prevent the element from being
513
- * removed from the DOM.
514
- */
515
- this.gestureAnimationDismissing = true;
516
- /**
517
- * Reset the status bar style as the dismiss animation
518
- * starts otherwise the status bar will be the wrong
519
- * color for the duration of the dismiss animation.
520
- * The dismiss method does this as well, but
521
- * in this case it's only called once the animation
522
- * has finished.
523
- */
524
- setCardStatusBarDefault(this.statusBarStyle);
525
- this.animation.onFinish(async () => {
526
- await this.dismiss(undefined, GESTURE);
527
- this.gestureAnimationDismissing = false;
528
- });
529
- });
504
+ this.gesture = createSwipeToCloseGesture(el, ani, statusBarStyle, () => this.cardOnDismiss(), () => this.onDragStart(), (detail) => this.onDragMove(detail), (detail) => this.onDragEnd(detail));
530
505
  this.gesture.enable(true);
531
506
  }
532
507
  initSheetGesture() {
@@ -547,7 +522,7 @@ export class Modal {
547
522
  this.currentBreakpoint = breakpoint;
548
523
  this.ionBreakpointDidChange.emit({ breakpoint });
549
524
  }
550
- });
525
+ }, () => this.onDragStart(), (detail) => this.onDragMove(detail), (detail) => this.onDragEnd(detail));
551
526
  this.gesture = gesture;
552
527
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
553
528
  this.gesture.enable(true);
@@ -644,6 +619,32 @@ export class Modal {
644
619
  this.gestureAnimationDismissing = false;
645
620
  });
646
621
  }
622
+ cardOnDismiss() {
623
+ /**
624
+ * While the gesture animation is finishing
625
+ * it is possible for a user to tap the backdrop.
626
+ * This would result in the dismiss animation
627
+ * being played again. Typically this is avoided
628
+ * by setting `presented = false` on the overlay
629
+ * component; however, we cannot do that here as
630
+ * that would prevent the element from being
631
+ * removed from the DOM.
632
+ */
633
+ this.gestureAnimationDismissing = true;
634
+ /**
635
+ * Reset the status bar style as the dismiss animation
636
+ * starts otherwise the status bar will be the wrong
637
+ * color for the duration of the dismiss animation.
638
+ * The dismiss method does this as well, but
639
+ * in this case it's only called once the animation
640
+ * has finished.
641
+ */
642
+ setCardStatusBarDefault(this.statusBarStyle);
643
+ this.animation.onFinish(async () => {
644
+ await this.dismiss(undefined, GESTURE);
645
+ this.gestureAnimationDismissing = false;
646
+ });
647
+ }
647
648
  /**
648
649
  * Dismiss the modal overlay after it has been presented.
649
650
  * This is a no-op if the overlay has not been presented yet. If you want
@@ -967,6 +968,15 @@ export class Modal {
967
968
  (_a = this.parentRemovalObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
968
969
  this.parentRemovalObserver = undefined;
969
970
  }
971
+ onDragStart() {
972
+ this.ionDragStart.emit();
973
+ }
974
+ onDragMove(detail) {
975
+ this.ionDragMove.emit(detail);
976
+ }
977
+ onDragEnd(detail) {
978
+ this.ionDragEnd.emit(detail);
979
+ }
970
980
  render() {
971
981
  const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
972
982
  const showHandle = handle !== false && isSheetModal;
@@ -974,20 +984,20 @@ export class Modal {
974
984
  const isCardModal = presentingElement !== undefined && mode === 'ios';
975
985
  const isHandleCycle = handleBehavior === 'cycle';
976
986
  const isSheetModalWithHandle = isSheetModal && showHandle;
977
- return (h(Host, Object.assign({ key: '87328006ea6c75ebc518ace300438492a567223e', "no-router": true,
987
+ return (h(Host, Object.assign({ key: 'bca3cbdb2ce8e5127d27eae7494c434aaeddaf88', "no-router": true,
978
988
  // Allow the modal to be navigable when the handle is focusable
979
989
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
980
990
  zIndex: `${20000 + this.overlayIndex}`,
981
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: 'ee94ff8e09b691dd4ad4e4db1720f06bc3c5a469', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'bffd69b4635c22d9f249725bd952c1e93d5615c7', class: "modal-shadow" }), h("div", Object.assign({ key: '1d394d3c68916e464ff1fbf5242419f4a3d3cca1',
991
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: 'b1c4112123d84d8a9b9d455bcc17d884f99d9f28', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '080c8e6d37b311a1b5ad2655d99f18668abae211', class: "modal-shadow" }), h("div", Object.assign({ key: '8ba05e9520b1b5529bf0caeb766da5bf612d4d14',
982
992
  /*
983
993
  role and aria-modal must be used on the
984
994
  same element. They must also be set inside the
985
995
  shadow DOM otherwise ion-button will not be highlighted
986
996
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
987
997
  */
988
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '2dcf58792018e557e0c323baad2d672bc99c0bb1', class: "modal-handle",
998
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '2d2d5d731f55755f32ebcbf557f97486dc6202a6', class: "modal-handle",
989
999
  // Prevents the handle from receiving keyboard focus when it does not cycle
990
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '44164b1e8710c3895400ad9f44ecd99873874ad5', onSlotchange: this.onSlotChange }))));
1000
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'c717d19865dafba02e15db79bc0662c5d89e96fd', onSlotchange: this.onSlotChange }))));
991
1001
  }
992
1002
  static get is() { return "ion-modal"; }
993
1003
  static get encapsulation() { return "shadow"; }
@@ -1734,6 +1744,63 @@ export class Modal {
1734
1744
  "resolved": "void",
1735
1745
  "references": {}
1736
1746
  }
1747
+ }, {
1748
+ "method": "ionDragStart",
1749
+ "name": "ionDragStart",
1750
+ "bubbles": true,
1751
+ "cancelable": true,
1752
+ "composed": true,
1753
+ "docs": {
1754
+ "tags": [],
1755
+ "text": "Event that is emitted when the sheet modal or card modal gesture starts."
1756
+ },
1757
+ "complexType": {
1758
+ "original": "void",
1759
+ "resolved": "void",
1760
+ "references": {}
1761
+ }
1762
+ }, {
1763
+ "method": "ionDragMove",
1764
+ "name": "ionDragMove",
1765
+ "bubbles": true,
1766
+ "cancelable": true,
1767
+ "composed": true,
1768
+ "docs": {
1769
+ "tags": [],
1770
+ "text": "Event that is emitted when the sheet modal or card modal gesture moves."
1771
+ },
1772
+ "complexType": {
1773
+ "original": "ModalDragEventDetail",
1774
+ "resolved": "ModalDragEventDetail",
1775
+ "references": {
1776
+ "ModalDragEventDetail": {
1777
+ "location": "import",
1778
+ "path": "./modal-interface",
1779
+ "id": "src/components/modal/modal-interface.ts::ModalDragEventDetail"
1780
+ }
1781
+ }
1782
+ }
1783
+ }, {
1784
+ "method": "ionDragEnd",
1785
+ "name": "ionDragEnd",
1786
+ "bubbles": true,
1787
+ "cancelable": true,
1788
+ "composed": true,
1789
+ "docs": {
1790
+ "tags": [],
1791
+ "text": "Event that is emitted when the sheet modal or card modal gesture ends."
1792
+ },
1793
+ "complexType": {
1794
+ "original": "ModalDragEventDetail",
1795
+ "resolved": "ModalDragEventDetail",
1796
+ "references": {
1797
+ "ModalDragEventDetail": {
1798
+ "location": "import",
1799
+ "path": "./modal-interface",
1800
+ "id": "src/components/modal/modal-interface.ts::ModalDragEventDetail"
1801
+ }
1802
+ }
1803
+ }
1737
1804
  }];
1738
1805
  }
1739
1806
  static get methods() {
@@ -17,30 +17,13 @@ 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.
21
20
  * @part tick - An inactive tick mark.
22
21
  * @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.
23
24
  * @part bar - The inactive part of the bar.
24
25
  * @part bar-active - The active part of the bar.
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`.
26
+ * @part label - The label text describing the range.
44
27
  */
45
28
  export class Range {
46
29
  constructor() {
@@ -51,7 +34,6 @@ export class Range {
51
34
  this.inheritedAttributes = {};
52
35
  this.contentEl = null;
53
36
  this.initialContentScrollY = true;
54
- this.focusFromPointer = false;
55
37
  this.ratioA = 0;
56
38
  this.ratioB = 0;
57
39
  /**
@@ -161,29 +143,6 @@ export class Range {
161
143
  this.gesture.enable(!this.disabled);
162
144
  }
163
145
  };
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
- };
187
146
  this.handleKeyboard = (knob, isIncrease) => {
188
147
  const { ensureValueInBounds } = this;
189
148
  let step = this.step;
@@ -206,7 +165,6 @@ export class Range {
206
165
  this.onBlur = () => {
207
166
  if (this.hasFocus) {
208
167
  this.hasFocus = false;
209
- this.focusedKnob = undefined;
210
168
  this.ionBlur.emit();
211
169
  }
212
170
  };
@@ -217,20 +175,21 @@ export class Range {
217
175
  }
218
176
  };
219
177
  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
230
178
  if (!this.hasFocus) {
231
179
  this.hasFocus = true;
232
180
  this.ionFocus.emit();
233
181
  }
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
+ }
234
193
  };
235
194
  this.onKnobBlur = () => {
236
195
  // Check if focus is moving to another knob within the same range
@@ -242,18 +201,18 @@ export class Range {
242
201
  if (!isStillFocusedOnKnob) {
243
202
  if (this.hasFocus) {
244
203
  this.hasFocus = false;
245
- this.focusedKnob = undefined;
246
204
  this.ionBlur.emit();
247
205
  }
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
+ }
248
213
  }
249
214
  }, 0);
250
215
  };
251
- this.onKnobMouseEnter = (knob) => {
252
- this.hoveredKnob = knob;
253
- };
254
- this.onKnobMouseLeave = () => {
255
- this.hoveredKnob = undefined;
256
- };
257
216
  }
258
217
  debounceChanged() {
259
218
  const { ionInput, debounce, originalIonInput } = this;
@@ -330,7 +289,6 @@ export class Range {
330
289
  this.originalIonInput = this.ionInput;
331
290
  this.setupGesture();
332
291
  this.updateRatio();
333
- this.setupActivatedObserver();
334
292
  this.didLoad = true;
335
293
  }
336
294
  connectedCallback() {
@@ -356,10 +314,6 @@ export class Range {
356
314
  this.gesture.destroy();
357
315
  this.gesture = undefined;
358
316
  }
359
- if (this.activatedObserver) {
360
- this.activatedObserver.disconnect();
361
- this.activatedObserver = undefined;
362
- }
363
317
  }
364
318
  getValue() {
365
319
  var _a;
@@ -512,6 +466,7 @@ export class Range {
512
466
  ratio = 1 - ratio;
513
467
  }
514
468
  this.pressedKnob = !this.dualKnobs || Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio) ? 'A' : 'B';
469
+ this.setFocus(this.pressedKnob);
515
470
  }
516
471
  get valA() {
517
472
  return ratioToValue(this.ratioA, this.min, this.max, this.step);
@@ -538,23 +493,9 @@ export class Range {
538
493
  updateRatio() {
539
494
  const value = this.getValue();
540
495
  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
- */
546
496
  if (this.dualKnobs) {
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
- }
497
+ this.ratioA = valueToRatio(value.lower, min, max);
498
+ this.ratioB = valueToRatio(value.upper, min, max);
558
499
  }
559
500
  else {
560
501
  this.ratioA = valueToRatio(value, min, max);
@@ -571,6 +512,14 @@ export class Range {
571
512
  };
572
513
  this.noUpdate = false;
573
514
  }
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
+ }
574
523
  /**
575
524
  * Returns true if content was passed to the "start" slot
576
525
  */
@@ -588,7 +537,7 @@ export class Range {
588
537
  }
589
538
  renderRangeSlider() {
590
539
  var _a;
591
- const { min, max, step, handleKeyboard, activatedKnob, focusedKnob, hoveredKnob, pressedKnob, disabled, pin, ratioLower, ratioUpper, pinFormatter, inheritedAttributes, } = this;
540
+ const { min, max, step, handleKeyboard, pressedKnob, disabled, pin, ratioLower, ratioUpper, pinFormatter, inheritedAttributes, } = this;
592
541
  let barStart = `${ratioLower * 100}%`;
593
542
  let barEnd = `${100 - ratioUpper * 100}%`;
594
543
  const rtl = isRTL(this.el);
@@ -648,9 +597,7 @@ export class Range {
648
597
  ticks.push(tick);
649
598
  }
650
599
  }
651
- return (h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl), onPointerDown: () => {
652
- this.focusFromPointer = true;
653
- },
600
+ return (h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl),
654
601
  /**
655
602
  * Since the gesture has a threshold, the value
656
603
  * won't change until the user has dragged past
@@ -663,7 +610,6 @@ export class Range {
663
610
  * we need to listen for the "pointerUp" event.
664
611
  */
665
612
  onPointerUp: (ev) => {
666
- this.focusFromPointer = false;
667
613
  /**
668
614
  * If the user drags the knob on the web
669
615
  * version (does not occur on mobile),
@@ -689,11 +635,6 @@ export class Range {
689
635
  'has-ticks': ticks.length > 0,
690
636
  }, role: "presentation", style: barStyle, part: "bar-active" })), renderKnob(rtl, {
691
637
  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',
697
638
  pressed: pressedKnob === 'A',
698
639
  value: this.valA,
699
640
  ratio: this.ratioA,
@@ -706,16 +647,9 @@ export class Range {
706
647
  inheritedAttributes,
707
648
  onKnobFocus: this.onKnobFocus,
708
649
  onKnobBlur: this.onKnobBlur,
709
- onKnobMouseEnter: this.onKnobMouseEnter,
710
- onKnobMouseLeave: this.onKnobMouseLeave,
711
650
  }), this.dualKnobs &&
712
651
  renderKnob(rtl, {
713
652
  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',
719
653
  pressed: pressedKnob === 'B',
720
654
  value: this.valB,
721
655
  ratio: this.ratioB,
@@ -728,8 +662,6 @@ export class Range {
728
662
  inheritedAttributes,
729
663
  onKnobFocus: this.onKnobFocus,
730
664
  onKnobBlur: this.onKnobBlur,
731
- onKnobMouseEnter: this.onKnobMouseEnter,
732
- onKnobMouseLeave: this.onKnobMouseLeave,
733
665
  })));
734
666
  }
735
667
  render() {
@@ -748,12 +680,6 @@ export class Range {
748
680
  const hasEndContent = (hasLabel && labelPlacement === 'end') || this.hasEndSlotContent;
749
681
  const needsEndAdjustment = inItem && !hasEndContent;
750
682
  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;
757
683
  /**
758
684
  * Determine if any knob is at the min or max value to
759
685
  * apply Host classes for styling.
@@ -761,24 +687,21 @@ export class Range {
761
687
  const valueAtMin = dualKnobs ? this.valA === min || this.valB === min : this.valA === min;
762
688
  const valueAtMax = dualKnobs ? this.valA === max || this.valB === max : this.valA === max;
763
689
  renderHiddenInput(true, el, this.name, JSON.stringify(this.getValue()), disabled);
764
- return (h(Host, { key: '8e439db9836fdefdc779573082c788e3ddb3ef73', onFocusin: this.onFocus, onFocusout: this.onBlur, id: rangeId, class: createColorClasses(this.color, {
690
+ return (h(Host, { key: 'ed646a42d51b8fe22012198c354cbcf5a389c108', onFocusin: this.onFocus, onFocusout: this.onBlur, id: rangeId, class: createColorClasses(this.color, {
765
691
  [mode]: true,
766
692
  'in-item': inItem,
767
693
  'range-disabled': disabled,
768
- 'range-dual-knobs': dualKnobs,
769
694
  'range-pressed': pressedKnob !== undefined,
770
- [`range-pressed-${pressedKnobName}`]: pressedKnob !== undefined && pressedKnobName !== undefined,
771
- [`range-pressed-${pressedKnobPosition}`]: pressedKnob !== undefined && pressedKnobPosition !== undefined,
772
695
  'range-has-pin': pin,
773
696
  [`range-label-placement-${labelPlacement}`]: true,
774
697
  'range-item-start-adjustment': needsStartAdjustment,
775
698
  'range-item-end-adjustment': needsEndAdjustment,
776
699
  'range-value-min': valueAtMin,
777
700
  'range-value-max': valueAtMax,
778
- }) }, h("label", { key: 'e14c56db4d1eef11a3a0639c4bbed368041fc3eb', class: "range-wrapper", id: "range-label" }, h("div", { key: '16d125b0af19fdfa3663731657a7cca51a7ed442', class: {
701
+ }) }, h("label", { key: '3083e4f2a624e3b268396acb4415f7c6ac44d851', class: "range-wrapper", id: "range-label" }, h("div", { key: '47b92f94d2a0381dd7c5cd3dda54ed2942096715', class: {
779
702
  'label-text-wrapper': true,
780
703
  'label-text-wrapper-hidden': !hasLabel,
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" })))));
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" })))));
782
705
  }
783
706
  static get is() { return "ion-range"; }
784
707
  static get encapsulation() { return "shadow"; }
@@ -1134,9 +1057,6 @@ export class Range {
1134
1057
  return {
1135
1058
  "ratioA": {},
1136
1059
  "ratioB": {},
1137
- "activatedKnob": {},
1138
- "focusedKnob": {},
1139
- "hoveredKnob": {},
1140
1060
  "pressedKnob": {}
1141
1061
  };
1142
1062
  }
@@ -1283,7 +1203,7 @@ export class Range {
1283
1203
  }];
1284
1204
  }
1285
1205
  }
1286
- const renderKnob = (rtl, { knob, position, dualKnobs, value, ratio, min, max, disabled, activated, focused, hovered, pressed, pin, handleKeyboard, pinFormatter, inheritedAttributes, onKnobFocus, onKnobBlur, onKnobMouseEnter, onKnobMouseLeave, }) => {
1206
+ const renderKnob = (rtl, { knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard, pinFormatter, inheritedAttributes, onKnobFocus, onKnobBlur, }) => {
1287
1207
  const start = rtl ? 'right' : 'left';
1288
1208
  const knobStyle = () => {
1289
1209
  const style = {};
@@ -1304,66 +1224,16 @@ const renderKnob = (rtl, { knob, position, dualKnobs, value, ratio, min, max, di
1304
1224
  ev.preventDefault();
1305
1225
  ev.stopPropagation();
1306
1226
  }
1307
- }, onFocus: () => onKnobFocus(knob), onBlur: onKnobBlur, onMouseEnter: () => onKnobMouseEnter(knob), onMouseLeave: onKnobMouseLeave, class: {
1227
+ }, onFocus: () => onKnobFocus(knob), onBlur: onKnobBlur, class: {
1308
1228
  'range-knob-handle': true,
1309
- 'range-knob-handle-a': knob === 'A',
1310
- 'range-knob-handle-b': knob === 'B',
1229
+ 'range-knob-a': knob === 'A',
1230
+ 'range-knob-b': knob === 'B',
1311
1231
  'range-knob-pressed': pressed,
1312
1232
  'range-knob-min': value === min,
1313
1233
  'range-knob-max': value === max,
1314
1234
  'ion-activatable': true,
1315
1235
  'ion-focusable': true,
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';
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" })));
1367
1237
  };
1368
1238
  const ratioToValue = (ratio, min, max, step) => {
1369
1239
  let value = (max - min) * ratio;