@ionic/core 8.8.9-dev.11780493108.1d8e1a89 → 8.8.9-dev.11780493937.17fe092d

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 (48) hide show
  1. package/components/ion-content.js +1 -1
  2. package/components/ion-footer.js +1 -1
  3. package/components/ion-gallery.js +1 -1
  4. package/components/ion-header.js +1 -1
  5. package/components/ion-select-modal.js +1 -1
  6. package/components/ion-select.js +1 -1
  7. package/components/p-BF5oFX1I.js +4 -0
  8. package/components/p-CpFORZud.js +4 -0
  9. package/components/p-d77Zk1DK.js +4 -0
  10. package/components/p-hdGd8ben.js +4 -0
  11. package/dist/cjs/ion-app_8.cjs.entry.js +286 -28
  12. package/dist/cjs/ion-gallery.cjs.entry.js +7 -26
  13. package/dist/collection/components/content/content.css +56 -0
  14. package/dist/collection/components/footer/footer.ios.css +13 -0
  15. package/dist/collection/components/footer/footer.js +65 -18
  16. package/dist/collection/components/footer/footer.md.css +13 -0
  17. package/dist/collection/components/footer/footer.utils.js +9 -0
  18. package/dist/collection/components/gallery/gallery.js +9 -14
  19. package/dist/collection/components/header/header.ionic.css +75 -0
  20. package/dist/collection/components/header/header.ios.css +75 -0
  21. package/dist/collection/components/header/header.js +41 -11
  22. package/dist/collection/components/header/header.md.css +75 -0
  23. package/dist/collection/components/header/header.utils.js +9 -0
  24. package/dist/collection/utils/css-value-validation.js +0 -14
  25. package/dist/collection/utils/on-scroll/collapse-hide.utils.js +168 -0
  26. package/dist/docs.json +18 -10
  27. package/dist/esm/ion-app_8.entry.js +286 -28
  28. package/dist/esm/ion-gallery.entry.js +7 -26
  29. package/dist/html.html-data.json +9 -3
  30. package/dist/ionic/ionic.esm.js +1 -1
  31. package/dist/ionic/p-04b5794c.entry.js +4 -0
  32. package/dist/ionic/p-ad4d0138.entry.js +4 -0
  33. package/dist/types/components/footer/footer.d.ts +12 -2
  34. package/dist/types/components/footer/footer.utils.d.ts +1 -0
  35. package/dist/types/components/gallery/gallery.d.ts +4 -6
  36. package/dist/types/components/header/header.d.ts +10 -3
  37. package/dist/types/components/header/header.utils.d.ts +1 -0
  38. package/dist/types/components.d.ts +12 -12
  39. package/dist/types/utils/css-value-validation.d.ts +0 -9
  40. package/dist/types/utils/on-scroll/collapse-hide.utils.d.ts +26 -0
  41. package/hydrate/index.js +293 -54
  42. package/hydrate/index.mjs +293 -54
  43. package/package.json +1 -1
  44. package/components/p-7kL3tltU.js +0 -4
  45. package/components/p-BGiYL2RS.js +0 -4
  46. package/components/p-LB-QPk3e.js +0 -4
  47. package/dist/ionic/p-290778c1.entry.js +0 -4
  48. package/dist/ionic/p-70ee89c9.entry.js +0 -4
@@ -281,4 +281,60 @@
281
281
  * still allow the fixed content to appear under the scroll content if specified.
282
282
  */
283
283
  transform: translateZ(0);
