@progressive-development/pd-page
Version:
Progressive development page helper, teaser, menu, footer.
372 lines (341 loc) • 14.3 kB
JavaScript
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 ;
}
/* ── 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 };