@ionic/core 8.8.16-dev.11784841344.1aa1a41c → 8.8.16-dev.11784914346.10d0804c

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 (205) hide show
  1. package/components/ion-accordion.js +1 -1
  2. package/components/ion-action-sheet.js +1 -1
  3. package/components/ion-alert.js +1 -1
  4. package/components/ion-avatar.js +1 -1
  5. package/components/ion-back-button.js +1 -1
  6. package/components/ion-badge.js +1 -1
  7. package/components/ion-breadcrumb.js +1 -1
  8. package/components/ion-button.js +1 -1
  9. package/components/ion-buttons.js +1 -1
  10. package/components/ion-content.js +1 -1
  11. package/components/ion-datetime.js +1 -1
  12. package/components/ion-fab-button.js +1 -1
  13. package/components/ion-footer.js +1 -1
  14. package/components/ion-header.js +1 -1
  15. package/components/ion-icon.js +1 -1
  16. package/components/ion-input-password-toggle.js +1 -1
  17. package/components/ion-input.js +1 -1
  18. package/components/ion-item-option.js +1 -1
  19. package/components/ion-item.js +1 -1
  20. package/components/ion-menu-button.js +1 -1
  21. package/components/ion-refresher-content.js +1 -1
  22. package/components/ion-reorder.js +1 -1
  23. package/components/ion-searchbar.js +1 -1
  24. package/components/ion-segment-button.js +1 -1
  25. package/components/ion-select-modal.js +1 -1
  26. package/components/ion-select-popover.js +1 -1
  27. package/components/ion-select.js +1 -1
  28. package/components/ion-tab-bar.js +1 -1
  29. package/components/ion-tab-button.js +1 -1
  30. package/components/ion-title.js +1 -1
  31. package/components/ion-toast.js +1 -1
  32. package/components/ion-toggle.js +1 -1
  33. package/components/{p-DiKh2eQw.js → p-4u9RNymb.js} +1 -1
  34. package/components/{p-B8KYxaMx.js → p-B8xhFBOE.js} +1 -1
  35. package/components/p-BAnNwWMA.js +4 -0
  36. package/components/p-BYZR_5L4.js +4 -0
  37. package/components/p-B_zaqq5F.js +4 -0
  38. package/components/p-CO9VfZyc.js +4 -0
  39. package/components/p-Cjx0xPoU.js +4 -0
  40. package/components/p-CyWeCU8e.js +4 -0
  41. package/components/p-DRnK3A-V.js +4 -0
  42. package/components/p-DcVqGRkT.js +4 -0
  43. package/components/p-Dght1fh7.js +4 -0
  44. package/components/p-GpxJMpJu.js +4 -0
  45. package/dist/cjs/{index-Cu9JNpPi.js → index-CgAbCW6L.js} +1 -1
  46. package/dist/cjs/ion-accordion_2.cjs.entry.js +1 -1
  47. package/dist/cjs/ion-action-sheet.cjs.entry.js +3 -3
  48. package/dist/cjs/ion-alert.cjs.entry.js +3 -3
  49. package/dist/cjs/ion-app_8.cjs.entry.js +194 -65
  50. package/dist/cjs/ion-avatar_3.cjs.entry.js +4 -4
  51. package/dist/cjs/ion-back-button.cjs.entry.js +1 -1
  52. package/dist/cjs/ion-breadcrumb_2.cjs.entry.js +1 -1
  53. package/dist/cjs/ion-button_2.cjs.entry.js +3 -3
  54. package/dist/cjs/ion-datetime_3.cjs.entry.js +1 -1
  55. package/dist/cjs/ion-fab_3.cjs.entry.js +1 -1
  56. package/dist/cjs/ion-input-password-toggle.cjs.entry.js +1 -1
  57. package/dist/cjs/ion-input.cjs.entry.js +4 -4
  58. package/dist/cjs/ion-item-option_3.cjs.entry.js +1 -1
  59. package/dist/cjs/ion-item_8.cjs.entry.js +1 -1
  60. package/dist/cjs/ion-menu_3.cjs.entry.js +1 -1
  61. package/dist/cjs/ion-refresher_2.cjs.entry.js +1 -1
  62. package/dist/cjs/ion-reorder_2.cjs.entry.js +1 -1
  63. package/dist/cjs/ion-searchbar.cjs.entry.js +2 -2
  64. package/dist/cjs/ion-segment_2.cjs.entry.js +1 -1
  65. package/dist/cjs/ion-select-modal.cjs.entry.js +4 -4
  66. package/dist/cjs/ion-select_3.cjs.entry.js +5 -5
  67. package/dist/cjs/ion-tab-bar_2.cjs.entry.js +78 -80
  68. package/dist/cjs/ion-toggle.cjs.entry.js +1 -1
  69. package/dist/cjs/ionic.cjs.js +1 -1
  70. package/dist/cjs/loader.cjs.js +1 -1
  71. package/dist/cjs/scroll-hide-controller-G9HOmmhN.js +345 -0
  72. package/dist/collection/components/action-sheet/action-sheet.ionic.css +2 -5
  73. package/dist/collection/components/action-sheet/action-sheet.ios.css +2 -1
  74. package/dist/collection/components/action-sheet/action-sheet.md.css +2 -1
  75. package/dist/collection/components/alert/alert.ionic.css +2 -5
  76. package/dist/collection/components/alert/alert.ios.css +2 -1
  77. package/dist/collection/components/alert/alert.md.css +2 -1
  78. package/dist/collection/components/avatar/avatar.ionic.css +12 -6
  79. package/dist/collection/components/badge/badge.ionic.css +6 -3
  80. package/dist/collection/components/badge/badge.ios.css +4 -2
  81. package/dist/collection/components/badge/badge.md.css +2 -1
  82. package/dist/collection/components/button/button.ionic.css +4 -2
  83. package/dist/collection/components/buttons/buttons.ionic.css +1 -1
  84. package/dist/collection/components/content/content.css +39 -0
  85. package/dist/collection/components/footer/footer-interface.js +1 -0
  86. package/dist/collection/components/footer/footer.ios.css +19 -4
  87. package/dist/collection/components/footer/footer.js +102 -15
  88. package/dist/collection/components/footer/footer.md.css +28 -0
  89. package/dist/collection/components/header/header-interface.js +1 -0
  90. package/dist/collection/components/header/header.ionic.css +153 -0
  91. package/dist/collection/components/header/header.ios.css +153 -123
  92. package/dist/collection/components/header/header.js +139 -28
  93. package/dist/collection/components/header/header.md.css +176 -1
  94. package/dist/collection/components/header/header.utils.js +6 -13
  95. package/dist/collection/components/input/input.ionic.css +2 -1
  96. package/dist/collection/components/input/input.ios.css +2 -1
  97. package/dist/collection/components/input/input.md.css +2 -1
  98. package/dist/collection/components/item-option/item-option.ionic.css +2 -1
  99. package/dist/collection/components/searchbar/searchbar.ionic.css +6 -2
  100. package/dist/collection/components/segment-button/segment-button.ionic.css +2 -1
  101. package/dist/collection/components/select/select.ionic.css +2 -1
  102. package/dist/collection/components/select-modal/select-modal.ionic.css +2 -5
  103. package/dist/collection/components/select-modal/select-modal.ios.css +2 -1
  104. package/dist/collection/components/select-modal/select-modal.md.css +2 -1
  105. package/dist/collection/components/select-popover/select-popover.ionic.css +2 -5
  106. package/dist/collection/components/select-popover/select-popover.ios.css +2 -1
  107. package/dist/collection/components/select-popover/select-popover.md.css +2 -1
  108. package/dist/collection/components/tab-bar/tab-bar.ionic.css +25 -3
  109. package/dist/collection/components/tab-bar/tab-bar.ios.css +15 -0
  110. package/dist/collection/components/tab-bar/tab-bar.js +94 -88
  111. package/dist/collection/components/tab-bar/tab-bar.md.css +15 -0
  112. package/dist/collection/components/tab-button/tab-button.ionic.css +2 -1
  113. package/dist/collection/components/title/title.md.css +7 -0
  114. package/dist/collection/utils/scroll-hide-controller.js +178 -0
  115. package/dist/docs.json +144 -32
  116. package/dist/esm/{index-1y3VhUSy.js → index-D2tu5BUg.js} +1 -1
  117. package/dist/esm/ion-accordion_2.entry.js +1 -1
  118. package/dist/esm/ion-action-sheet.entry.js +3 -3
  119. package/dist/esm/ion-alert.entry.js +3 -3
  120. package/dist/esm/ion-app_8.entry.js +194 -65
  121. package/dist/esm/ion-avatar_3.entry.js +4 -4
  122. package/dist/esm/ion-back-button.entry.js +1 -1
  123. package/dist/esm/ion-breadcrumb_2.entry.js +1 -1
  124. package/dist/esm/ion-button_2.entry.js +3 -3
  125. package/dist/esm/ion-datetime_3.entry.js +1 -1
  126. package/dist/esm/ion-fab_3.entry.js +1 -1
  127. package/dist/esm/ion-input-password-toggle.entry.js +1 -1
  128. package/dist/esm/ion-input.entry.js +4 -4
  129. package/dist/esm/ion-item-option_3.entry.js +1 -1
  130. package/dist/esm/ion-item_8.entry.js +1 -1
  131. package/dist/esm/ion-menu_3.entry.js +1 -1
  132. package/dist/esm/ion-refresher_2.entry.js +1 -1
  133. package/dist/esm/ion-reorder_2.entry.js +1 -1
  134. package/dist/esm/ion-searchbar.entry.js +2 -2
  135. package/dist/esm/ion-segment_2.entry.js +1 -1
  136. package/dist/esm/ion-select-modal.entry.js +4 -4
  137. package/dist/esm/ion-select_3.entry.js +5 -5
  138. package/dist/esm/ion-tab-bar_2.entry.js +79 -81
  139. package/dist/esm/ion-toggle.entry.js +1 -1
  140. package/dist/esm/ionic.js +1 -1
  141. package/dist/esm/loader.js +1 -1
  142. package/dist/esm/scroll-hide-controller-CUNW7EpD.js +342 -0
  143. package/dist/html.html-data.json +36 -4
  144. package/dist/ionic/ionic.esm.js +1 -1
  145. package/dist/ionic/p-07bd8446.entry.js +4 -0
  146. package/dist/ionic/p-103e1d9f.entry.js +4 -0
  147. package/dist/ionic/{p-eeb7b373.entry.js → p-18bc4f2d.entry.js} +1 -1
  148. package/dist/ionic/p-248265c2.entry.js +4 -0
  149. package/dist/ionic/{p-fb457fdd.entry.js → p-3bc5b5e9.entry.js} +1 -1
  150. package/dist/ionic/{p-42a882b7.entry.js → p-43b00c5b.entry.js} +1 -1
  151. package/dist/ionic/{p-d0f253fc.entry.js → p-56224b93.entry.js} +1 -1
  152. package/dist/ionic/p-5732396e.entry.js +4 -0
  153. package/dist/ionic/{p-b3d04a86.entry.js → p-58d8877d.entry.js} +1 -1
  154. package/dist/ionic/{p-9e9be378.entry.js → p-5d079dc1.entry.js} +1 -1
  155. package/dist/ionic/{p-d0dfc9a3.entry.js → p-5e9665dd.entry.js} +1 -1
  156. package/dist/ionic/{p-7b2fe639.entry.js → p-5fee5cc1.entry.js} +1 -1
  157. package/dist/ionic/p-64734604.entry.js +4 -0
  158. package/dist/ionic/p-69feeff9.entry.js +4 -0
  159. package/dist/ionic/p-83acfa2d.entry.js +4 -0
  160. package/dist/ionic/{p-f692219d.entry.js → p-849b1759.entry.js} +1 -1
  161. package/dist/ionic/{p-a09c7a4c.entry.js → p-86f040e0.entry.js} +1 -1
  162. package/dist/ionic/p-8cb23f27.entry.js +4 -0
  163. package/dist/ionic/p-C1I_d964.js +4 -0
  164. package/dist/ionic/p-b97778f5.entry.js +4 -0
  165. package/dist/ionic/p-be9db7e8.entry.js +4 -0
  166. package/dist/ionic/{p-2fba8f15.entry.js → p-c04429f2.entry.js} +1 -1
  167. package/dist/ionic/p-c25a2a9b.entry.js +4 -0
  168. package/dist/ionic/{p-a797625e.entry.js → p-d272ff4f.entry.js} +1 -1
  169. package/dist/types/components/footer/footer-interface.d.ts +1 -0
  170. package/dist/types/components/footer/footer.d.ts +16 -0
  171. package/dist/types/components/header/header-interface.d.ts +1 -0
  172. package/dist/types/components/header/header.d.ts +17 -0
  173. package/dist/types/components/header/header.utils.d.ts +4 -5
  174. package/dist/types/components/tab-bar/tab-bar-interface.d.ts +1 -0
  175. package/dist/types/components/tab-bar/tab-bar.d.ts +17 -14
  176. package/dist/types/components.d.ts +37 -13
  177. package/dist/types/utils/scroll-hide-controller.d.ts +56 -0
  178. package/hydrate/index.js +477 -175
  179. package/hydrate/index.mjs +477 -175
  180. package/package.json +2 -2
  181. package/components/p-BZwQa9ey.js +0 -4
  182. package/components/p-CJCMdFb9.js +0 -4
  183. package/components/p-Ck_9SoHZ.js +0 -4
  184. package/components/p-CmnPD02Z.js +0 -4
  185. package/components/p-D-Xa9lMG.js +0 -4
  186. package/components/p-DFSqPtzD.js +0 -4
  187. package/components/p-NPsFR8Ng.js +0 -4
  188. package/components/p-U2gyA6Z0.js +0 -4
  189. package/components/p-q8wGOPJB.js +0 -4
  190. package/dist/cjs/keyboard-controller-CefyW9lm.js +0 -169
  191. package/dist/esm/keyboard-controller-BFwRdk2T.js +0 -167
  192. package/dist/ionic/p-0c4d9d7c.entry.js +0 -4
  193. package/dist/ionic/p-40dc9dbd.entry.js +0 -4
  194. package/dist/ionic/p-54419365.entry.js +0 -4
  195. package/dist/ionic/p-6293e9f1.entry.js +0 -4
  196. package/dist/ionic/p-7de397c7.entry.js +0 -4
  197. package/dist/ionic/p-8cf82b85.entry.js +0 -4
  198. package/dist/ionic/p-MqqcZQSB.js +0 -4
  199. package/dist/ionic/p-a6e04ca3.entry.js +0 -4
  200. package/dist/ionic/p-a6fe63f0.entry.js +0 -4
  201. package/dist/ionic/p-b275f17e.entry.js +0 -4
  202. package/dist/ionic/p-c5635175.entry.js +0 -4
  203. package/dist/ionic/p-dc8e0b91.entry.js +0 -4
  204. /package/components/{p-J03hHbbL.js → p-BaPtaYEC.js} +0 -0
  205. /package/dist/ionic/{p-1y3VhUSy.js → p-D2tu5BUg.js} +0 -0