284
+ }
285
+
286
+ :host(.content-header-hide-scroll-partner:not(.content-footer-hide-scroll-partner)) .inner-scroll {
287
+ transform: translateY(0);
288
+ backface-visibility: hidden;
289
+ height: 100%;
290
+ transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), height 300ms cubic-bezier(0, 0, 0.2, 1);
291
+ }
292
+
293
+ :host(.content-header-hide-scroll-partner:not(.content-footer-hide-scroll-partner).content-header-hide-scroll-hidden) .inner-scroll {
294
+ transform: translateY(calc(-1 * var(--header-hide-slide-y, 0px)));
295
+ backface-visibility: hidden;
296
+ height: calc(100% + var(--header-hide-slide-y, 0px));
297
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), height 200ms cubic-bezier(0.4, 0, 1, 1);
298
+ }
299
+
300
+ :host(.content-footer-hide-scroll-partner:not(.content-header-hide-scroll-partner)) .inner-scroll {
301
+ transform: translateY(0);
302
+ backface-visibility: hidden;
303
+ height: 100%;
304
+ transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), height 300ms cubic-bezier(0, 0, 0.2, 1);
305
+ }
306
+
307
+ :host(.content-footer-hide-scroll-partner:not(.content-header-hide-scroll-partner).content-footer-hide-scroll-hidden) .inner-scroll {
308
+ transform: translateY(0);
309
+ backface-visibility: hidden;
310
+ height: calc(100% + var(--footer-hide-slide-y, 0px));
311
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), height 200ms cubic-bezier(0.4, 0, 1, 1);
312
+ }
313
+
314
+ :host(.content-header-hide-scroll-partner.content-footer-hide-scroll-partner:not(.content-header-hide-scroll-hidden):not(.content-footer-hide-scroll-hidden)) .inner-scroll {
315
+ transform: translateY(0);
316
+ backface-visibility: hidden;
317
+ height: 100%;
318
+ transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), height 300ms cubic-bezier(0, 0, 0.2, 1);
319
+ }
320
+
321
+ :host(.content-header-hide-scroll-partner.content-footer-hide-scroll-partner.content-header-hide-scroll-hidden:not(.content-footer-hide-scroll-hidden)) .inner-scroll {
322
+ transform: translateY(calc(-1 * var(--header-hide-slide-y, 0px)));
323
+ backface-visibility: hidden;
324
+ height: calc(100% + var(--header-hide-slide-y, 0px));
325
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), height 200ms cubic-bezier(0.4, 0, 1, 1);
326
+ }
327
+
328
+ :host(.content-header-hide-scroll-partner.content-footer-hide-scroll-partner:not(.content-header-hide-scroll-hidden).content-footer-hide-scroll-hidden) .inner-scroll {
329
+ transform: translateY(0);
330
+ backface-visibility: hidden;
331
+ height: calc(100% + var(--footer-hide-slide-y, 0px));
332
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), height 200ms cubic-bezier(0.4, 0, 1, 1);
333
+ }
334
+
335
+ :host(.content-header-hide-scroll-partner.content-footer-hide-scroll-partner.content-header-hide-scroll-hidden.content-footer-hide-scroll-hidden) .inner-scroll {
336
+ transform: translateY(calc(-1 * var(--header-hide-slide-y, 0px)));
337
+ backface-visibility: hidden;
338
+ height: calc(100% + var(--header-hide-slide-y, 0px) + var(--footer-hide-slide-y, 0px));
339
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), height 200ms cubic-bezier(0.4, 0, 1, 1);
284
340
  }
@@ -71,6 +71,19 @@ ion-footer.footer-toolbar-padding ion-toolbar:last-of-type {
71
71
  padding-bottom: var(--ion-safe-area-bottom, 0);
72
72
  }
73
73
 
74
+ ion-footer.footer-collapse-hide {
75
+ transform: translateY(0);
76
+ transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), opacity 300ms cubic-bezier(0, 0, 0.2, 1);
77
+ opacity: 1;
78
+ }
79
+
80
+ ion-footer.footer-collapse-hide.footer-collapse-hide-hidden {
81
+ transform: translateY(var(--footer-hide-slide-y, 0px));
82
+ pointer-events: none;
83
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), opacity 300ms cubic-bezier(0.4, 0, 1, 1);
84
+ opacity: 0;
85
+ }
86
+
74
87
  /**
75
88
  * Convert a pixels given value into rem
76
89
  *
@@ -6,13 +6,15 @@ import { findIonContent, getScrollElement, printIonContentErrorMsg } from "../..
6
6
  import { createKeyboardController } from "../../utils/keyboard/keyboard-controller";
7
7
  import { config } from "../../global/config";
8
8
  import { getIonTheme } from "../../global/ionic-global";
9
- import { handleFooterFade } from "./footer.utils";
9
+ import { handleFooterFade, createFooterHideInteraction } from "./footer.utils";
10
10
  /**
11
11
  * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component.
12
12
  * @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the visual appearance of the component.
13
13
  */
