@ionic/core 8.5.8-dev.11748374591.17686118 → 8.5.8-dev.11748388365.11ad9dfe

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 (67) hide show
  1. package/components/button.js +23 -5
  2. package/components/ion-datetime.js +27 -51
  3. package/components/ion-input-password-toggle.js +2 -2
  4. package/components/ion-searchbar.js +4 -4
  5. package/components/modal.js +159 -68
  6. package/components/picker-column2.js +3 -3
  7. package/css/core.css +1 -1
  8. package/css/core.css.map +1 -1
  9. package/css/ionic.bundle.css +1 -1
  10. package/css/ionic.bundle.css.map +1 -1
  11. package/dist/cjs/ion-button_2.cjs.entry.js +19 -3
  12. package/dist/cjs/ion-datetime_3.cjs.entry.js +30 -54
  13. package/dist/cjs/ion-input-password-toggle.cjs.entry.js +2 -2
  14. package/dist/cjs/ion-modal.cjs.entry.js +159 -68
  15. package/dist/cjs/ion-searchbar.cjs.entry.js +4 -4
  16. package/dist/cjs/ionic.cjs.js +1 -1
  17. package/dist/cjs/loader.cjs.js +1 -1
  18. package/dist/collection/components/button/button.js +23 -3
  19. package/dist/collection/components/datetime/datetime.js +27 -51
  20. package/dist/collection/components/input-password-toggle/input-password-toggle.js +2 -2
  21. package/dist/collection/components/modal/animations/ios.enter.js +41 -1
  22. package/dist/collection/components/modal/animations/ios.leave.js +24 -2
  23. package/dist/collection/components/modal/animations/md.enter.js +41 -1
  24. package/dist/collection/components/modal/animations/md.leave.js +24 -2
  25. package/dist/collection/components/modal/gestures/sheet.js +29 -62
  26. package/dist/collection/components/picker-legacy-column/picker-column.js +3 -3
  27. package/dist/collection/components/searchbar/searchbar.js +5 -5
  28. package/dist/docs.json +2 -2
  29. package/dist/esm/ion-button_2.entry.js +20 -4
  30. package/dist/esm/ion-datetime_3.entry.js +30 -54
  31. package/dist/esm/ion-input-password-toggle.entry.js +2 -2
  32. package/dist/esm/ion-modal.entry.js +159 -68
  33. package/dist/esm/ion-searchbar.entry.js +4 -4
  34. package/dist/esm/ionic.js +1 -1
  35. package/dist/esm/loader.js +1 -1
  36. package/dist/esm-es5/ion-button_2.entry.js +1 -1
  37. package/dist/esm-es5/ion-datetime_3.entry.js +1 -1
  38. package/dist/esm-es5/ion-input-password-toggle.entry.js +1 -1
  39. package/dist/esm-es5/ion-modal.entry.js +1 -1
  40. package/dist/esm-es5/ion-searchbar.entry.js +1 -1
  41. package/dist/esm-es5/ionic.js +1 -1
  42. package/dist/esm-es5/loader.js +1 -1
  43. package/dist/html.html-data.json +1 -1
  44. package/dist/ionic/ionic.esm.js +1 -1
  45. package/dist/ionic/p-12914457.entry.js +4 -0
  46. package/dist/ionic/{p-50cb2e8b.system.entry.js → p-3cee5d7c.system.entry.js} +2 -2
  47. package/dist/ionic/p-3d2b2787.system.entry.js +4 -0
  48. package/dist/ionic/{p-7611ede3.system.entry.js → p-3ec227cd.system.entry.js} +1 -1
  49. package/dist/ionic/p-4090ce24.entry.js +4 -0
  50. package/dist/ionic/p-8b8b4530.system.entry.js +4 -0
  51. package/dist/ionic/p-94a18d00.entry.js +4 -0
  52. package/dist/ionic/p-cec902a0.system.entry.js +4 -0
  53. package/dist/ionic/{p-891553d8.entry.js → p-e7f732ef.entry.js} +1 -1
  54. package/dist/ionic/p-f725bf9e.system.js +2 -2
  55. package/dist/ionic/{p-a835aca8.entry.js → p-ffa2125f.entry.js} +1 -1
  56. package/dist/types/components/button/button.d.ts +11 -0
  57. package/dist/types/components/searchbar/searchbar.d.ts +1 -1
  58. package/dist/types/components.d.ts +2 -2
  59. package/hydrate/index.js +213 -131
  60. package/hydrate/index.mjs +213 -131
  61. package/package.json +2 -2
  62. package/dist/ionic/p-57957c0f.entry.js +0 -4
  63. package/dist/ionic/p-6b8530f5.system.entry.js +0 -4
  64. package/dist/ionic/p-b5432d15.entry.js +0 -4
  65. package/dist/ionic/p-b7242f55.entry.js +0 -4
  66. package/dist/ionic/p-c1b7d311.system.entry.js +0 -4
  67. package/dist/ionic/p-db06d822.system.entry.js +0 -4