@@ -1,10 +1,13 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { Host, h, readTask, writeTask } from "@stencil/core";
5
- import { findIonContent, getScrollElement } from "../../utils/content/index";
4
+ import { Host, h } from "@stencil/core";
5
+ import { findIonContent } from "../../utils/content/index";
6
6
  import { createKeyboardController } from "../../utils/keyboard/keyboard-controller";
7
+ import { printIonWarning } from "../../utils/logging/index";
8
+ import { createScrollHideController } from "../../utils/scroll-hide-controller";
7
9
  import { createColorClasses } from "../../utils/theme";
10
+ import { config } from "../../global/config";
8
11
  import { getIonTheme } from "../../global/ionic-global";
9
12
  /**
10
13
  * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component.
@@ -15,16 +18,9 @@ export class TabBar {
15
18
  this.keyboardCtrl = null;
16
19
  this.keyboardCtrlPromise = null;
17
20
  this.didLoad = false;
18
- this.lastScrollTop = 0;
19
- this.scrollDirectionChangeTop = 0;
21
+ this.scrollHideCtrlPromise = null;
22
+ this.hasWarnedFooter = false;
20
23
  this.keyboardVisible = false;
21
- this.scrollHidden = false;
22
- /**
23
- * If `true`, the tab bar will be hidden when the user scrolls down
24
- * and shown when the user scrolls up.
25
- * Only applies when the theme is `"ionic"` and `expand` is `"compact"`.
26
- */
27
- this.hideOnScroll = false;
28
24
  /**
29
25
  * If `true`, the tab bar will be translucent.
30
26
  * Only applies when the theme is `"ios"` and the device supports
@@ -41,6 +37,40 @@ export class TabBar {
41
37
  * Defaults to `"full"`.
42
38
  */