14
14
  export class Footer {
15
15
  constructor() {
16
+ this.didLoad = false;
17
+ this.setupToken = 0;
16
18
  this.keyboardCtrl = null;
17
19
  this.keyboardCtrlPromise = null;
18
20
  this.keyboardVisible = false;
@@ -27,25 +29,45 @@ export class Footer {
27
29
  this.translucent = false;
28
30
  this.checkCollapsibleFooter = () => {
29
31
  const theme = getIonTheme(this);
30
- if (theme !== 'ios') {
31
- return;
32
- }
33
32
  const { collapse } = this;
34
33
  const hasFade = collapse === 'fade';
34
+ const hasHide = collapse === 'hide';
35
+ const runIosFade = theme === 'ios' && hasFade;
36
+ if (!runIosFade && !hasHide) {
37
+ this.destroyCollapsibleFooter();
38
+ return;
39
+ }
40
+ // Skip teardown/rebuild when the collapse mode and theme have not changed
41
+ // since the last setup — avoids thrashing listeners and resetting scroll
42
+ // accumulators on unrelated re-renders (e.g. keyboardVisible state flips).
43
+ const activeMode = hasHide ? 'hide' : 'fade';
44
+ if (this.appliedCollapse === activeMode && this.appliedTheme === theme) {
45
+ return;
46
+ }
35
47
  this.destroyCollapsibleFooter();
36
- if (hasFade) {
37
- const appRootSelector = config.get('appRootSelector', 'ion-app');
38
- const pageEl = this.el.closest(`${appRootSelector},ion-page,.ion-page,page-inner`);
39
- const contentEl = pageEl ? findIonContent(pageEl) : null;
40
- if (!contentEl) {
41
- printIonContentErrorMsg(this.el);
42
- return;
43
- }
48
+ const appRootSelector = config.get('appRootSelector', 'ion-app');
49
+ const pageEl = this.el.closest(`${appRootSelector},ion-page,.ion-page,page-inner`);
50
+ const contentEl = pageEl ? findIonContent(pageEl) : null;
51
+ if (!contentEl) {
52
+ printIonContentErrorMsg(this.el);
53
+ return;
54
+ }
55
+ this.appliedCollapse = activeMode;
56
+ this.appliedTheme = theme;
57
+ if (runIosFade) {
44
58
  this.setupFadeFooter(contentEl);
45
59
  }
60
+ else if (hasHide) {
61
+ void this.setupHideFooter(contentEl);
62
+ }
46
63
  };
47
64
  this.setupFadeFooter = async (contentEl) => {
48
- const scrollEl = (this.scrollEl = await getScrollElement(contentEl));
65
+ const token = ++this.setupToken;
66
+ const scrollEl = await getScrollElement(contentEl);
67
+ if (token !== this.setupToken) {
68
+ return;
69
+ }
70
+ this.scrollEl = scrollEl;
49
71
  /**
50
72
  * Handle fading of toolbars on scroll
51
73
  */
@@ -57,12 +79,18 @@ export class Footer {
57
79
  };
58
80
  }
59
81
  componentDidLoad() {
82
+ this.didLoad = true;
60
83
  this.checkCollapsibleFooter();
61
84
  }
62
85
  componentDidUpdate() {
63
86
  this.checkCollapsibleFooter();
64
87
  }
65
88
  async connectedCallback() {
89
+ // On re-attach (didLoad already true but disconnectedCallback ran since),
90
+ // componentDidLoad will not fire again — re-run setup here.
91
+ if (this.didLoad) {
92
+ this.checkCollapsibleFooter();
93
+ }
66
94
  const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
67
95
  /**
68
96
  * If the keyboard is hiding, then we need to wait
@@ -90,6 +118,7 @@ export class Footer {
90
118
  }
91
119
  }
92
120
  disconnectedCallback() {
121
+ this.destroyCollapsibleFooter();
93
122
  if (this.keyboardCtrlPromise) {
94
123
  this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
95
124
  this.keyboardCtrlPromise = null;
@@ -99,18 +128,36 @@ export class Footer {
99
128
  this.keyboardCtrl = null;
100
129
  }
101
130
  }
131
+ async setupHideFooter(contentEl) {
132
+ const token = ++this.setupToken;
133
+ const scrollEl = await getScrollElement(contentEl);
134
+ // A newer checkCollapsibleFooter ran while we were awaiting — abandon.
135
+ if (token !== this.setupToken) {
136
+ return;
137
+ }
138
+ this.scrollEl = scrollEl;
139
+ this.footerHideCleanup = createFooterHideInteraction(this.el, scrollEl);
140
+ }
102
141
  destroyCollapsibleFooter() {
142
+ // Invalidate any in-flight setupHideFooter/setupFadeFooter awaits.
143
+ this.setupToken++;
144
+ if (this.footerHideCleanup) {
145
+ this.footerHideCleanup();
146
+ this.footerHideCleanup = undefined;
147
+ }
103
148
  if (this.scrollEl && this.contentScrollCallback) {
104
149
  this.scrollEl.removeEventListener('scroll', this.contentScrollCallback);
105
150
  this.contentScrollCallback = undefined;
106
151
  }
152
+ this.appliedCollapse = undefined;
153
+ this.appliedTheme = undefined;
107
154
  }
108
155
  render() {
109
156
  const { translucent, collapse } = this;
110
157
  const theme = getIonTheme(this);
111
158
  const tabs = this.el.closest('ion-tabs');
112
159
  const tabBar = tabs === null || tabs === void 0 ? void 0 : tabs.querySelector(':scope > ion-tab-bar');
113
- return (h(Host, { key: '4a2989a177b47ee26ffa23e2c9f5418773584796', role: "contentinfo", class: {
160
+ return (h(Host, { key: '5df79a31f36febfad49c5858727e93c7ba5734f8', role: "contentinfo", class: {
114
161
  [theme]: true,
115
162
  // Used internally for styling
116
163
  [`footer-${theme}`]: true,
@@ -118,7 +165,7 @@ export class Footer {
118
165
  [`footer-translucent-${theme}`]: translucent,
119
166
  ['footer-toolbar-padding']: !this.keyboardVisible && (!tabBar || tabBar.slot !== 'bottom'),
120
167
  [`footer-collapse-${collapse}`]: collapse !== undefined,
121
- } }, theme === 'ios' && translucent && h("div", { key: 'c644963db3e06254014e398ea02189d6127ccf61', class: "footer-background" }), h("slot", { key: '839e33c6c30f5c1c703239a19458ff10da9a5e76' })));
168
+ } }, theme === 'ios' && translucent && h("div", { key: '9175ae4f6576d82dff2a00a36e91f4b633d8c9ad', class: "footer-background" }), h("slot", { key: 'd6d618cdae4726822d8e82edb782c5c86fc7b77b' })));
122
169
  }
123
170
  static get is() { return "ion-footer"; }
124
171
  static get originalStyleUrls() {
@@ -141,15 +188,15 @@ export class Footer {
141
188
  "type": "string",
142
189
  "mutable": false,
143
190
  "complexType": {
144
- "original": "'fade'",
145
- "resolved": "\"fade\" | undefined",
191
+ "original": "'fade' | 'hide'",
192
+ "resolved": "\"fade\" | \"hide\" | undefined",
146
193
  "references": {}
147
194
  },
148
195
  "required": false,
149
196
  "optional": true,
150
197
  "docs": {
151
198
  "tags": [],
152
- "text": "Describes the scroll effect that will be applied to the footer.\nOnly applies when the theme is `\"ios\"`."
199
+ "text": "Describes the scroll effect that will be applied to the footer.\n\n- `\"fade\"` only applies when the theme is `\"ios\"`.\n- `\"hide\"` applies to all themes (`\"ios\"`, `\"md\"`, and `\"ionic\"`): the footer\n slides down and fades out after cumulative downward scrolling on the page content,\n and returns on any upward scroll (same behavior as `ion-header[collapse=\"hide\"]`)."
153
200
  },
154
201
  "getter": false,
155
202
  "setter": false,
@@ -71,6 +71,19 @@ ion-footer.footer-toolbar-padding ion-toolbar:last-of-type {
71
71
  padding-bottom: var(--ion-safe-area-bottom, 0);
72
72
  }
73
73
 
74
+ ion-footer.footer-collapse-hide {
75
+ transform: translateY(0);
76
+ transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), opacity 300ms cubic-bezier(0, 0, 0.2, 1);
77
+ opacity: 1;
78
+ }
79
+
80
+ ion-footer.footer-collapse-hide.footer-collapse-hide-hidden {
81
+ transform: translateY(var(--footer-hide-slide-y, 0px));
82
+ pointer-events: none;
83
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), opacity 300ms cubic-bezier(0.4, 0, 1, 1);
84
+ opacity: 0;
85
+ }
86
+
74
87
  /**
75
88
  * Convert a pixels given value into rem
76
89
  *
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { readTask, writeTask } from "@stencil/core";
5
5
  import { clamp } from "../../utils/helpers";
6
+ import { createCollapseHideInteraction } from "../../utils/on-scroll/collapse-hide.utils";
6
7
  export const handleFooterFade = (scrollEl, baseEl) => {
7
8
  readTask(() => {
8
9
  const scrollTop = scrollEl.scrollTop;
@@ -31,3 +32,11 @@ export const handleFooterFade = (scrollEl, baseEl) => {
31
32
  });
32
33
  });
33
34
  };
35
+ export const createFooterHideInteraction = (footerEl, scrollEl) => createCollapseHideInteraction({
36
+ regionEl: footerEl,
37
+ scrollEl,
38
+ slideCssVar: '--footer-hide-slide-y',
39
+ contentPartnerClass: 'content-footer-hide-scroll-partner',
40
+ contentHiddenClass: 'content-footer-hide-scroll-hidden',
41
+ regionHiddenClass: 'footer-collapse-hide-hidden',
42
+ });
@@ -2,7 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Host, h } from "@stencil/core";
5
- import { isCssVariable, isValidLengthPercentage } from "../../utils/css-value-validation";
5
+ import { isValidLengthPercentage } from "../../utils/css-value-validation";
6
6
  import { raf } from "../../utils/helpers";
7
7
  import { printIonWarning } from "../../utils/logging/index";
8
8
  import { getIonTheme } from "../../global/ionic-global";
@@ -46,8 +46,7 @@ export class Gallery {
46
46
  /**
47
47
  * The space between gallery items. Accepts valid CSS [length-percentage](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/length-percentage)
48
48
  * values like `16px`, `1rem`, `20%`, math functions like `calc(10px + 20%)`,
49
- * CSS variables like `var(--app-gallery-gap)`, or numbers (treated as pixel
50
- * values). Can also be set as a breakpoint map
49
+ * or numbers (treated as pixel values). Can also be set as a breakpoint map
51
50
  * (e.g. `{ xs: '8px', sm: '1rem', md: '24px' }`). Does not accept
52
51
  * space-separated values or CSS keyword values like `inherit`, `auto`, etc.
53
52
  */
@@ -168,10 +167,9 @@ export class Gallery {
168
167
  return numericColumns;
169
168
  }
170
169
  /**
171
- * Normalize a single gap value (`gap` as a number, a string such as a CSS
172
- * length-percentage or `var()` reference, or one entry from a `gap`
173
- * breakpoint map) to a CSS length string. Returns `undefined` when the
174
- * input cannot be interpreted as a valid CSS length or `var()` reference.
170
+ * Normalize a single gap value (`gap` as a number, string, or one entry from
171
+ * a `gap` breakpoint map) to a CSS length string. Returns `undefined` when
172
+ * the input cannot be interpreted as a valid CSS length.
175
173
  */
176
174
  sanitizeGap(gap) {
177
175
  if (gap === undefined) {
@@ -188,9 +186,6 @@ export class Gallery {
188
186
  if (typeof normalizedGap !== 'string') {
189
187
  return undefined;
190
188
  }
191
- if (isCssVariable(normalizedGap)) {
192
- return normalizedGap;
193
- }
194
189
  const isValidCssLength = isValidLengthPercentage(normalizedGap);
195
190
  return isValidCssLength ? normalizedGap : undefined;
196
191
  }
@@ -271,7 +266,7 @@ export class Gallery {
271
266
  if (this.hasWarnedInvalidGap) {
272
267
  return;
273
268
  }
274
- printIonWarning(`[ion-gallery] - Invalid "gap" value (${JSON.stringify(gap)}). Expected a non-negative number, CSS length string, CSS variable (e.g. var(--app-gap)), or breakpoint map object (e.g. { xs: 8, md: "1rem" }).`, this.el);
269
+ printIonWarning(`[ion-gallery] - Invalid "gap" value (${JSON.stringify(gap)}). Expected a non-negative number, CSS length string, or breakpoint map object (e.g. { xs: 8, md: "1rem" }).`, this.el);
275
270
  this.hasWarnedInvalidGap = true;
276
271
  }
277
272
  /**
@@ -457,11 +452,11 @@ export class Gallery {
457
452
  const { layout } = this;
458
453
  const order = this.getOrder();
459
454
  const theme = getIonTheme(this);
460
- return (h(Host, { key: '4f578bea373b422f5dd41395ca04ade699a2f398', class: {
455
+ return (h(Host, { key: '1bf2973d22835c0dbddf3214b602f8c08b95e421', class: {
461
456
  [theme]: true,
462
457
  [`gallery-layout-${layout}`]: true,
463
458
  [`gallery-order-${order}`]: layout === 'masonry' && order !== undefined,
464
- } }, h("slot", { key: '0daa3ba1ea894c8c10ab077a6b83476ad5cf514d', onSlotchange: this.onSlotChange })));
459
+ } }, h("slot", { key: '0dea31f609f6afdb1d73ecb2d873909ffe49203f', onSlotchange: this.onSlotChange })));
465
460
  }
466
461
  static get is() { return "ion-gallery"; }
467
462
  static get encapsulation() { return "shadow"; }
@@ -562,7 +557,7 @@ export class Gallery {
562
557
  "optional": false,
563
558
  "docs": {
564
559
  "tags": [],
565
- "text": "The space between gallery items. Accepts valid CSS [length-percentage](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/length-percentage)\nvalues like `16px`, `1rem`, `20%`, math functions like `calc(10px + 20%)`,\nCSS variables like `var(--app-gallery-gap)`, or numbers (treated as pixel\nvalues). Can also be set as a breakpoint map\n(e.g. `{ xs: '8px', sm: '1rem', md: '24px' }`). Does not accept\nspace-separated values or CSS keyword values like `inherit`, `auto`, etc."
560
+ "text": "The space between gallery items. Accepts valid CSS [length-percentage](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/length-percentage)\nvalues like `16px`, `1rem`, `20%`, math functions like `calc(10px + 20%)`,\nor numbers (treated as pixel values). Can also be set as a breakpoint map\n(e.g. `{ xs: '8px', sm: '1rem', md: '24px' }`). Does not accept\nspace-separated values or CSS keyword values like `inherit`, `auto`, etc."
566
561
  },
567
562
  "getter": false,
568
563
  "setter": false,
@@ -1,3 +1,64 @@
1
+ /**
2
+ * Convert a pixels given value into rem
3
+ *
4
+ * @param pixels - Value in pixels to be converted (i.e. px)
5
+ * @param context (optional) - Baseline value
6
+ */
7
+ /**
8
+ * Convert a font size to a dynamic font size.
9
+ * Fonts that participate in Dynamic Type should use
10
+ * dynamic font sizes.
11
+ * @param size - The initial font size including the unit (i.e. px or pt)
12
+ * @param unit (optional) - The unit to convert to. Use this if you want to
13
+ * convert to a unit other than $baselineUnit.
14
+ */
15
+ /**
16
+ * Convert a font size to a dynamic font size but impose
17
+ * a maximum font size.
18
+ * @param size - The initial font size including the unit (i.e. px or pt)
19
+ * @param maxScale - The maximum scale of the font (i.e. 2.5 for a maximum 250% scale).
20
+ * @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
21
+ * convert to a unit other than $baselineUnit.
22
+ */
23
+ /**
24
+ * Convert a font size to a dynamic font size but impose
25
+ * a minimum font size.
26
+ * @param size - The initial font size including the unit (i.e. px or pt)
27
+ * @param minScale - The minimum scale of the font (i.e. 0.8 for a minimum 80% scale).
28
+ * @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
29
+ * convert to a unit other than $baselineUnit.
30
+ */
31
+ /**
32
+ * Convert a font size to a dynamic font size but impose
33
+ * maximum and minimum font sizes.
34
+ * @param size - The initial font size including the unit (i.e. px or pt)
35
+ * @param minScale - The minimum scale of the font (i.e. 0.8 for a minimum 80% scale).
36
+ * @param maxScale - The maximum scale of the font (i.e. 2.5 for a maximum 250% scale).
37
+ * @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
38
+ * convert to a unit other than $baselineUnit.
39
+ */
40
+ /**
41
+ * A heuristic that applies CSS to tablet
42
+ * viewports.
43
+ *
44
+ * Usage:
45
+ * @include tablet-viewport() {
46
+ * :host {
47
+ * background-color: green;
48
+ * }
49
+ * }
50
+ */
51
+ /**
52
+ * A heuristic that applies CSS to mobile
53
+ * viewports (i.e. phones, not tablets).
54
+ *
55
+ * Usage:
56
+ * @include mobile-viewport() {
57
+ * :host {
58
+ * background-color: blue;
59
+ * }
60
+ * }
61
+ */
1
62
  ion-header {
2
63
  display: block;
3
64
  position: relative;
@@ -9,6 +70,20 @@ ion-header ion-toolbar:first-of-type {
9
70
  padding-top: var(--ion-safe-area-top, 0);
10
71
  }
11
72
 
73
+ ion-header.header-collapse-hide {
74
+ transform: translateY(0);
75
+ transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), opacity 300ms cubic-bezier(0, 0, 0.2, 1);
76
+ opacity: 1;
77
+ z-index: 10;
78
+ }
79
+
80
+ ion-header.header-collapse-hide.header-collapse-hide-hidden {
81
+ transform: translateY(calc(-1 * var(--header-hide-slide-y, 0px)));
82
+ pointer-events: none;
83
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), opacity 300ms cubic-bezier(0.4, 0, 1, 1);
84
+ opacity: 0;
85
+ }
86
+
12
87
  /**
13
88
  * A heuristic that applies CSS to tablet
14
89
  * viewports.
@@ -1,3 +1,64 @@
1
+ /**
2
+ * Convert a pixels given value into rem
3
+ *
4
+ * @param pixels - Value in pixels to be converted (i.e. px)
5
+ * @param context (optional) - Baseline value
6
+ */
7
+ /**
8
+ * Convert a font size to a dynamic font size.
9
+ * Fonts that participate in Dynamic Type should use
10
+ * dynamic font sizes.
11
+ * @param size - The initial font size including the unit (i.e. px or pt)
12
+ * @param unit (optional) - The unit to convert to. Use this if you want to
13
+ * convert to a unit other than $baselineUnit.
14
+ */
15
+ /**
16
+ * Convert a font size to a dynamic font size but impose
17
+ * a maximum font size.
18
+ * @param size - The initial font size including the unit (i.e. px or pt)
19
+ * @param maxScale - The maximum scale of the font (i.e. 2.5 for a maximum 250% scale).
20
+ * @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
21
+ * convert to a unit other than $baselineUnit.
22
+ */
23
+ /**
24
+ * Convert a font size to a dynamic font size but impose
25
+ * a minimum font size.
26
+ * @param size - The initial font size including the unit (i.e. px or pt)
27
+ * @param minScale - The minimum scale of the font (i.e. 0.8 for a minimum 80% scale).
28
+ * @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
29
+ * convert to a unit other than $baselineUnit.
30
+ */
31
+ /**
32
+ * Convert a font size to a dynamic font size but impose
33
+ * maximum and minimum font sizes.
34
+ * @param size - The initial font size including the unit (i.e. px or pt)
35
+ * @param minScale - The minimum scale of the font (i.e. 0.8 for a minimum 80% scale).
36
+ * @param maxScale - The maximum scale of the font (i.e. 2.5 for a maximum 250% scale).
37
+ * @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
38
+ * convert to a unit other than $baselineUnit.
39
+ */
40
+ /**
41
+ * A heuristic that applies CSS to tablet
42
+ * viewports.
43
+ *
44
+ * Usage:
45
+ * @include tablet-viewport() {
46
+ * :host {
47
+ * background-color: green;
48
+ * }
49
+ * }
50
+ */
51
+ /**
52
+ * A heuristic that applies CSS to mobile
53
+ * viewports (i.e. phones, not tablets).
54
+ *
55
+ * Usage:
56
+ * @include mobile-viewport() {
57
+ * :host {
58
+ * background-color: blue;
59
+ * }
60
+ * }
61
+ */
1
62
  ion-header {
2
63
  display: block;
3
64
  position: relative;
@@ -9,6 +70,20 @@ ion-header ion-toolbar:first-of-type {
9
70
  padding-top: var(--ion-safe-area-top, 0);
10
71
  }
11
72
 
73
+ ion-header.header-collapse-hide {
74
+ transform: translateY(0);
75
+ transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), opacity 300ms cubic-bezier(0, 0, 0.2, 1);
76
+ opacity: 1;
77
+ z-index: 10;
78
+ }
79
+
80
+ ion-header.header-collapse-hide.header-collapse-hide-hidden {
81
+ transform: translateY(calc(-1 * var(--header-hide-slide-y, 0px)));
82
+ pointer-events: none;
83
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), opacity 300ms cubic-bezier(0.4, 0, 1, 1);
84
+ opacity: 0;
85
+ }
86
+
12
87
  /**
13
88
  * Convert a pixels given value into rem
14
89
  *
@@ -7,7 +7,7 @@ import { inheritAriaAttributes } from "../../utils/helpers";
7
7
  import { hostContext } from "../../utils/theme";
8
8
  import { config } from "../../global/config";
9
9
  import { getIonTheme } from "../../global/ionic-global";
10
- import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity, getRoleType, } from "./header.utils";
10
+ import { cloneElement, createHeaderHideInteraction, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity, getRoleType, } from "./header.utils";
11
11
  /**
12
12
  * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component.
13
13
  * @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the visual appearance of the component.
@@ -15,6 +15,7 @@ import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade,
15
15
  export class Header {
16
16
  constructor() {
17
17
  this.inheritedAttributes = {};
18
+ this.didLoad = false;
18
19
  /**
19
20
  * If `true`, the header will have a line at the bottom.
20
21
  * TODO(ROU-10855): add support for this prop on ios/md themes
@@ -45,25 +46,37 @@ export class Header {
45
46
  this.inheritedAttributes = inheritAriaAttributes(this.el);
46
47
  }
47
48
  componentDidLoad() {
49
+ this.didLoad = true;
48
50
  this.checkCollapsibleHeader();
49
51
  }
50
52
  componentDidUpdate() {
51
53
  this.checkCollapsibleHeader();
52
54
  }
55
+ connectedCallback() {
56
+ // On re-attach (didLoad already true but disconnectedCallback ran since),
57
+ // componentDidLoad will not fire again — re-run setup here.
58
+ if (this.didLoad) {
59
+ this.checkCollapsibleHeader();
60
+ }
61
+ }
53
62
  disconnectedCallback() {
54
63
  this.destroyCollapsibleHeader();
55
64
  }
56
65
  async checkCollapsibleHeader() {
57
66
  const theme = getIonTheme(this);
58
- if (theme !== 'ios') {
59
- return;
60
- }
61
67
  const { collapse } = this;
62
68
  const hasCondense = collapse === 'condense';
63
69
  const hasFade = collapse === 'fade';
70
+ const hasHide = collapse === 'hide';
71
+ const runIosCollapse = theme === 'ios' && (hasCondense || hasFade);
72
+ const runHide = hasHide;
73
+ if (!runIosCollapse && !runHide) {
74
+ this.destroyCollapsibleHeader();
75
+ return;
76
+ }
64
77
  this.destroyCollapsibleHeader();
65
78
  const appRootSelector = config.get('appRootSelector', 'ion-app');
66
- if (hasCondense) {
79
+ if (runIosCollapse && hasCondense) {
67
80
  const pageEl = this.el.closest(`${appRootSelector},ion-page,.ion-page,page-inner`);
68
81
  const contentEl = pageEl ? findIonContent(pageEl) : null;
69
82
  // Cloned elements are always needed in iOS transition
@@ -74,7 +87,7 @@ export class Header {
74
87
  });
75
88
  await this.setupCondenseHeader(contentEl, pageEl);
76
89
  }
77
- else if (hasFade) {
90
+ else if (runIosCollapse && hasFade) {
78
91
  const pageEl = this.el.closest(`${appRootSelector},ion-page,.ion-page,page-inner`);
79
92
  const contentEl = pageEl ? findIonContent(pageEl) : null;
80
93
  if (!contentEl) {
@@ -84,12 +97,29 @@ export class Header {
84
97
  const condenseHeader = contentEl.querySelector('ion-header[collapse="condense"]');
85
98
  await this.setupFadeHeader(contentEl, condenseHeader);
86
99
  }
100
+ if (runHide) {
101
+ const pageEl = this.el.closest(`${appRootSelector},ion-page,.ion-page,page-inner`);
102
+ const contentEl = pageEl ? findIonContent(pageEl) : null;
103
+ if (!contentEl) {
104
+ printIonContentErrorMsg(this.el);
105
+ return;
106
+ }
107
+ await this.setupHideHeader(contentEl);
108
+ }
109
+ }
110
+ async setupHideHeader(contentEl) {
111
+ const scrollEl = (this.scrollEl = await getScrollElement(contentEl));
112
+ this.headerHideCleanup = createHeaderHideInteraction(this.el, scrollEl);
87
113
  }
88
114
  destroyCollapsibleHeader() {
89
115
  if (this.intersectionObserver) {
90
116
  this.intersectionObserver.disconnect();
91
117
  this.intersectionObserver = undefined;
92
118
  }
119
+ if (this.headerHideCleanup) {
120
+ this.headerHideCleanup();
121
+ this.headerHideCleanup = undefined;
122
+ }
93
123
  if (this.scrollEl && this.contentScrollCallback) {
94
124
  this.scrollEl.removeEventListener('scroll', this.contentScrollCallback);
95
125
  this.contentScrollCallback = undefined;
@@ -156,7 +186,7 @@ export class Header {
156
186
  const isCondensed = collapse === 'condense';
157
187
  // banner role must be at top level, so remove role if inside a menu
158
188
  const roleType = getRoleType(hostContext('ion-menu', this.el), isCondensed, theme);
159
- return (h(Host, Object.assign({ key: 'b43e5f542bdcec5e94c299556183cf6d9d673438', role: roleType, class: {
189
+ return (h(Host, Object.assign({ key: '5cae1ff0bbc5f2035325c128a9394caf7f1459a0', role: roleType, class: {
160
190
  [theme]: true,
161
191
  // Used internally for styling
162
192
  [`header-${theme}`]: true,
@@ -164,7 +194,7 @@ export class Header {
164
194
  [`header-collapse-${collapse}`]: true,
165
195
  [`header-translucent-${theme}`]: this.translucent,
166
196
  ['header-divider']: divider,
167
- } }, inheritedAttributes), theme !== 'md' && translucent && h("div", { key: 'fd9938f02edd38e1afc83025373ec0aec5633711', class: "header-background" }), h("slot", { key: '900aaa7da5d6f08e6f94b128fa065348d595159e' })));
197
+ } }, inheritedAttributes), theme !== 'md' && translucent && h("div", { key: '705f120951a3dd429286b66cd0b511fa267b3702', class: "header-background" }), h("slot", { key: '1c7a9d474083cf92abfe88c02d363f8d420716ca' })));
168
198
  }
169
199
  static get is() { return "ion-header"; }
170
200
  static get originalStyleUrls() {
@@ -187,15 +217,15 @@ export class Header {
187
217
  "type": "string",
188
218
  "mutable": false,
189
219
  "complexType": {
190
- "original": "'condense' | 'fade'",
191
- "resolved": "\"condense\" | \"fade\" | undefined",
220
+ "original": "'condense' | 'fade' | 'hide'",
221
+ "resolved": "\"condense\" | \"fade\" | \"hide\" | undefined",
192
222
  "references": {}
193
223
  },
194
224
  "required": false,
195
225
  "optional": true,
196
226
  "docs": {
197
227
  "tags": [],
198
- "text": "Describes the scroll effect that will be applied to the header.\nOnly applies when the theme is `\"ios\"`.\n\nTypically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)"
228
+ "text": "Describes the scroll effect that will be applied to the header.\n\n- `\"condense\"` and `\"fade\"` only apply when the theme is `\"ios\"`.\n Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles).\n- `\"hide\"` applies to all themes (`\"ios\"`, `\"md\"`, and `\"ionic\"`): the header\n slides up and fades out after cumulative downward scrolling on the page content,\n and returns on any upward scroll."
199
229
  },
200
230
  "getter": false,
201
231
  "setter": false,