@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.
- package/components/ion-accordion.js +1 -1
- package/components/ion-action-sheet.js +1 -1
- package/components/ion-alert.js +1 -1
- package/components/ion-avatar.js +1 -1
- package/components/ion-back-button.js +1 -1
- package/components/ion-badge.js +1 -1
- package/components/ion-breadcrumb.js +1 -1
- package/components/ion-button.js +1 -1
- package/components/ion-buttons.js +1 -1
- package/components/ion-content.js +1 -1
- package/components/ion-datetime.js +1 -1
- package/components/ion-fab-button.js +1 -1
- package/components/ion-footer.js +1 -1
- package/components/ion-header.js +1 -1
- package/components/ion-icon.js +1 -1
- package/components/ion-input-password-toggle.js +1 -1
- package/components/ion-input.js +1 -1
- package/components/ion-item-option.js +1 -1
- package/components/ion-item.js +1 -1
- package/components/ion-menu-button.js +1 -1
- package/components/ion-refresher-content.js +1 -1
- package/components/ion-reorder.js +1 -1
- package/components/ion-searchbar.js +1 -1
- package/components/ion-segment-button.js +1 -1
- package/components/ion-select-modal.js +1 -1
- package/components/ion-select-popover.js +1 -1
- package/components/ion-select.js +1 -1
- package/components/ion-tab-bar.js +1 -1
- package/components/ion-tab-button.js +1 -1
- package/components/ion-title.js +1 -1
- package/components/ion-toast.js +1 -1
- package/components/ion-toggle.js +1 -1
- package/components/{p-DiKh2eQw.js → p-4u9RNymb.js} +1 -1
- package/components/{p-B8KYxaMx.js → p-B8xhFBOE.js} +1 -1
- package/components/p-BAnNwWMA.js +4 -0
- package/components/p-BYZR_5L4.js +4 -0
- package/components/p-B_zaqq5F.js +4 -0
- package/components/p-CO9VfZyc.js +4 -0
- package/components/p-Cjx0xPoU.js +4 -0
- package/components/p-CyWeCU8e.js +4 -0
- package/components/p-DRnK3A-V.js +4 -0
- package/components/p-DcVqGRkT.js +4 -0
- package/components/p-Dght1fh7.js +4 -0
- package/components/p-GpxJMpJu.js +4 -0
- package/dist/cjs/{index-Cu9JNpPi.js → index-CgAbCW6L.js} +1 -1
- package/dist/cjs/ion-accordion_2.cjs.entry.js +1 -1
- package/dist/cjs/ion-action-sheet.cjs.entry.js +3 -3
- package/dist/cjs/ion-alert.cjs.entry.js +3 -3
- package/dist/cjs/ion-app_8.cjs.entry.js +194 -65
- package/dist/cjs/ion-avatar_3.cjs.entry.js +4 -4
- package/dist/cjs/ion-back-button.cjs.entry.js +1 -1
- package/dist/cjs/ion-breadcrumb_2.cjs.entry.js +1 -1
- package/dist/cjs/ion-button_2.cjs.entry.js +3 -3
- package/dist/cjs/ion-datetime_3.cjs.entry.js +1 -1
- package/dist/cjs/ion-fab_3.cjs.entry.js +1 -1
- package/dist/cjs/ion-input-password-toggle.cjs.entry.js +1 -1
- package/dist/cjs/ion-input.cjs.entry.js +4 -4
- package/dist/cjs/ion-item-option_3.cjs.entry.js +1 -1
- package/dist/cjs/ion-item_8.cjs.entry.js +1 -1
- package/dist/cjs/ion-menu_3.cjs.entry.js +1 -1
- package/dist/cjs/ion-refresher_2.cjs.entry.js +1 -1
- package/dist/cjs/ion-reorder_2.cjs.entry.js +1 -1
- package/dist/cjs/ion-searchbar.cjs.entry.js +2 -2
- package/dist/cjs/ion-segment_2.cjs.entry.js +1 -1
- package/dist/cjs/ion-select-modal.cjs.entry.js +4 -4
- package/dist/cjs/ion-select_3.cjs.entry.js +5 -5
- package/dist/cjs/ion-tab-bar_2.cjs.entry.js +78 -80
- package/dist/cjs/ion-toggle.cjs.entry.js +1 -1
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/scroll-hide-controller-G9HOmmhN.js +345 -0
- package/dist/collection/components/action-sheet/action-sheet.ionic.css +2 -5
- package/dist/collection/components/action-sheet/action-sheet.ios.css +2 -1
- package/dist/collection/components/action-sheet/action-sheet.md.css +2 -1
- package/dist/collection/components/alert/alert.ionic.css +2 -5
- package/dist/collection/components/alert/alert.ios.css +2 -1
- package/dist/collection/components/alert/alert.md.css +2 -1
- package/dist/collection/components/avatar/avatar.ionic.css +12 -6
- package/dist/collection/components/badge/badge.ionic.css +6 -3
- package/dist/collection/components/badge/badge.ios.css +4 -2
- package/dist/collection/components/badge/badge.md.css +2 -1
- package/dist/collection/components/button/button.ionic.css +4 -2
- package/dist/collection/components/buttons/buttons.ionic.css +1 -1
- package/dist/collection/components/content/content.css +39 -0
- package/dist/collection/components/footer/footer-interface.js +1 -0
- package/dist/collection/components/footer/footer.ios.css +19 -4
- package/dist/collection/components/footer/footer.js +102 -15
- package/dist/collection/components/footer/footer.md.css +28 -0
- package/dist/collection/components/header/header-interface.js +1 -0
- package/dist/collection/components/header/header.ionic.css +153 -0
- package/dist/collection/components/header/header.ios.css +153 -123
- package/dist/collection/components/header/header.js +139 -28
- package/dist/collection/components/header/header.md.css +176 -1
- package/dist/collection/components/header/header.utils.js +6 -13
- package/dist/collection/components/input/input.ionic.css +2 -1
- package/dist/collection/components/input/input.ios.css +2 -1
- package/dist/collection/components/input/input.md.css +2 -1
- package/dist/collection/components/item-option/item-option.ionic.css +2 -1
- package/dist/collection/components/searchbar/searchbar.ionic.css +6 -2
- package/dist/collection/components/segment-button/segment-button.ionic.css +2 -1
- package/dist/collection/components/select/select.ionic.css +2 -1
- package/dist/collection/components/select-modal/select-modal.ionic.css +2 -5
- package/dist/collection/components/select-modal/select-modal.ios.css +2 -1
- package/dist/collection/components/select-modal/select-modal.md.css +2 -1
- package/dist/collection/components/select-popover/select-popover.ionic.css +2 -5
- package/dist/collection/components/select-popover/select-popover.ios.css +2 -1
- package/dist/collection/components/select-popover/select-popover.md.css +2 -1
- package/dist/collection/components/tab-bar/tab-bar.ionic.css +25 -3
- package/dist/collection/components/tab-bar/tab-bar.ios.css +15 -0
- package/dist/collection/components/tab-bar/tab-bar.js +94 -88
- package/dist/collection/components/tab-bar/tab-bar.md.css +15 -0
- package/dist/collection/components/tab-button/tab-button.ionic.css +2 -1
- package/dist/collection/components/title/title.md.css +7 -0
- package/dist/collection/utils/scroll-hide-controller.js +178 -0
- package/dist/docs.json +144 -32
- package/dist/esm/{index-1y3VhUSy.js → index-D2tu5BUg.js} +1 -1
- package/dist/esm/ion-accordion_2.entry.js +1 -1
- package/dist/esm/ion-action-sheet.entry.js +3 -3
- package/dist/esm/ion-alert.entry.js +3 -3
- package/dist/esm/ion-app_8.entry.js +194 -65
- package/dist/esm/ion-avatar_3.entry.js +4 -4
- package/dist/esm/ion-back-button.entry.js +1 -1
- package/dist/esm/ion-breadcrumb_2.entry.js +1 -1
- package/dist/esm/ion-button_2.entry.js +3 -3
- package/dist/esm/ion-datetime_3.entry.js +1 -1
- package/dist/esm/ion-fab_3.entry.js +1 -1
- package/dist/esm/ion-input-password-toggle.entry.js +1 -1
- package/dist/esm/ion-input.entry.js +4 -4
- package/dist/esm/ion-item-option_3.entry.js +1 -1
- package/dist/esm/ion-item_8.entry.js +1 -1
- package/dist/esm/ion-menu_3.entry.js +1 -1
- package/dist/esm/ion-refresher_2.entry.js +1 -1
- package/dist/esm/ion-reorder_2.entry.js +1 -1
- package/dist/esm/ion-searchbar.entry.js +2 -2
- package/dist/esm/ion-segment_2.entry.js +1 -1
- package/dist/esm/ion-select-modal.entry.js +4 -4
- package/dist/esm/ion-select_3.entry.js +5 -5
- package/dist/esm/ion-tab-bar_2.entry.js +79 -81
- package/dist/esm/ion-toggle.entry.js +1 -1
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/scroll-hide-controller-CUNW7EpD.js +342 -0
- package/dist/html.html-data.json +36 -4
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-07bd8446.entry.js +4 -0
- package/dist/ionic/p-103e1d9f.entry.js +4 -0
- package/dist/ionic/{p-eeb7b373.entry.js → p-18bc4f2d.entry.js} +1 -1
- package/dist/ionic/p-248265c2.entry.js +4 -0
- package/dist/ionic/{p-fb457fdd.entry.js → p-3bc5b5e9.entry.js} +1 -1
- package/dist/ionic/{p-42a882b7.entry.js → p-43b00c5b.entry.js} +1 -1
- package/dist/ionic/{p-d0f253fc.entry.js → p-56224b93.entry.js} +1 -1
- package/dist/ionic/p-5732396e.entry.js +4 -0
- package/dist/ionic/{p-b3d04a86.entry.js → p-58d8877d.entry.js} +1 -1
- package/dist/ionic/{p-9e9be378.entry.js → p-5d079dc1.entry.js} +1 -1
- package/dist/ionic/{p-d0dfc9a3.entry.js → p-5e9665dd.entry.js} +1 -1
- package/dist/ionic/{p-7b2fe639.entry.js → p-5fee5cc1.entry.js} +1 -1
- package/dist/ionic/p-64734604.entry.js +4 -0
- package/dist/ionic/p-69feeff9.entry.js +4 -0
- package/dist/ionic/p-83acfa2d.entry.js +4 -0
- package/dist/ionic/{p-f692219d.entry.js → p-849b1759.entry.js} +1 -1
- package/dist/ionic/{p-a09c7a4c.entry.js → p-86f040e0.entry.js} +1 -1
- package/dist/ionic/p-8cb23f27.entry.js +4 -0
- package/dist/ionic/p-C1I_d964.js +4 -0
- package/dist/ionic/p-b97778f5.entry.js +4 -0
- package/dist/ionic/p-be9db7e8.entry.js +4 -0
- package/dist/ionic/{p-2fba8f15.entry.js → p-c04429f2.entry.js} +1 -1
- package/dist/ionic/p-c25a2a9b.entry.js +4 -0
- package/dist/ionic/{p-a797625e.entry.js → p-d272ff4f.entry.js} +1 -1
- package/dist/types/components/footer/footer-interface.d.ts +1 -0
- package/dist/types/components/footer/footer.d.ts +16 -0
- package/dist/types/components/header/header-interface.d.ts +1 -0
- package/dist/types/components/header/header.d.ts +17 -0
- package/dist/types/components/header/header.utils.d.ts +4 -5
- package/dist/types/components/tab-bar/tab-bar-interface.d.ts +1 -0
- package/dist/types/components/tab-bar/tab-bar.d.ts +17 -14
- package/dist/types/components.d.ts +37 -13
- package/dist/types/utils/scroll-hide-controller.d.ts +56 -0
- package/hydrate/index.js +477 -175
- package/hydrate/index.mjs +477 -175
- package/package.json +2 -2
- package/components/p-BZwQa9ey.js +0 -4
- package/components/p-CJCMdFb9.js +0 -4
- package/components/p-Ck_9SoHZ.js +0 -4
- package/components/p-CmnPD02Z.js +0 -4
- package/components/p-D-Xa9lMG.js +0 -4
- package/components/p-DFSqPtzD.js +0 -4
- package/components/p-NPsFR8Ng.js +0 -4
- package/components/p-U2gyA6Z0.js +0 -4
- package/components/p-q8wGOPJB.js +0 -4
- package/dist/cjs/keyboard-controller-CefyW9lm.js +0 -169
- package/dist/esm/keyboard-controller-BFwRdk2T.js +0 -167
- package/dist/ionic/p-0c4d9d7c.entry.js +0 -4
- package/dist/ionic/p-40dc9dbd.entry.js +0 -4
- package/dist/ionic/p-54419365.entry.js +0 -4
- package/dist/ionic/p-6293e9f1.entry.js +0 -4
- package/dist/ionic/p-7de397c7.entry.js +0 -4
- package/dist/ionic/p-8cf82b85.entry.js +0 -4
- package/dist/ionic/p-MqqcZQSB.js +0 -4
- package/dist/ionic/p-a6e04ca3.entry.js +0 -4
- package/dist/ionic/p-a6fe63f0.entry.js +0 -4
- package/dist/ionic/p-b275f17e.entry.js +0 -4
- package/dist/ionic/p-c5635175.entry.js +0 -4
- package/dist/ionic/p-dc8e0b91.entry.js +0 -4
- /package/components/{p-J03hHbbL.js → p-BaPtaYEC.js} +0 -0
- /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
|
|
5
|
-
import { findIonContent
|
|
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.
|
|
19
|
-
this.
|
|
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.
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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,
|
|
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: '
|
|
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-
|
|
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: '
|
|
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
|
-
"
|
|
256
|
-
"type": "
|
|
253
|
+
"scrollEffect": {
|
|
254
|
+
"type": "string",
|
|
257
255
|
"mutable": false,
|
|
258
256
|
"complexType": {
|
|
259
|
-
"original": "
|
|
260
|
-
"resolved": "
|
|
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":
|
|
269
|
+
"optional": true,
|
|
265
270
|
"docs": {
|
|
266
271
|
"tags": [],
|
|
267
|
-
"text": "
|
|
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":
|
|
272
|
-
"attribute": "
|
|
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
|
*
|
|
@@ -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
|
+
};
|