43
39
  this.expand = 'full';
40
+ this.setupScrollEffect = async () => {
41
+ // When nested inside ion-footer, the tab-bar should not hide
42
+ // independently — doing so would leave an empty footer behind.
43
+ // The footer should own the scroll effect instead.
44
+ if (this.el.closest('ion-footer')) {
45
+ if (!this.hasWarnedFooter) {
46
+ this.hasWarnedFooter = true;
47
+ printIonWarning(`[ion-tab-bar] - scroll-effect="hide" is ignored when nested inside <ion-footer>. ` +
48
+ `Set scroll-effect="hide" on the <ion-footer> instead.`, this.el);
49
+ }
50
+ return;
51
+ }
52
+ const appRootSelector = config.get('appRootSelector', 'ion-app');
53
+ const pageEl = this.el.closest(`${appRootSelector}, ion-page, .ion-page, page-inner`);
54
+ const contentEl = pageEl ? findIonContent(pageEl) : null;
55
+ if (!contentEl) {
56
+ return;
57
+ }
58
+ const setupPromise = createScrollHideController(contentEl, {
59
+ el: this.el,
60
+ cssVar: '--internal-tab-bar-hide-height',
61
+ hiddenClass: 'tab-bar-scroll-hidden',
62
+ contentPartnerClass: 'content-tab-bar-hide-scroll-partner',
63
+ contentHiddenClass: 'content-tab-bar-hide-scroll-hidden',
64
+ shouldKeepAriaHidden: () => this.keyboardVisible && this.el.getAttribute('slot') !== 'top',
65
+ });
66
+ this.scrollHideCtrlPromise = setupPromise;
67
+ const controller = await setupPromise;
68
+ if (this.scrollHideCtrlPromise === setupPromise) {
69
+ this.scrollHideCtrlPromise = null;
70
+ controller.init();
71
+ this.scrollHideCtrl = controller;
72
+ }
73
+ };
44
74
  }