@@ -210,6 +210,20 @@ const Button = class {
210
210
  this.formButtonEl.disabled = disabled;
211
211
  }
212
212
  }
213
+ /**
214
+ * This component is used within the `ion-input-password-toggle` component
215
+ * to toggle the visibility of the password input.
216
+ * These attributes need to update based on the state of the password input.
217
+ * Otherwise, the values will be stale.
218
+ *
219
+ * @param newValue
220
+ * @param _oldValue
221
+ * @param propName
222
+ */
223
+ onAriaChanged(newValue, _oldValue, propName) {
224
+ this.inheritedAttributes = Object.assign(Object.assign({}, this.inheritedAttributes), { [propName]: newValue });
225
+ index.forceUpdate(this);
226
+ }
213
227
  /**
214
228
  * This is responsible for rendering a hidden native
215
229
  * button element inside the associated form. This allows
@@ -347,7 +361,7 @@ const Button = class {
347
361
  {
348
362
  type !== 'button' && this.renderHiddenButton();
349
363
  }
350
- return (index.h(index.Host, { key: 'e213b0bb76b3f90f883b1a0ea463bb86c2df69c3', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: theme.createColorClasses(color, {
364
+ return (index.h(index.Host, { key: 'b105ad09215adb3ca2298acdadf0dc9154bbb9b0', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: theme.createColorClasses(color, {
351
365
  [mode]: true,
352
366
  [buttonType]: true,
353
367
  [`${buttonType}-${expand}`]: expand !== undefined,
@@ -362,11 +376,13 @@ const Button = class {
362
376
  'button-disabled': disabled,
363
377
  'ion-activatable': true,
364
378
  'ion-focusable': true,
365
- }) }, index.h(TagType, Object.assign({ key: 'b7c2a46fb994024841219316f4089335fa463d84' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), index.h("span", { key: '3e07a5c9f86836f9fbaefc6c617bdde6eb6f70cd', class: "button-inner" }, index.h("slot", { key: '41c08ae09aed16faaa57707d229ff75d97f0731c', name: "icon-only", onSlotchange: this.slotChanged }), index.h("slot", { key: 'ab8b749e4572884cc04970a7594466b4a24c886e', name: "start" }), index.h("slot", { key: '397f7ff48ea45e82029414be95d29ae86e12c3a9' }), index.h("slot", { key: '9a5627713002e8be3738d392b616f6e951b70e12', name: "end" })), mode === 'md' && index.h("ion-ripple-effect", { key: '105566543ceda102474edab7ca8a44c7e71af589', type: this.rippleType }))));
379
+ }) }, index.h(TagType, Object.assign({ key: '66b4e7112bcb9e41d5a723fbbadb0a3104f9ee1d' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), index.h("span", { key: '1439fc3da280221028dcf7ce8ec9dab273c4d4bb', class: "button-inner" }, index.h("slot", { key: 'd5269ae1afc87ec7b99746032f59cbae93720a9f', name: "icon-only", onSlotchange: this.slotChanged }), index.h("slot", { key: '461c83e97aa246aa86d83e14f1e15a288d35041e', name: "start" }), index.h("slot", { key: '807170d47101f9f6a333dd4ff489c89284f306fe' }), index.h("slot", { key: 'e67f116dd0349a0d27893e4f3ff0ccef1d402f80', name: "end" })), mode === 'md' && index.h("ion-ripple-effect", { key: '273f0bd9645a36c1bfd18a5c2ab4f81e22b7b989', type: this.rippleType }))));
366
380
  }
367
381
  get el() { return index.getElement(this); }
368
382
  static get watchers() { return {
369
- "disabled": ["disabledChanged"]
383
+ "disabled": ["disabledChanged"],
384
+ "aria-checked": ["onAriaChanged"],
385
+ "aria-label": ["onAriaChanged"]
370
386
  }; }
371
387
  };
372
388
  Button.style = {
@@ -687,19 +687,18 @@ const Datetime = class {
687
687
  data.warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
688
688
  }
689
689
  /**
690
- * If there are multiple values, pick an arbitrary one to clamp to. This way,
691
- * if the values are across months, we always show at least one of them. Note
692
- * that the values don't necessarily have to be in order.
690
+ * If there are multiple values, clamp to the last one.
691
+ * This is because the last value is the one that the user
692
+ * has most recently interacted with.
693
693
  */
694
- const singleValue = Array.isArray(valueToProcess) ? valueToProcess[0] : valueToProcess;
694
+ const singleValue = Array.isArray(valueToProcess) ? valueToProcess[valueToProcess.length - 1] : valueToProcess;
695
695
  const targetValue = data.clampDate(singleValue, minParts, maxParts);
696
696
  const { month, day, year, hour, minute } = targetValue;
697
697
  const ampm = data.parseAmPm(hour);
698
698
  /**
699
- * Since `activeParts` indicates a value that
700
- * been explicitly selected either by the
701
- * user or the app, only update `activeParts`
702
- * if the `value` property is set.
699
+ * Since `activeParts` indicates a value that been explicitly selected
700
+ * either by the user or the app, only update `activeParts` if the
701
+ * `value` property is set.
703
702
  */
