123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- /*!
- * (C) Ionic http://ionicframework.com - MIT License
- */
- import { readTask, writeTask, proxyCustomElement, HTMLElement, h, Host } from '@stencil/core/internal/client';
- import { g as getScrollElement, f as findIonContent, p as printIonContentErrorMsg } from './index8.js';
- import { k as clamp, i as inheritAriaAttributes } from './helpers.js';
- import { h as hostContext } from './theme.js';
- import { b as getIonMode } from './ionic-global.js';
- const TRANSITION = 'all 0.2s ease-in-out';
- const cloneElement = (tagName) => {
- const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
- if (getCachedEl !== null) {
- return getCachedEl;
- }
- const clonedEl = document.createElement(tagName);
- clonedEl.classList.add('ion-cloned-element');
- clonedEl.style.setProperty('display', 'none');
- document.body.appendChild(clonedEl);
- return clonedEl;
- };
- const createHeaderIndex = (headerEl) => {
- if (!headerEl) {
- return;
- }
- const toolbars = headerEl.querySelectorAll('ion-toolbar');
- return {
- el: headerEl,
- toolbars: Array.from(toolbars).map((toolbar) => {
- const ionTitleEl = toolbar.querySelector('ion-title');
- return {
- el: toolbar,
- background: toolbar.shadowRoot.querySelector('.toolbar-background'),
- ionTitleEl,
- innerTitleEl: ionTitleEl ? ionTitleEl.shadowRoot.querySelector('.toolbar-title') : null,
- ionButtonsEl: Array.from(toolbar.querySelectorAll('ion-buttons')),
- };
- }),
- };
- };
- const handleContentScroll = (scrollEl, scrollHeaderIndex, contentEl) => {
- readTask(() => {
- const scrollTop = scrollEl.scrollTop;
- const scale = clamp(1, 1 + -scrollTop / 500, 1.1);
- // Native refresher should not cause titles to scale
- const nativeRefresher = contentEl.querySelector('ion-refresher.refresher-native');
- if (nativeRefresher === null) {
- writeTask(() => {
- scaleLargeTitles(scrollHeaderIndex.toolbars, scale);
- });
- }
- });
- };
- const setToolbarBackgroundOpacity = (headerEl, opacity) => {
- /**
- * Fading in the backdrop opacity
- * should happen after the large title
- * has collapsed, so it is handled
- * by handleHeaderFade()
- */
- if (headerEl.collapse === 'fade') {
- return;
- }
- if (opacity === undefined) {
- headerEl.style.removeProperty('--opacity-scale');
- }
- else {
- headerEl.style.setProperty('--opacity-scale', opacity.toString());
- }
- };
- const handleToolbarBorderIntersection = (ev, mainHeaderIndex, scrollTop) => {
- if (!ev[0].isIntersecting) {
- return;
- }
- /**
- * There is a bug in Safari where overflow scrolling on a non-body element
- * does not always reset the scrollTop position to 0 when letting go. It will
- * set to 1 once the rubber band effect has ended. This causes the background to
- * appear slightly on certain app setups.
- *
- * Additionally, we check if user is rubber banding (scrolling is negative)
- * as this can mean they are using pull to refresh. Once the refresher starts,
- * the content is transformed which can cause the intersection observer to erroneously
- * fire here as well.
- */
- const scale = ev[0].intersectionRatio > 0.9 || scrollTop <= 0 ? 0 : ((1 - ev[0].intersectionRatio) * 100) / 75;
- setToolbarBackgroundOpacity(mainHeaderIndex.el, scale === 1 ? undefined : scale);
- };
- /**
- * If toolbars are intersecting, hide the scrollable toolbar content
- * and show the primary toolbar content. If the toolbars are not intersecting,
- * hide the primary toolbar content and show the scrollable toolbar content
- */
- const handleToolbarIntersection = (ev, // TODO(FW-2832): type (IntersectionObserverEntry[] triggers errors which should be sorted)
- mainHeaderIndex, scrollHeaderIndex, scrollEl) => {
- writeTask(() => {
- const scrollTop = scrollEl.scrollTop;
- handleToolbarBorderIntersection(ev, mainHeaderIndex, scrollTop);
- const event = ev[0];
- const intersection = event.intersectionRect;
- const intersectionArea = intersection.width * intersection.height;
- const rootArea = event.rootBounds.width * event.rootBounds.height;
- const isPageHidden = intersectionArea === 0 && rootArea === 0;
- const leftDiff = Math.abs(intersection.left - event.boundingClientRect.left);
- const rightDiff = Math.abs(intersection.right - event.boundingClientRect.right);
- const isPageTransitioning = intersectionArea > 0 && (leftDiff >= 5 || rightDiff >= 5);
- if (isPageHidden || isPageTransitioning) {
- return;
- }
- if (event.isIntersecting) {
- setHeaderActive(mainHeaderIndex, false);
- setHeaderActive(scrollHeaderIndex);
- }
- else {
- /**
- * There is a bug with IntersectionObserver on Safari
- * where `event.isIntersecting === false` when cancelling
- * a swipe to go back gesture. Checking the intersection
- * x, y, width, and height provides a workaround. This bug
- * does not happen when using Safari + Web Animations,
- * only Safari + CSS Animations.
- */
- const hasValidIntersection = (intersection.x === 0 && intersection.y === 0) || (intersection.width !== 0 && intersection.height !== 0);
- if (hasValidIntersection && scrollTop > 0) {
- setHeaderActive(mainHeaderIndex);
- setHeaderActive(scrollHeaderIndex, false);
- setToolbarBackgroundOpacity(mainHeaderIndex.el);
- }
- }
- });
- };
- const setHeaderActive = (headerIndex, active = true) => {
- const headerEl = headerIndex.el;
- const toolbars = headerIndex.toolbars;
- const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
- if (active) {
- headerEl.classList.remove('header-collapse-condense-inactive');
- ionTitles.forEach((ionTitle) => {
- if (ionTitle) {
- ionTitle.removeAttribute('aria-hidden');
- }
- });
- }
- else {
- headerEl.classList.add('header-collapse-condense-inactive');
- /**
- * The small title should only be accessed by screen readers
- * when the large title collapses into the small title due
- * to scrolling.
- *
- * Originally, the header was given `aria-hidden="true"`
- * but this caused issues with screen readers not being
- * able to access any focusable elements within the header.
- */
- ionTitles.forEach((ionTitle) => {
- if (ionTitle) {
- ionTitle.setAttribute('aria-hidden', 'true');
- }
- });
- }
- };
- const scaleLargeTitles = (toolbars = [], scale = 1, transition = false) => {
- toolbars.forEach((toolbar) => {
- const ionTitle = toolbar.ionTitleEl;
- const titleDiv = toolbar.innerTitleEl;
- if (!ionTitle || ionTitle.size !== 'large') {
- return;
- }
- titleDiv.style.transition = transition ? TRANSITION : '';
- titleDiv.style.transform = `scale3d(${scale}, ${scale}, 1)`;
- });
- };
- const handleHeaderFade = (scrollEl, baseEl, condenseHeader) => {
- readTask(() => {
- const scrollTop = scrollEl.scrollTop;
- const baseElHeight = baseEl.clientHeight;
- const fadeStart = condenseHeader ? condenseHeader.clientHeight : 0;
- /**
- * If we are using fade header with a condense
- * header, then the toolbar backgrounds should
- * not begin to fade in until the condense
- * header has fully collapsed.
- *
- * Additionally, the main content should not
- * overflow out of the container until the
- * condense header has fully collapsed. When
- * using just the condense header the content
- * should overflow out of the container.
- */
- if (condenseHeader !== null && scrollTop < fadeStart) {
- baseEl.style.setProperty('--opacity-scale', '0');
- scrollEl.style.setProperty('clip-path', `inset(${baseElHeight}px 0px 0px 0px)`);
- return;
- }
- const distanceToStart = scrollTop - fadeStart;
- const fadeDuration = 10;
- const scale = clamp(0, distanceToStart / fadeDuration, 1);
- writeTask(() => {
- scrollEl.style.removeProperty('clip-path');
- baseEl.style.setProperty('--opacity-scale', scale.toString());
- });
- });
- };
- const headerIosCss = "ion-header{display:block;position:relative;-ms-flex-order:-1;order:-1;width:100%;z-index:10}ion-header ion-toolbar:first-of-type{padding-top:var(--ion-safe-area-top, 0)}.header-ios ion-toolbar:last-of-type{--border-width:0 0 0.55px}@supports ((-webkit-backdrop-filter: blur(0)) or (backdrop-filter: blur(0))){.header-background{left:0;right:0;top:0;bottom:0;position:absolute;-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px)}.header-translucent-ios ion-toolbar{--opacity:.8}.header-collapse-condense-inactive .header-background{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}}.header-ios.ion-no-border ion-toolbar:last-of-type{--border-width:0}.header-collapse-fade ion-toolbar{--opacity-scale:inherit}.header-collapse-condense{z-index:9}.header-collapse-condense ion-toolbar{position:-webkit-sticky;position:sticky;top:0}.header-collapse-condense ion-toolbar:first-of-type{padding-top:0px;z-index:1}.header-collapse-condense ion-toolbar{--background:var(--ion-background-color, #fff);z-index:0}.header-collapse-condense ion-toolbar:last-of-type{--border-width:0px}.header-collapse-condense ion-toolbar ion-searchbar{padding-top:0px;padding-bottom:13px}.header-collapse-main{--opacity-scale:1}.header-collapse-main ion-toolbar{--opacity-scale:inherit}.header-collapse-main ion-toolbar.in-toolbar ion-title,.header-collapse-main ion-toolbar.in-toolbar ion-buttons{-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse{opacity:0;pointer-events:none}.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-title,.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-buttons.buttons-collapse{visibility:hidden}ion-header.header-ios:not(.header-collapse-main):has(~ion-content ion-header.header-ios[collapse=condense],~ion-content ion-header.header-ios.header-collapse-condense){opacity:0}";
- const IonHeaderIosStyle0 = headerIosCss;
- const headerMdCss = "ion-header{display:block;position:relative;-ms-flex-order:-1;order:-1;width:100%;z-index:10}ion-header ion-toolbar:first-of-type{padding-top:var(--ion-safe-area-top, 0)}.header-md{-webkit-box-shadow:0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);box-shadow:0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12)}.header-collapse-condense{display:none}.header-md.ion-no-border{-webkit-box-shadow:none;box-shadow:none}";
- const IonHeaderMdStyle0 = headerMdCss;
- const Header = /*@__PURE__*/ proxyCustomElement(class Header extends HTMLElement {
- constructor() {
- super();
- this.__registerHost();
- this.inheritedAttributes = {};
- this.setupFadeHeader = async (contentEl, condenseHeader) => {
- const scrollEl = (this.scrollEl = await getScrollElement(contentEl));
- /**
- * Handle fading of toolbars on scroll
- */
- this.contentScrollCallback = () => {
- handleHeaderFade(this.scrollEl, this.el, condenseHeader);
- };
- scrollEl.addEventListener('scroll', this.contentScrollCallback);
- handleHeaderFade(this.scrollEl, this.el, condenseHeader);
- };
- this.collapse = undefined;
- this.translucent = false;
- }
- componentWillLoad() {
- this.inheritedAttributes = inheritAriaAttributes(this.el);
- }
- componentDidLoad() {
- this.checkCollapsibleHeader();
- }
- componentDidUpdate() {
- this.checkCollapsibleHeader();
- }
- disconnectedCallback() {
- this.destroyCollapsibleHeader();
- }
- async checkCollapsibleHeader() {
- const mode = getIonMode(this);
- if (mode !== 'ios') {
- return;
- }
- const { collapse } = this;
- const hasCondense = collapse === 'condense';
- const hasFade = collapse === 'fade';
- this.destroyCollapsibleHeader();
- if (hasCondense) {
- const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
- const contentEl = pageEl ? findIonContent(pageEl) : null;
- // Cloned elements are always needed in iOS transition
- writeTask(() => {
- const title = cloneElement('ion-title');
- title.size = 'large';
- cloneElement('ion-back-button');
- });
- await this.setupCondenseHeader(contentEl, pageEl);
- }
- else if (hasFade) {
- const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
- const contentEl = pageEl ? findIonContent(pageEl) : null;
- if (!contentEl) {
- printIonContentErrorMsg(this.el);
- return;
- }
- const condenseHeader = contentEl.querySelector('ion-header[collapse="condense"]');
- await this.setupFadeHeader(contentEl, condenseHeader);
- }
- }
- destroyCollapsibleHeader() {
- if (this.intersectionObserver) {
- this.intersectionObserver.disconnect();
- this.intersectionObserver = undefined;
- }
- if (this.scrollEl && this.contentScrollCallback) {
- this.scrollEl.removeEventListener('scroll', this.contentScrollCallback);
- this.contentScrollCallback = undefined;
- }
- if (this.collapsibleMainHeader) {
- this.collapsibleMainHeader.classList.remove('header-collapse-main');
- this.collapsibleMainHeader = undefined;
- }
- }
- async setupCondenseHeader(contentEl, pageEl) {
- if (!contentEl || !pageEl) {
- printIonContentErrorMsg(this.el);
- return;
- }
- if (typeof IntersectionObserver === 'undefined') {
- return;
- }
- this.scrollEl = await getScrollElement(contentEl);
- const headers = pageEl.querySelectorAll('ion-header');
- this.collapsibleMainHeader = Array.from(headers).find((header) => header.collapse !== 'condense');
- if (!this.collapsibleMainHeader) {
- return;
- }
- const mainHeaderIndex = createHeaderIndex(this.collapsibleMainHeader);
- const scrollHeaderIndex = createHeaderIndex(this.el);
- if (!mainHeaderIndex || !scrollHeaderIndex) {
- return;
- }
- setHeaderActive(mainHeaderIndex, false);
- setToolbarBackgroundOpacity(mainHeaderIndex.el, 0);
- /**
- * Handle interaction between toolbar collapse and
- * showing/hiding content in the primary ion-header
- * as well as progressively showing/hiding the main header
- * border as the top-most toolbar collapses or expands.
- */
- const toolbarIntersection = (ev) => {
- handleToolbarIntersection(ev, mainHeaderIndex, scrollHeaderIndex, this.scrollEl);
- };
- this.intersectionObserver = new IntersectionObserver(toolbarIntersection, {
- root: contentEl,
- threshold: [0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
- });
- this.intersectionObserver.observe(scrollHeaderIndex.toolbars[scrollHeaderIndex.toolbars.length - 1].el);
- /**
- * Handle scaling of large iOS titles and
- * showing/hiding border on last toolbar
- * in primary header
- */
- this.contentScrollCallback = () => {
- handleContentScroll(this.scrollEl, scrollHeaderIndex, contentEl);
- };
- this.scrollEl.addEventListener('scroll', this.contentScrollCallback);
- writeTask(() => {
- if (this.collapsibleMainHeader !== undefined) {
- this.collapsibleMainHeader.classList.add('header-collapse-main');
- }
- });
- }
- render() {
- const { translucent, inheritedAttributes } = this;
- const mode = getIonMode(this);
- const collapse = this.collapse || 'none';
- // banner role must be at top level, so remove role if inside a menu
- const roleType = hostContext('ion-menu', this.el) ? 'none' : 'banner';
- return (h(Host, Object.assign({ key: 'b6cc27f0b08afc9fcc889683525da765d80ba672', role: roleType, class: {
- [mode]: true,
- // Used internally for styling
- [`header-${mode}`]: true,
- [`header-translucent`]: this.translucent,
- [`header-collapse-${collapse}`]: true,
- [`header-translucent-${mode}`]: this.translucent,
- } }, inheritedAttributes), mode === 'ios' && translucent && h("div", { key: '395766d4dcee3398bc91960db21f922095292f14', class: "header-background" }), h("slot", { key: '09a67ece27b258ff1248805d43d92a49b2c6859a' })));
- }
- get el() { return this; }
- static get style() { return {
- ios: IonHeaderIosStyle0,
- md: IonHeaderMdStyle0
- }; }
- }, [36, "ion-header", {
- "collapse": [1],
- "translucent": [4]
- }]);
- function defineCustomElement() {
- if (typeof customElements === "undefined") {
- return;
- }
- const components = ["ion-header"];
- components.forEach(tagName => { switch (tagName) {
- case "ion-header":
- if (!customElements.get(tagName)) {
- customElements.define(tagName, Header);
- }
- break;
- } });
- }
- export { Header as H, defineCustomElement as d };
|