45
75
  selectedTabChanged() {
46
76
  // Skip the initial watcher call that happens during component load
@@ -54,22 +84,28 @@ export class TabBar {
54
84
  });
55
85
  }
56
86
  }
87
+ scrollEffectChanged() {
88
+ this.checkScrollEffect();
89
+ }
57
90
  componentDidLoad() {
58
91
  this.ionTabBarLoaded.emit();
59
- // Set the flag to indicate the component has loaded
60
- // This allows the watcher to emit changes from this point forward
61
92
  this.didLoad = true;
62
- // Emit the initial selected tab after the component is fully loaded
63
- // This ensures all child components (ion-tab-button) are ready
64
93
  if (this.selectedTab !== undefined) {
65
94
  this.ionTabBarChanged.emit({
66
95
  tab: this.selectedTab,
67
96
  });
68
97
  }
69
- this.setupHideOnScroll();
98
+ this.checkScrollEffect();
99
+ }
100
+ checkScrollEffect() {
101
+ this.destroyScrollEffect();
102
+ if (this.scrollEffect === 'hide') {
103
+ this.setupScrollEffect();
104
+ }
70
105
  }
71
106
  async connectedCallback() {
72
107
  const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
108
+ var _a, _b;
73
109
  /**
74
110
  * If the keyboard is hiding, then we need to wait
75
111
  * for the webview to resize. Otherwise, the tab bar
@@ -79,6 +115,18 @@ export class TabBar {
79
115
  await waitForResize;
80
116
  }
81
117
  this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
118
+ // Toggle aria-hidden imperatively for the keyboard case.
119
+ // scroll-effect hide also sets aria-hidden via setHidden,
120
+ // so keeping a single imperative path avoids render() clearing
121
+ // the scroll-set attribute on re-render.
122
+ const shouldHide = keyboardOpen && this.el.getAttribute('slot') !== 'top';
123
+ const isScrollHidden = (_b = (_a = this.scrollHideCtrl) === null || _a === void 0 ? void 0 : _a.isHidden()) !== null && _b !== void 0 ? _b : false;
124
+ if (shouldHide || isScrollHidden) {
125
+ this.el.setAttribute('aria-hidden', 'true');
126
+ }
127
+ else {
128
+ this.el.removeAttribute('aria-hidden');
129
+ }
82
130
  });
83
131
  this.keyboardCtrlPromise = promise;
84
132
  const keyboardCtrl = await promise;
@@ -96,6 +144,7 @@ export class TabBar {
96
144
  }
97
145
  }
98
146
  disconnectedCallback() {
147
+ this.destroyScrollEffect();
99
148
  if (this.keyboardCtrlPromise) {
100
149
  this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
101
150
  this.keyboardCtrlPromise = null;
@@ -104,63 +153,13 @@ export class TabBar {
104
153
  this.keyboardCtrl.destroy();
105
154
  this.keyboardCtrl = null;
106
155
  }
107
- this.destroyHideOnScroll();
108
156
  }
109
- setupHideOnScroll() {
110
- var _a;
111
- const theme = getIonTheme(this);
112
- if (theme !== 'ionic' || !this.hideOnScroll || this.expand !== 'compact') {
113
- return;
114
- }
115
- const footerEl = this.el.closest('ion-footer');
116
- const pageEl = (_a = footerEl === null || footerEl === void 0 ? void 0 : footerEl.closest('ion-page, .ion-page')) !== null && _a !== void 0 ? _a : this.el.closest('ion-page, .ion-page');
117
- const contentEl = pageEl ? findIonContent(pageEl) : null;
118
- if (!contentEl) {
119
- return;
157
+ destroyScrollEffect() {
158
+ this.scrollHideCtrlPromise = null;
159
+ if (this.scrollHideCtrl) {
160
+ this.scrollHideCtrl.destroy();
161
+ this.scrollHideCtrl = undefined;
120
162
  }
121
- this.initScrollListener(contentEl);
122
- }
123
- async initScrollListener(contentEl) {
124
- const scrollEl = (this.scrollEl = await getScrollElement(contentEl));
125
- this.contentScrollCallback = () => {
126
- readTask(() => {
127
- const scrollTop = scrollEl.scrollTop;
128
- const shouldHide = this.checkScrollStatus(scrollTop);
129
- if (shouldHide !== this.scrollHidden) {
130
- writeTask(() => {
131
- this.scrollHidden = shouldHide;
132
- });
133
- }
134
- this.lastScrollTop = scrollTop;
135
- });
136
- };
137
- scrollEl.addEventListener('scroll', this.contentScrollCallback, { passive: true });
138
- }
139
- destroyHideOnScroll() {
140
- if (this.scrollEl && this.contentScrollCallback) {
141
- this.scrollEl.removeEventListener('scroll', this.contentScrollCallback);
142
- this.contentScrollCallback = undefined;
143
- }
144
- }
145
- checkScrollStatus(scrollTop) {
146
- // Always visible within the first 80px of scroll
147
- const visibleZone = 80;
148
- // Hides after 60px of continuous downward scrolling only, when scrolling up threashold should be 0px
149
- const scrollThresholdHide = 60;
150
- if (scrollTop <= visibleZone) {
151
- return false;
152
- }
153
- const isScrollingDown = scrollTop > this.lastScrollTop;
154
- const wasScrollingDown = this.lastScrollTop > this.scrollDirectionChangeTop;
155
- if (isScrollingDown !== wasScrollingDown) {
156
- this.scrollDirectionChangeTop = this.lastScrollTop;
157
- }
158
- const delta = Math.abs(scrollTop - this.scrollDirectionChangeTop);
159
- const threshold = isScrollingDown ? scrollThresholdHide : 0;
160
- if (delta < threshold) {
161
- return this.scrollHidden;
162
- }
163
- return isScrollingDown;
164
163
  }
165
164
  getShape() {
166
165
  const theme = getIonTheme(this);
@@ -175,19 +174,18 @@ export class TabBar {
175
174
  return shape;
176
175
  }
177
176
  render() {
178
- const { color, translucent, keyboardVisible, scrollHidden, expand, hideOnScroll } = this;
177
+ const { color, translucent, keyboardVisible, scrollEffect, expand } = this;
179
178
  const theme = getIonTheme(this);
180
179
  const shape = this.getShape();
181
180
  const shouldHide = keyboardVisible && this.el.getAttribute('slot') !== 'top';
182
- return (h(Host, { key: '16cd11a815d777a7517150b49b57a204c83c3c37', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
181
+ return (h(Host, { key: '749dbd95dd87e73ceea2dab9767a285fa3ccf5e1', role: "tablist", class: createColorClasses(color, {
183
182
  [theme]: true,
184
183
  'tab-bar-translucent': translucent,
185
184
  'tab-bar-hidden': shouldHide,
186
- 'tab-bar-hide-on-scroll': hideOnScroll,
187
- 'tab-bar-scroll-hidden': scrollHidden,
185
+ 'tab-bar-scroll-effect-hide': scrollEffect === 'hide',
188
186
  [`tab-bar-${expand}`]: true,
189
187
  [`tab-bar-${shape}`]: shape !== undefined,
190
- }) }, h("slot", { key: 'dfdff308f24b3addaf18528a8d869b3fc64306d1' })));
188
+ }) }, h("slot", { key: '05a13a25a37c76245dad1710ef58ba5e69cf9f6c' })));
191
189
  }
192
190
  static get is() { return "ion-tab-bar"; }
193
191
  static get encapsulation() { return "shadow"; }
@@ -252,25 +250,31 @@ export class TabBar {
252
250
  "reflect": false,
253
251
  "attribute": "selected-tab"
254
252
  },
255
- "hideOnScroll": {
256
- "type": "boolean",
253
+ "scrollEffect": {
254
+ "type": "string",
257
255
  "mutable": false,
258
256
  "complexType": {
259
- "original": "boolean",
260
- "resolved": "boolean",
261
- "references": {}
257
+ "original": "TabBarScrollEffect",
258
+ "resolved": "\"hide\" | undefined",
259
+ "references": {
260
+ "TabBarScrollEffect": {
261
+ "location": "import",
262
+ "path": "./tab-bar-interface",
263
+ "id": "src/components/tab-bar/tab-bar-interface.ts::TabBarScrollEffect",
264
+ "referenceLocation": "TabBarScrollEffect"
265
+ }
266
+ }
262
267
  },
263
268
  "required": false,
264
- "optional": false,
269
+ "optional": true,
265
270
  "docs": {
266
271
  "tags": [],
267
- "text": "If `true`, the tab bar will be hidden when the user scrolls down\nand shown when the user scrolls up.\nOnly applies when the theme is `\"ionic\"` and `expand` is `\"compact\"`."
272
+ "text": "Describes the scroll effect that will be applied to the tab bar.\n`\"hide\"` slides the tab bar out of view when scrolling down and back in\nwhen scrolling up.\n\nNote: `\"hide\"` is ignored when the tab bar is nested inside an\n`ion-footer` to avoid leaving an empty footer visible on screen.\nSet `scroll-effect=\"hide\"` on the footer instead so the entire\nfooter hides together."
268
273
  },
269
274
  "getter": false,
270
275
  "setter": false,
271
- "reflect": false,
272
- "attribute": "hide-on-scroll",
273
- "defaultValue": "false"
276
+ "reflect": true,
277
+ "attribute": "scroll-effect"
274
278
  },
275
279
  "translucent": {
276
280
  "type": "boolean",
@@ -335,8 +339,7 @@ export class TabBar {
335
339
  }
336
340
  static get states() {
337
341
  return {
338
- "keyboardVisible": {},
339
- "scrollHidden": {}
342
+ "keyboardVisible": {}
340
343
  };
341
344
  }
342
345
  static get events() {
@@ -390,6 +393,9 @@ export class TabBar {
390
393
  return [{
391
394
  "propName": "selectedTab",
392
395
  "methodName": "selectedTabChanged"
396
+ }, {
397
+ "propName": "scrollEffect",
398
+ "methodName": "scrollEffectChanged"
393
399
  }];
394
400
  }
395
401
  }
@@ -51,6 +51,21 @@
51
51
  padding-bottom: 0;
52
52
  }
53
53
 
54
+ :host(.tab-bar-scroll-effect-hide) {
55
+ transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
56
+ }
57
+
58
+ :host(.tab-bar-scroll-effect-hide.tab-bar-scroll-hidden) {
59
+ transform: translateY(100%);
60
+ transition: transform 350ms cubic-bezier(0.4, 0, 1, 1);
61
+ }
62
+
63
+ @media (prefers-reduced-motion: reduce) {
64
+ :host(.tab-bar-scroll-effect-hide),
65
+ :host(.tab-bar-scroll-effect-hide.tab-bar-scroll-hidden) {
66
+ transition: none;
67
+ }
68
+ }
54
69
  /**
55
70
  * Convert a pixels given value into rem
56
71
  *
@@ -276,7 +276,8 @@ ion-ripple-effect {
276
276
  }
277
277
 
278
278
  ::slotted(ion-icon) {
279
- font-size: var(--token-font-size-600, 1.5rem);
279
+ width: var(--token-scale-600, 24px);
280
+ height: var(--token-scale-600, 24px);
280
281
  }
281
282
 
282
283
  :host(.tab-button-shape-soft) {
@@ -127,4 +127,11 @@
127
127
  height: 100%;
128
128
  font-size: 0.9375rem;
129
129
  font-weight: normal;
130
+ }
131
+
132
+ :host(.title-large) {
133
+ width: 100%;
134
+ height: 100%;
135
+ font-size: 1.75rem;
136
+ font-weight: 400;
130
137
  }
@@ -0,0 +1,178 @@
1
+ /*!
2
+ * (C) Ionic http://ionicframework.com - MIT License
3
+ */
4
+ import { readTask, writeTask } from "@stencil/core";
5
+ import { getScrollElement } from "./content";
6
+ const TOP_VISIBLE_THRESHOLD = 80;
7
+ const SCROLL_HIDE_THRESHOLD = 60;
8
+ const WHEEL_SUPPRESS_DURATION_MS = 80;
9
+ const SUPPRESS_SHOW_DURATION_MS = 400;
10
+ /**
11
+ * Creates a controller that manages scroll-based hide/show behavior
12
+ * for headers, footers, and tab bars.
13
+ *
14
+ * Listens to both wheel and scroll events. Wheel events (desktop mice)
15
+ * give direction instantly via deltaY. Scroll events cover touch,
16
+ * trackpad, and programmatic scrolling where wheel doesn't fire.
17
+ * A short suppression window stops them from double-processing the
18
+ * same gesture.
19
+ *
20
+ * When the user reverses scroll direction, we save that position as
21
+ * an anchor. The bar only hides after scrolling 60px past that
22
+ * anchor, which prevents flickering on small or jittery movements.
23
+ * Showing is immediate on direction change.
24
+ *
25
+ * When `options` is provided, the controller also handles the DOM
26
+ * setup: ResizeObserver for height tracking, CSS variable read/write,
27
+ * content partner/hidden class toggling, inert/aria-hidden, and
28
+ * full teardown cleanup. This avoids duplicating the same setup
29
+ * across header, footer, and tab-bar.
30
+ *
31
+ * @internal
32
+ * @param contentEl The content element to resolve the scroll element from.
33
+ * @param options Configuration for DOM setup (classes, CSS vars, a11y).
34
+ */
35
+ export const createScrollHideController = async (contentEl, options) => {
36
+ const { el, cssVar, hiddenClass, contentPartnerClass, contentHiddenClass, shouldKeepAriaHidden } = options;
37
+ let controllerIsHidden = false;
38
+ let resizeObserver;
39
+ // --- DOM setup: height tracking, classes, a11y ---
40
+ const updateHideHeight = () => {
41
+ readTask(() => {
42
+ const heightPx = el.offsetHeight;
43
+ writeTask(() => {
44
+ el.style.setProperty(cssVar, `${heightPx}px`);
45
+ contentEl.style.setProperty(cssVar, `${heightPx}px`);
46
+ });
47
+ });
48
+ };
49
+ const setHidden = (hidden) => {
50
+ controllerIsHidden = hidden;
51
+ el.classList.toggle(hiddenClass, hidden);
52
+ if (hidden) {
53
+ el.setAttribute('inert', '');
54
+ el.setAttribute('aria-hidden', 'true');
55
+ }
56
+ else {
57
+ el.removeAttribute('inert');
58
+ if (!shouldKeepAriaHidden || !shouldKeepAriaHidden()) {
59
+ el.removeAttribute('aria-hidden');
60
+ }
61
+ }
62
+ contentEl.classList.toggle(contentHiddenClass, hidden);
63
+ };
64
+ // --- Scroll math ---
65
+ const scrollEl = await getScrollElement(contentEl);
66
+ let lastScrollPosition = 0;
67
+ let scrollPositionAtDirectionChange = 0;
68
+ let lastWheelEventTimestamp = 0;
69
+ let suppressShowUntil = 0;
70
+ const commitHide = (hidden) => {
71
+ if (hidden) {
72
+ suppressShowUntil = Date.now() + SUPPRESS_SHOW_DURATION_MS;
73
+ }
74
+ else {
75
+ suppressShowUntil = 0;
76
+ }
77
+ setHidden(hidden);
78
+ };
79
+ const handleWheel = (wheelEvent) => {
80
+ lastWheelEventTimestamp = Date.now();
81
+ readTask(() => {
82
+ const currentScrollTop = scrollEl.scrollTop;
83
+ // Both wheel and scroll events fire for mouse wheel input.
84
+ // Scroll events are suppressed for 80ms after a wheel event,
85
+ // but when they resume, handleScroll needs an up-to-date
86
+ // lastScrollPosition or it may detect the wrong direction.
87
+ lastScrollPosition = currentScrollTop;
88
+ if (currentScrollTop <= TOP_VISIBLE_THRESHOLD) {
89
+ if (controllerIsHidden) {
90
+ writeTask(() => commitHide(false));
91
+ }
92
+ return;
93
+ }
94
+ if (wheelEvent.deltaY < 0) {
95
+ scrollPositionAtDirectionChange = currentScrollTop;
96
+ if (controllerIsHidden) {
97
+ writeTask(() => commitHide(false));
98
+ }
99
+ }
100
+ else if (wheelEvent.deltaY > 0) {
101
+ const scrolledSinceDirectionChange = currentScrollTop - scrollPositionAtDirectionChange;
102
+ if (scrolledSinceDirectionChange >= SCROLL_HIDE_THRESHOLD && !controllerIsHidden) {
103
+ writeTask(() => commitHide(true));
104
+ }
105
+ }
106
+ });
107
+ };
108
+ const handleScroll = () => {
109
+ if (Date.now() - lastWheelEventTimestamp < WHEEL_SUPPRESS_DURATION_MS) {
110
+ return;
111
+ }
112
+ readTask(() => {
113
+ const currentScrollTop = scrollEl.scrollTop;
114
+ if (currentScrollTop <= TOP_VISIBLE_THRESHOLD) {
115
+ if (controllerIsHidden) {
116
+ writeTask(() => commitHide(false));
117
+ }
118
+ lastScrollPosition = currentScrollTop;
119
+ return;
120
+ }
121
+ if (currentScrollTop === lastScrollPosition) {
122
+ return;
123
+ }
124
+ const isScrollingDown = currentScrollTop > lastScrollPosition;
125
+ const wasScrollingDown = lastScrollPosition > scrollPositionAtDirectionChange;
126
+ if (isScrollingDown !== wasScrollingDown) {
127
+ scrollPositionAtDirectionChange = lastScrollPosition;
128
+ }
129
+ const scrolledSinceDirectionChange = Math.abs(currentScrollTop - scrollPositionAtDirectionChange);
130
+ const requiredScrollDistance = isScrollingDown ? SCROLL_HIDE_THRESHOLD : 0;
131
+ lastScrollPosition = currentScrollTop;
132
+ if (scrolledSinceDirectionChange < requiredScrollDistance) {
133
+ return;
134
+ }
135
+ const shouldHide = isScrollingDown;
136
+ if (shouldHide !== controllerIsHidden) {
137
+ if (!shouldHide && Date.now() < suppressShowUntil) {
138
+ return;
139
+ }
140
+ writeTask(() => commitHide(shouldHide));
141
+ }
142
+ });
143
+ };
144
+ // --- Controller ---
145
+ let initialized = false;
146
+ const init = () => {
147
+ initialized = true;
148
+ updateHideHeight();
149
+ if (typeof ResizeObserver !== 'undefined') {
150
+ resizeObserver = new ResizeObserver(() => updateHideHeight());
151
+ resizeObserver.observe(el);
152
+ }
153
+ scrollEl.addEventListener('scroll', handleScroll, { passive: true });
154
+ scrollEl.addEventListener('wheel', handleWheel, { passive: true });
155
+ contentEl.classList.add(contentPartnerClass);
156
+ };
157
+ const destroy = () => {
158
+ if (!initialized)
159
+ return;
160
+ scrollEl.removeEventListener('scroll', handleScroll);
161
+ scrollEl.removeEventListener('wheel', handleWheel);
162
+ if (resizeObserver) {
163
+ resizeObserver.disconnect();
164
+ resizeObserver = undefined;
165
+ }
166
+ contentEl.classList.remove(contentPartnerClass, contentHiddenClass);
167
+ contentEl.style.removeProperty(cssVar);
168
+ if (controllerIsHidden) {
169
+ el.classList.remove(hiddenClass);
170
+ el.removeAttribute('inert');
171
+ el.removeAttribute('aria-hidden');
172
+ controllerIsHidden = false;
173
+ }
174
+ el.style.removeProperty(cssVar);
175
+ };
176
+ const isHidden = () => controllerIsHidden;
177
+ return { isHidden, init, destroy };
178
+ };