704
703
  if (hasValue) {
705
704
  if (Array.isArray(valueToProcess)) {
@@ -724,51 +723,28 @@ const Datetime = class {
724
723
  */
725
724
  this.activeParts = [];
726
725
  }
727
- /**
728
- * Only animate if:
729
- * 1. We're using grid style (wheel style pickers should just jump to new value)
730
- * 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
731
- * 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
732
- * 4. The month/year picker is not open (since you wouldn't see the animation anyway)
733
- */
734
726
  const didChangeMonth = (month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
735
727
  const bodyIsVisible = el.classList.contains('datetime-ready');
736
728
  const { isGridStyle, showMonthAndYear } = this;
737
- let areAllSelectedDatesInSameMonth = true;
738
- if (Array.isArray(valueToProcess)) {
739
- const firstMonth = valueToProcess[0].month;
740
- for (const date of valueToProcess) {
741
- if (date.month !== firstMonth) {
742
- areAllSelectedDatesInSameMonth = false;
743
- break;
744
- }
745
- }
729
+ if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
730
+ /**
731
+ * Only animate if:
732
+ * 1. We're using grid style (wheel style pickers should just jump to new value)
733
+ * 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
734
+ * 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
735
+ * 4. The month/year picker is not open (since you wouldn't see the animation anyway)
736
+ */
737
+ this.animateToDate(targetValue);
746
738
  }
747
- /**
748
- * If there is more than one date selected
749
- * and the dates aren't all in the same month,
750
- * then we should neither animate to the date
751
- * nor update the working parts because we do
752
- * not know which date the user wants to view.
753
- */
754
- if (areAllSelectedDatesInSameMonth) {
755
- if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
756
- this.animateToDate(targetValue);
757
- }
758
- else {
759
- /**
760
- * We only need to do this if we didn't just animate to a new month,
761
- * since that calls prevMonth/nextMonth which calls setWorkingParts for us.
762
- */
763
- this.setWorkingParts({
764
- month,
765
- day,
766
- year,
767
- hour,
768
- minute,
769
- ampm,
770
- });
771
- }
739
+ else {
740
+ this.setWorkingParts({
741
+ month,
742
+ day,
743
+ year,
744
+ hour,
745
+ minute,
746
+ ampm,
747
+ });
772
748
  }
773
749
  };
774
750
  this.animateToDate = async (targetValue) => {
@@ -1763,7 +1739,7 @@ const Datetime = class {
1763
1739
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
1764
1740
  const hasWheelVariant = hasDatePresentation && preferWheel;
1765
1741
  helpers.renderHiddenInput(true, el, name, data.formatValue(value), disabled);
1766
- return (index$1.h(index$1.Host, { key: 'c3dfea8f46fcbcef38eb9e8a69b1b46a4e4b82fd', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, theme.createColorClasses(color, {
1742
+ return (index$1.h(index$1.Host, { key: '20e64fb4cfe3fbd74b24038c89e00651d78b500c', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, theme.createColorClasses(color, {
1767
1743
  [mode]: true,
1768
1744
  ['datetime-readonly']: readonly,
1769
1745
  ['datetime-disabled']: disabled,
@@ -1773,7 +1749,7 @@ const Datetime = class {
1773
1749
  [`datetime-size-${size}`]: true,
1774
1750
  [`datetime-prefer-wheel`]: hasWheelVariant,
1775
1751
  [`datetime-grid`]: isGridStyle,
1776
- })) }, index$1.h("div", { key: '75c91243cf6a51f44b83d7cf7d8c0c96bfd3c83f', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1752
+ })) }, index$1.h("div", { key: '488fe7bf6b9052e208d65206a580d5d65b945a62', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1777
1753
  }
1778
1754
  get el() { return index$1.getElement(this); }
1779
1755
  static get watchers() { return {
@@ -2383,7 +2359,7 @@ const PickerColumnCmp = class {
2383
2359
  const colEl = this.optsEl;
2384
2360
  if (colEl) {
2385
2361
  // DOM READ
2386
- // We perfom a DOM read over a rendered item, this needs to happen after the first render or after the the column has changed
2362
+ // We perfom a DOM read over a rendered item, this needs to happen after the first render or after the column has changed
2387
2363
  this.optHeight = colEl.firstElementChild ? colEl.firstElementChild.clientHeight : 0;
2388
2364
  }
2389
2365
  this.refresh(forceRefresh, animated);
@@ -2391,9 +2367,9 @@ const PickerColumnCmp = class {
2391
2367
  render() {
2392
2368
  const col = this.col;
2393
2369
  const mode = ionicGlobal.getIonMode(this);
2394
- return (index$1.h(index$1.Host, { key: '88a3c9397c9ac92dd814074c8ae6ecf8e3420a2c', class: Object.assign({ [mode]: true, 'picker-col': true, 'picker-opts-left': this.col.align === 'left', 'picker-opts-right': this.col.align === 'right' }, theme.getClassMap(col.cssClass)), style: {
2370
+ return (index$1.h(index$1.Host, { key: '10ef653cbae8e1ed92a23fa17f3396082e35951f', class: Object.assign({ [mode]: true, 'picker-col': true, 'picker-opts-left': this.col.align === 'left', 'picker-opts-right': this.col.align === 'right' }, theme.getClassMap(col.cssClass)), style: {
2395
2371
  'max-width': this.col.columnWidth,
2396
- } }, col.prefix && (index$1.h("div", { key: '4491a705d15337e6f45f3cf6fd21af5242474729', class: "picker-prefix", style: { width: col.prefixWidth } }, col.prefix)), index$1.h("div", { key: 'b0dd4b7a7a4c1edc4b73e7fb134ac85264072365', class: "picker-opts", style: { maxWidth: col.optionsWidth }, ref: (el) => (this.optsEl = el) }, col.options.map((o, index) => (index$1.h("button", { "aria-label": o.ariaLabel, class: { 'picker-opt': true, 'picker-opt-disabled': !!o.disabled }, "opt-index": index }, o.text)))), col.suffix && (index$1.h("div", { key: 'c16419ce6481d60fc3ba6b8d102a4edf0ede02aa', class: "picker-suffix", style: { width: col.suffixWidth } }, col.suffix))));
2372
+ } }, col.prefix && (index$1.h("div", { key: '253066708012034206b1d3d466ea48d0a3be3323', class: "picker-prefix", style: { width: col.prefixWidth } }, col.prefix)), index$1.h("div", { key: '15fa07307d4d2b4994504a5bf2de8db05b51cd2c', class: "picker-opts", style: { maxWidth: col.optionsWidth }, ref: (el) => (this.optsEl = el) }, col.options.map((o, index) => (index$1.h("button", { "aria-label": o.ariaLabel, class: { 'picker-opt': true, 'picker-opt-disabled': !!o.disabled }, "opt-index": index }, o.text)))), col.suffix && (index$1.h("div", { key: '4db779830b1e6aa6b324e5a72541d4f610ac1ff5', class: "picker-suffix", style: { width: col.suffixWidth } }, col.suffix))));
2397
2373
  }
2398
2374
  get el() { return index$1.getElement(this); }
2399
2375
  static get watchers() { return {
@@ -71,14 +71,14 @@ const InputPasswordToggle = class {
71
71
  const isPasswordVisible = type === 'text';
72
72
  return (index.h(index.Host, { key: '1a28e078c83e74c72d8bb8189ece93ec2e3fa3d0', class: theme.createColorClasses(color, {
73
73
  [mode]: true,
74
- }) }, index.h("ion-button", { key: '039d1bab764093bb6fe4a34299b0872abda087fd', mode: mode, color: color, fill: "clear", shape: "round", "aria-checked": isPasswordVisible ? 'true' : 'false', "aria-label": "show password", role: "switch", type: "button", onPointerDown: (ev) => {
74
+ }) }, index.h("ion-button", { key: '61591d11d97f3065240e51d8887f831339fd36cf', mode: mode, color: color, fill: "clear", shape: "round", "aria-checked": isPasswordVisible ? 'true' : 'false', "aria-label": isPasswordVisible ? 'Hide password' : 'Show password', role: "switch", type: "button", onPointerDown: (ev) => {
75
75
  /**
76
76
  * This prevents mobile browsers from
77
77
  * blurring the input when the password toggle
78
78
  * button is activated.
79
79
  */
80
80
  ev.preventDefault();
81
- }, onClick: this.togglePasswordVisibility }, index.h("ion-icon", { key: '26477ee97b808c3d79944bf5e33d4e05f1ae0b3f', slot: "icon-only", "aria-hidden": "true", icon: isPasswordVisible ? hidePasswordIcon : showPasswordIcon }))));
81
+ }, onClick: this.togglePasswordVisibility }, index.h("ion-icon", { key: '9d98e6675d5e2adea5c0f56ab67e98b6f57521dd', slot: "icon-only", "aria-hidden": "true", icon: isPasswordVisible ? hidePasswordIcon : showPasswordIcon }))));
82
82
  }
83
83
  get el() { return index.getElement(this); }
84
84
  static get watchers() { return {
@@ -588,7 +588,47 @@ const iosEnterAnimation = (baseEl, opts) => {
588
588
  .addElement(baseEl)
589
589
  .easing('cubic-bezier(0.32,0.72,0,1)')
590
590
  .duration(500)
591
- .addAnimation([wrapperAnimation]);
591
+ .addAnimation([wrapperAnimation])
592
+ .beforeAddWrite(() => {
593
+ if (expandToScroll) {
594
+ // Scroll can only be done when the modal is fully expanded.
595
+ return;
596
+ }
597
+ /**
598
+ * There are some browsers that causes flickering when
599
+ * dragging the content when scroll is enabled at every
600
+ * breakpoint. This is due to the wrapper element being
601
+ * transformed off the screen and having a snap animation.
602
+ *
603
+ * A workaround is to clone the footer element and append
604
+ * it outside of the wrapper element. This way, the footer
605
+ * is still visible and the drag can be done without
606
+ * flickering. The original footer is hidden until the modal
607
+ * is dismissed. This maintains the animation of the footer
608
+ * when the modal is dismissed.
609
+ *
610
+ * The workaround needs to be done before the animation starts
611
+ * so there are no flickering issues.
612
+ */
613
+ const ionFooter = baseEl.querySelector('ion-footer');
614
+ /**
615
+ * This check is needed to prevent more than one footer
616
+ * from being appended to the shadow root.
617
+ * Otherwise, iOS and MD enter animations would append
618
+ * the footer twice.
619
+ */
620
+ const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
621
+ if (ionFooter && !ionFooterAlreadyAppended) {
622
+ const footerHeight = ionFooter.clientHeight;
623
+ const clonedFooter = ionFooter.cloneNode(true);
624
+ baseEl.shadowRoot.appendChild(clonedFooter);
625
+ ionFooter.style.setProperty('display', 'none');
626
+ ionFooter.setAttribute('aria-hidden', 'true');
627
+ // Padding is added to prevent some content from being hidden.
628
+ const page = baseEl.querySelector('.ion-page');
629
+ page.style.setProperty('padding-bottom', `${footerHeight}px`);
630
+ }
631
+ });
592
632
  if (contentAnimation) {
593
633
  baseAnimation.addAnimation(contentAnimation);
594
634
  }
@@ -669,7 +709,7 @@ const createLeaveAnimation$1 = () => {
669
709
  * iOS Modal Leave Animation
670
710
  */
671
711
  const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
672
- const { presentingEl, currentBreakpoint } = opts;
712
+ const { presentingEl, currentBreakpoint, expandToScroll } = opts;
673
713
  const root = helpers.getElementRoot(baseEl);
674
714
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation$1();
675
715
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -678,7 +718,29 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
678
718
  .addElement(baseEl)
679
719
  .easing('cubic-bezier(0.32,0.72,0,1)')
680
720
  .duration(duration)
681
- .addAnimation(wrapperAnimation);
721
+ .addAnimation(wrapperAnimation)
722
+ .beforeAddWrite(() => {
723
+ if (expandToScroll) {
724
+ // Scroll can only be done when the modal is fully expanded.
725
+ return;
726
+ }
727
+ /**
728
+ * If expandToScroll is disabled, we need to swap
729
+ * the visibility to the original, so the footer
730
+ * dismisses with the modal and doesn't stay
731
+ * until the modal is removed from the DOM.
732
+ */
733
+ const ionFooter = baseEl.querySelector('ion-footer');
734
+ if (ionFooter) {
735
+ const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
736
+ ionFooter.style.removeProperty('display');
737
+ ionFooter.removeAttribute('aria-hidden');
738
+ clonedFooter.style.setProperty('display', 'none');
739
+ clonedFooter.setAttribute('aria-hidden', 'true');
740
+ const page = baseEl.querySelector('.ion-page');
741
+ page.style.removeProperty('padding-bottom');
742
+ }
743
+ });
682
744
  if (presentingEl) {
683
745
  const isMobile = window.innerWidth < 768;
684
746
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
@@ -774,7 +836,47 @@ const mdEnterAnimation = (baseEl, opts) => {
774
836
  .addElement(baseEl)
775
837
  .easing('cubic-bezier(0.36,0.66,0.04,1)')
776
838
  .duration(280)
777
- .addAnimation([backdropAnimation, wrapperAnimation]);
839
+ .addAnimation([backdropAnimation, wrapperAnimation])
840
+ .beforeAddWrite(() => {
841
+ if (expandToScroll) {
842
+ // Scroll can only be done when the modal is fully expanded.
843
+ return;
844
+ }
845
+ /**
846
+ * There are some browsers that causes flickering when
847
+ * dragging the content when scroll is enabled at every
848
+ * breakpoint. This is due to the wrapper element being
849
+ * transformed off the screen and having a snap animation.
850
+ *
851
+ * A workaround is to clone the footer element and append
852
+ * it outside of the wrapper element. This way, the footer
853
+ * is still visible and the drag can be done without
854
+ * flickering. The original footer is hidden until the modal
855
+ * is dismissed. This maintains the animation of the footer
856
+ * when the modal is dismissed.
857
+ *
858
+ * The workaround needs to be done before the animation starts
859
+ * so there are no flickering issues.
860
+ */
861
+ const ionFooter = baseEl.querySelector('ion-footer');
862
+ /**
863
+ * This check is needed to prevent more than one footer
864
+ * from being appended to the shadow root.
865
+ * Otherwise, iOS and MD enter animations would append
866
+ * the footer twice.
867
+ */
868
+ const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
869
+ if (ionFooter && !ionFooterAlreadyAppended) {
870
+ const footerHeight = ionFooter.clientHeight;
871
+ const clonedFooter = ionFooter.cloneNode(true);
872
+ baseEl.shadowRoot.appendChild(clonedFooter);
873
+ ionFooter.style.setProperty('display', 'none');
874
+ ionFooter.setAttribute('aria-hidden', 'true');
875
+ // Padding is added to prevent some content from being hidden.
876
+ const page = baseEl.querySelector('.ion-page');
877
+ page.style.setProperty('padding-bottom', `${footerHeight}px`);
878
+ }
879
+ });
778
880
  if (contentAnimation) {
779
881
  baseAnimation.addAnimation(contentAnimation);
780
882
  }
@@ -793,7 +895,7 @@ const createLeaveAnimation = () => {
793
895
  * Md Modal Leave Animation
794
896
  */
795
897
  const mdLeaveAnimation = (baseEl, opts) => {
796
- const { currentBreakpoint } = opts;
898
+ const { currentBreakpoint, expandToScroll } = opts;
797
899
  const root = helpers.getElementRoot(baseEl);
798
900
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
799
901
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -801,7 +903,29 @@ const mdLeaveAnimation = (baseEl, opts) => {
801
903
  const baseAnimation = animation.createAnimation()
802
904
  .easing('cubic-bezier(0.47,0,0.745,0.715)')
803
905
  .duration(200)
804
- .addAnimation([backdropAnimation, wrapperAnimation]);
906
+ .addAnimation([backdropAnimation, wrapperAnimation])
907
+ .beforeAddWrite(() => {
908
+ if (expandToScroll) {
909
+ // Scroll can only be done when the modal is fully expanded.
910
+ return;
911
+ }
912
+ /**
913
+ * If expandToScroll is disabled, we need to swap
914
+ * the visibility to the original, so the footer
915
+ * dismisses with the modal and doesn't stay
916
+ * until the modal is removed from the DOM.
917
+ */
918
+ const ionFooter = baseEl.querySelector('ion-footer');
919
+ if (ionFooter) {
920
+ const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
921
+ ionFooter.style.removeProperty('display');
922
+ ionFooter.removeAttribute('aria-hidden');
923
+ clonedFooter.style.setProperty('display', 'none');
924
+ clonedFooter.setAttribute('aria-hidden', 'true');
925
+ const page = baseEl.querySelector('.ion-page');
926
+ page.style.removeProperty('padding-bottom');
927
+ }
928
+ });
805
929
  return baseAnimation;
806
930
  };
807
931
 
@@ -833,9 +957,6 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
833
957
  let offset = 0;
834
958
  let canDismissBlocksGesture = false;
835
959
  let cachedScrollEl = null;
836
- let cachedFooterEl = null;
837
- let cachedFooterYPosition = null;
838
- let currentFooterState = null;
839
960
  const canDismissMaxStep = 0.95;
840
961
  const maxBreakpoint = breakpoints[breakpoints.length - 1];
841
962
  const minBreakpoint = breakpoints[0];
@@ -865,45 +986,29 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
865
986
  baseEl.classList.add(overlays.FOCUS_TRAP_DISABLE_CLASS);
866
987
  };
867
988
  /**
868
- * Toggles the footer to an absolute position while moving to prevent
869
- * it from shaking while the sheet is being dragged.
870
- * @param footer Whether the footer is in a moving or stationary position.
989
+ * Toggles the visible modal footer when `expandToScroll` is disabled.
990
+ * @param footer The footer to show.
871
991
  */
872
- const swapFooterPosition = (newPosition) => {
873
- if (!cachedFooterEl) {
874
- cachedFooterEl = baseEl.querySelector('ion-footer');
875
- if (!cachedFooterEl) {
876
- return;
877
- }
992
+ const swapFooterVisibility = (footer) => {
993
+ const originalFooter = baseEl.querySelector('ion-footer');
994
+ if (!originalFooter) {
995
+ return;
878
996
  }
997
+ const clonedFooter = wrapperEl.nextElementSibling;
998
+ const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
999
+ const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
1000
+ footerToShow.style.removeProperty('display');
1001
+ footerToShow.removeAttribute('aria-hidden');
879
1002
  const page = baseEl.querySelector('.ion-page');
880
- currentFooterState = newPosition;
881
- if (newPosition === 'stationary') {
882
- // Reset positioning styles to allow normal document flow
883
- cachedFooterEl.classList.remove('modal-footer-moving');
884
- cachedFooterEl.style.removeProperty('position');
885
- cachedFooterEl.style.removeProperty('bottom');
886
- page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
887
- // Move to page
888
- page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
1003
+ if (footer === 'original') {
1004
+ page.style.removeProperty('padding-bottom');
889
1005
  }
890
1006
  else {
891
- // Add padding to the parent element to prevent content from being hidden
892
- // when the footer is positioned absolutely. This has to be done before we
893
- // make the footer absolutely positioned or we may accidentally cause the
894
- // sheet to scroll.
895
- const footerHeight = cachedFooterEl.clientHeight;
896
- page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeight}px`);
897
- // Apply positioning styles to keep footer at bottom
898
- cachedFooterEl.classList.add('modal-footer-moving');
899
- cachedFooterEl.style.setProperty('position', 'absolute');
900
- cachedFooterEl.style.setProperty('bottom', '0');
901
- // Also cache the footer Y position, which we use to determine if the
902
- // sheet has been moved below the footer. When that happens, we need to swap
903
- // the position back so it will collapse correctly.
904
- cachedFooterYPosition = cachedFooterEl.getBoundingClientRect().top + window.scrollY;
905
- document.body.appendChild(cachedFooterEl);
1007
+ const pagePadding = footerToShow.clientHeight;
1008
+ page.style.setProperty('padding-bottom', `${pagePadding}px`);
906
1009
  }
1010
+ footerToHide.style.setProperty('display', 'none');
1011
+ footerToHide.setAttribute('aria-hidden', 'true');
907
1012
  };
908
1013
  /**
909
1014
  * After the entering animation completes,
@@ -997,11 +1102,12 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
997
1102
  }
998
1103
  /**
999
1104
  * If expandToScroll is disabled, we need to swap
1000
- * the footer position to moving so that it doesn't shake
1001
- * while the sheet is being dragged.
1105
+ * the footer visibility to the original, so if the modal
1106
+ * is dismissed, the footer dismisses with the modal
1107
+ * and doesn't stay on the screen after the modal is gone.
1002
1108
  */
1003
1109
  if (!expandToScroll) {
1004
- swapFooterPosition('moving');
1110
+ swapFooterVisibility('original');
1005
1111
  }
1006
1112
  /**
1007
1113
  * If we are pulling down, then it is possible we are pulling on the content.
@@ -1020,21 +1126,6 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1020
1126
  animation.progressStart(true, 1 - currentBreakpoint);
1021
1127
  };
1022
1128
  const onMove = (detail) => {
1023
- /**
1024
- * If `expandToScroll` is disabled, we need to see if we're currently below
1025
- * the footer element and the footer is in a stationary position. If so,
1026
- * we need to make the stationary the original position so that the footer
1027
- * collapses with the sheet.
1028
- */
1029
- if (!expandToScroll && cachedFooterYPosition !== null && currentFooterState !== null) {
1030
- // Check if we need to swap the footer position
1031
- if (detail.currentY >= cachedFooterYPosition && currentFooterState === 'moving') {
1032
- swapFooterPosition('stationary');
1033
- }
1034
- else if (detail.currentY < cachedFooterYPosition && currentFooterState === 'stationary') {
1035
- swapFooterPosition('moving');
1036
- }
1037
- }
1038
1129
  /**
1039
1130
  * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
1040
1131
  * the scrollable content, we should not allow the swipe gesture to continue.
@@ -1168,6 +1259,14 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1168
1259
  * snapping animation completes.
1169
1260
  */
1170
1261
  gesture.enable(false);
1262
+ /**
1263
+ * If expandToScroll is disabled, we need to swap
1264
+ * the footer visibility to the cloned one so the footer
1265
+ * doesn't flicker when the sheet's height is animated.
1266
+ */
1267
+ if (!expandToScroll && shouldRemainOpen) {
1268
+ swapFooterVisibility('cloned');
1269
+ }
1171
1270
  if (shouldPreventDismiss) {
1172
1271
  handleCanDismiss(baseEl, animation);
1173
1272
  }
@@ -1188,14 +1287,6 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1188
1287
  animation
1189
1288
  .onFinish(() => {
1190
1289
  if (shouldRemainOpen) {
1191
- /**
1192
- * If expandToScroll is disabled, we need to swap
1193
- * the footer position to stationary so that it
1194
- * will act as it would by default
1195
- */
1196
- if (!expandToScroll) {
1197
- swapFooterPosition('stationary');
1198
- }
1199
1290
  /**
1200
1291
  * Once the snapping animation completes,
1201
1292
  * we need to reset the animation to go
@@ -394,8 +394,8 @@ const Searchbar = class {
394
394
  const clearIcon = this.clearIcon || (mode === 'ios' ? index$2.closeCircle : index$2.closeSharp);
395
395
  const searchIcon = this.searchIcon || (mode === 'ios' ? index$2.searchOutline : index$2.searchSharp);
396
396
  const shouldShowCancelButton = this.shouldShowCancelButton();
397
- const cancelButton = this.showCancelButton !== 'never' && (index.h("button", { key: '989f3e84c472ada6e66dd9b249d0d268bf17ce26', "aria-label": cancelButtonText, "aria-hidden": shouldShowCancelButton ? undefined : 'true', type: "button", tabIndex: mode === 'ios' && !shouldShowCancelButton ? -1 : undefined, onMouseDown: this.onCancelSearchbar, onTouchStart: this.onCancelSearchbar, class: "searchbar-cancel-button" }, index.h("div", { key: '7d335d4fde33822dc79d26b748ba2e98db7494bb', "aria-hidden": "true" }, mode === 'md' ? (index.h("ion-icon", { "aria-hidden": "true", mode: mode, icon: this.cancelButtonIcon, lazy: false })) : (cancelButtonText))));
398
- return (index.h(index.Host, { key: 'd1a1972725e949fb102c91487aaa7b9d10c2d718', role: "search", "aria-disabled": this.disabled ? 'true' : null, class: theme.createColorClasses(this.color, {
397
+ const cancelButton = this.showCancelButton !== 'never' && (index.h("button", { key: '4a4bddfaf44acb9ea71d37d11c3fc11450134a5c', "aria-label": cancelButtonText, "aria-hidden": shouldShowCancelButton ? undefined : 'true', type: "button", tabIndex: mode === 'ios' && !shouldShowCancelButton ? -1 : undefined, onMouseDown: this.onCancelSearchbar, onTouchStart: this.onCancelSearchbar, class: "searchbar-cancel-button" }, index.h("div", { key: '24424547e34a80422f617f5597965724a3ef65f0', "aria-hidden": "true" }, mode === 'md' ? (index.h("ion-icon", { "aria-hidden": "true", mode: mode, icon: this.cancelButtonIcon, lazy: false })) : (cancelButtonText))));
398
+ return (index.h(index.Host, { key: '0c0e7334cb2af87081a278e5a3f6be3dc646a073', role: "search", "aria-disabled": this.disabled ? 'true' : null, class: theme.createColorClasses(this.color, {
399
399
  [mode]: true,
400
400
  'searchbar-animated': animated,
401
401
  'searchbar-disabled': this.disabled,
@@ -405,14 +405,14 @@ const Searchbar = class {
405
405
  'searchbar-has-focus': this.focused,
406
406
  'searchbar-should-show-clear': this.shouldShowClearButton(),
407
407
  'searchbar-should-show-cancel': this.shouldShowCancelButton(),
408
- }) }, index.h("div", { key: 'add53640b2994cb6b2bf02792dafe51aba6b1684', class: "searchbar-input-container" }, index.h("input", Object.assign({ key: '160cc36459a4a652e7f41ccd14dcdc782278779e', "aria-label": "search text", disabled: this.disabled, ref: (el) => (this.nativeInput = el), class: "searchbar-input", inputMode: this.inputmode, enterKeyHint: this.enterkeyhint, name: this.name, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, minLength: this.minlength, maxLength: this.maxlength, placeholder: this.placeholder, type: this.type, value: this.getValue(), autoCapitalize: autocapitalize === 'default' ? undefined : autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, spellcheck: this.spellcheck }, this.inheritedAttributes)), mode === 'md' && cancelButton, index.h("ion-icon", { key: '8825fd13af0d2dea451ccc0e00ae7b5021dc01c4', "aria-hidden": "true", mode: mode, icon: searchIcon, lazy: false, class: "searchbar-search-icon" }), index.h("button", { key: '8a7b56da278b9ca5c4f5a4ee9c01924fd5ae29d8', "aria-label": "reset", type: "button", "no-blur": true, class: "searchbar-clear-button", onPointerDown: (ev) => {
408
+ }) }, index.h("div", { key: '859da78b062b9059701eba166be3bc5417f7395b', class: "searchbar-input-container" }, index.h("input", Object.assign({ key: '840fd06a1e4b5a8d7f6d7439d02aef9fcb9720b6', "aria-label": "search text", disabled: this.disabled, ref: (el) => (this.nativeInput = el), class: "searchbar-input", inputMode: this.inputmode, enterKeyHint: this.enterkeyhint, name: this.name, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, minLength: this.minlength, maxLength: this.maxlength, placeholder: this.placeholder, type: this.type, value: this.getValue(), autoCapitalize: autocapitalize === 'default' ? undefined : autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, spellcheck: this.spellcheck }, this.inheritedAttributes)), mode === 'md' && cancelButton, index.h("ion-icon", { key: 'a5aaa6a2204d38433cac283dd47b6197499fa585', "aria-hidden": "true", mode: mode, icon: searchIcon, lazy: false, class: "searchbar-search-icon" }), index.h("button", { key: 'cf62c8969c5c916ac838e122a0ad45b2ae234791', "aria-label": "reset", type: "button", "no-blur": true, class: "searchbar-clear-button", onPointerDown: (ev) => {
409
409
  /**
410
410
  * This prevents mobile browsers from
411
411
  * blurring the input when the clear
412
412
  * button is activated.
413
413
  */
414
414
  ev.preventDefault();
415
- }, onClick: () => this.onClearInput(true) }, index.h("ion-icon", { key: '24c55274516ab012d8c25f03525c6cdb9409e52f', "aria-hidden": "true", mode: mode, icon: clearIcon, lazy: false, class: "searchbar-clear-icon" }))), mode === 'ios' && cancelButton));
415
+ }, onClick: () => this.onClearInput(true) }, index.h("ion-icon", { key: 'b1a75f0964fc2119479e246dc27bef7cc33bd19f', "aria-hidden": "true", mode: mode, icon: clearIcon, lazy: false, class: "searchbar-clear-icon" }))), mode === 'ios' && cancelButton));
416
416
  }
417
417
  get el() { return index.getElement(this); }
418
418
  static get watchers() { return {