UNPKG

@progressive-development/pd-page

Version:

Progressive development page helper, teaser, menu, footer.

372 lines (341 loc) 14.3 kB
import { css, LitElement, html } from 'lit'; import { property } from 'lit/decorators.js'; import { localized } from '@lit/localize'; import '../pd-menu.js'; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; let PdSiteHeader = class extends LitElement { constructor() { super(...arguments); this.menuItems = []; this.topMenuItems = []; this.activeRoute = ""; this.logoRoute = "/"; this.selectedLocale = ""; this.locales = []; this.singleLocaleMenu = false; this.showTeaser = false; this.teaserCollapsed = false; this.keepTeaserLogo = false; this.teaserFitContent = false; this.skipTeaserTransition = false; this.topMenuMode = "detached"; this.bgImage = ""; this._lastHeight = 0; } // ── Lifecycle ────────────────────────────────────────────── connectedCallback() { super.connectedCallback(); this._resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { const height = entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height; if (Math.abs(height - this._lastHeight) > 0.5) { this._lastHeight = height; this.dispatchEvent( new CustomEvent("height-change", { detail: { height }, bubbles: true, composed: true }) ); } } }); this._resizeObserver.observe(this); } disconnectedCallback() { this._resizeObserver?.disconnect(); super.disconnectedCallback(); } updated(changedProps) { super.updated(changedProps); if (changedProps.has("teaserCollapsed")) { this.dispatchEvent( new CustomEvent("teaser-collapse-change", { detail: { collapsed: this.teaserCollapsed }, bubbles: true, composed: true }) ); } } // ── Render ───────────────────────────────────────────────── render() { const hasTopMenu = this.topMenuItems.length > 0; const showLogo = !this.showTeaser || this.teaserCollapsed || this.keepTeaserLogo; const isIntegratedBg = this.bgImage && this.topMenuMode === "integrated"; const mainMenuBgImage = this.bgImage && !isIntegratedBg ? this.bgImage : ""; const topMenuLocales = hasTopMenu && !this.singleLocaleMenu ? this.locales : []; const mainMenuLocales = this.singleLocaleMenu ? this.locales : []; return html` ${isIntegratedBg ? html` <div class="header-bg-image" style="background-image: url('${this.bgImage}')" ></div> <div class="header-bg-gradient"></div> ` : ""} ${hasTopMenu ? html` <div class="top-menu-area ${isIntegratedBg ? "over-bg-image" : ""}"> <pd-menu noBurgerMenu .topMenuItems=${this.topMenuItems} .menuItems=${[]} .locales=${topMenuLocales} selectedLocale="${this.selectedLocale}" activeRoute="${this.activeRoute}" ></pd-menu> </div> ` : ""} ${this.showTeaser ? html` <div class="teaser-area"> <slot name="teaser-content"></slot> </div> ` : ""} <div class="menu-area ${hasTopMenu ? "with-top-menu" : ""} ${isIntegratedBg ? "over-bg-image" : ""}" > <pd-menu .menuItems=${this.menuItems} .topMenuItems=${[]} .locales=${mainMenuLocales} selectedLocale="${this.selectedLocale}" activeRoute="${this.activeRoute}" logoRoute="${this.logoRoute}" .bgImage=${mainMenuBgImage} > <span slot="slotLogo" class="logo" ?hidden=${!showLogo}> <slot name="logo"></slot> </span> </pd-menu> </div> `; } }; // ── Styles ───────────────────────────────────────────────── PdSiteHeader.styles = [ css` /* ═══════════════════════════════════════════════════════════ HOST ═══════════════════════════════════════════════════════════ */ :host { display: block; position: relative; --_transition: var(--pd-site-header-transition, 300ms ease-out); /* Responsive teaser height defaults (mobile-first) */ --_teaser-h: 200px; /* Responsive menu height defaults (mobile-first) */ --_menu-h: 50px; } @media (min-width: 768px) { :host { --_teaser-h: 280px; --_menu-h: 56px; } } @media (min-width: 1024px) { :host { --_teaser-h: 380px; --_menu-h: 60px; } } /* ═══════════════════════════════════════════════════════════ TOP MENU AREA ═══════════════════════════════════════════════════════════ */ .top-menu-area { --pd-menu-height: var(--pd-site-header-top-height, 30px); --pd-menu-font-size: var(--pd-site-header-top-font-size, 0.8em); --pd-menu-shadow: 0; } /* Detached mode: top menu has its own background */ :host([top-menu-mode="detached"]) .top-menu-area { --pd-menu-bg-col: var( --pd-site-header-top-bg, var(--pd-default-darker-col, #1a1a2e) ); --pd-menu-font-col: var( --pd-site-header-top-col, var(--pd-on-primary-col, #fff) ); } /* Top-menu colors when teaser is visible and not collapsed */ :host([show-teaser]:not([teaser-collapsed])) .top-menu-area { --pd-menu-bg-col: var(--pd-teaser-bg-col); } /* ═══════════════════════════════════════════════════════════ TEASER AREA ═══════════════════════════════════════════════════════════ */ .teaser-area { height: var(--pd-site-header-teaser-height, var(--_teaser-h)); overflow: hidden; transition: height var(--_transition); } .teaser-area ::slotted(*) { width: 100%; max-width: 100%; height: 100%; } /* Teaser collapsed */ :host([teaser-collapsed]) .teaser-area { height: 0; pointer-events: none; } /* Skip teaser transition during navigate-and-scroll */ :host([skip-teaser-transition]) .teaser-area { transition: none !important; } /* ── Content-driven teaser height (opt-in) ───────────── */ :host([teaser-fit-content]) .teaser-area { height: auto; display: grid; grid-template-rows: 1fr; transition: grid-template-rows var(--_transition); } :host([teaser-fit-content]) .teaser-area ::slotted(*) { height: auto; overflow: hidden; min-height: 0; } :host([teaser-fit-content][teaser-collapsed]) .teaser-area { grid-template-rows: 0fr; } /* ═══════════════════════════════════════════════════════════ MENU AREA ═══════════════════════════════════════════════════════════ */ .menu-area { --pd-menu-height: var(--pd-site-header-menu-height, var(--_menu-h)); transition: background-color var(--_transition); } /* Menu colors when teaser is visible and not collapsed */ :host([show-teaser]:not([teaser-collapsed])) .menu-area { --pd-menu-bg-col: var( --pd-site-header-menu-over-teaser-bg, var(--pd-spa-menu-change-col, #afc1d2) ); --pd-menu-font-col: var( --pd-site-header-menu-over-teaser-col, var(--pd-spa-menu-change-font-col, #0a3a48) ); } /* Content offset when top-menu is present — opt-in via CSS var */ .menu-area.with-top-menu { --pd-menu-content-offset-y: var(--pd-site-header-content-offset-y, 0); } /* ═══════════════════════════════════════════════════════════ LOGO (slotted into pd-menu via slot="slotLogo") The .logo <span> is a slot-forwarding wrapper — it sits in pd-site-header's shadow DOM but is slotted into pd-menu. pd-menu controls its box via ::slotted(.logo): - height: var(--pd-menu-logo-height, 60%) - max-width: var(--pd-menu-logo-maxwidth, 8rem) - padding, fill, cursor IMPORTANT: Do NOT set display, height, or width here — pd-site-header's own styles win over pd-menu's ::slotted() (shadow-DOM cascade: originating tree > ::slotted from outer tree). overflow:hidden is safe because it doesn't conflict. ═══════════════════════════════════════════════════════════ */ .logo[hidden] { display: none; } /* Non-replaced slotted content (e.g. <span> wrapping an inline SVG): display:contents makes it invisible in layout so the SVG inside participates directly in the wrapper's box. */ .logo ::slotted(*) { display: contents; } /* Replaced elements (img) lose their box with display:contents — restore them. height:100% fills the wrapper (pd-menu controls wrapper height via --pd-menu-logo-height). width:auto computes from intrinsic aspect ratio. No max-width — the wrapper's overflow:hidden clips excess width instead of shrinking the image (avoids circular max-width:100% dependency). */ .logo ::slotted(img) { display: block; height: 100%; width: auto; object-fit: contain; } /* ═══════════════════════════════════════════════════════════ BACKGROUND IMAGE (integrated mode — spans all areas) ═══════════════════════════════════════════════════════════ */ .header-bg-image { position: absolute; inset: 0; background-size: var(--pd-menu-bg-size, cover); background-repeat: var(--pd-menu-bg-repeat, no-repeat); background-attachment: var(--pd-menu-bg-attachment, scroll); background-position: var(--pd-menu-bg-position, center); z-index: 0; } .header-bg-gradient { position: absolute; inset: 0; background: var(--pd-menu-gradient, none); z-index: 0; } /* Content areas above the integrated background layer */ .over-bg-image { position: relative; z-index: 1; --pd-menu-bg-col: transparent; } ` ]; __decorateClass([ property({ type: Array }) ], PdSiteHeader.prototype, "menuItems", 2); __decorateClass([ property({ type: Array }) ], PdSiteHeader.prototype, "topMenuItems", 2); __decorateClass([ property({ type: String }) ], PdSiteHeader.prototype, "activeRoute", 2); __decorateClass([ property({ type: String }) ], PdSiteHeader.prototype, "logoRoute", 2); __decorateClass([ property({ type: String }) ], PdSiteHeader.prototype, "selectedLocale", 2); __decorateClass([ property({ type: Array }) ], PdSiteHeader.prototype, "locales", 2); __decorateClass([ property({ type: Boolean }) ], PdSiteHeader.prototype, "singleLocaleMenu", 2); __decorateClass([ property({ type: Boolean, reflect: true, attribute: "show-teaser" }) ], PdSiteHeader.prototype, "showTeaser", 2); __decorateClass([ property({ type: Boolean, reflect: true, attribute: "teaser-collapsed" }) ], PdSiteHeader.prototype, "teaserCollapsed", 2); __decorateClass([ property({ type: Boolean, attribute: "keep-teaser-logo" }) ], PdSiteHeader.prototype, "keepTeaserLogo", 2); __decorateClass([ property({ type: Boolean, reflect: true, attribute: "teaser-fit-content" }) ], PdSiteHeader.prototype, "teaserFitContent", 2); __decorateClass([ property({ type: Boolean, reflect: true, attribute: "skip-teaser-transition" }) ], PdSiteHeader.prototype, "skipTeaserTransition", 2); __decorateClass([ property({ type: String, reflect: true, attribute: "top-menu-mode" }) ], PdSiteHeader.prototype, "topMenuMode", 2); __decorateClass([ property({ type: String, reflect: true, attribute: "bg-image" }) ], PdSiteHeader.prototype, "bgImage", 2); PdSiteHeader = __decorateClass([ localized() ], PdSiteHeader); export { PdSiteHeader };