import { html, nothing, type PropertyValues, type TemplateResult } from 'lit' import { property, state } from 'lit/decorators.js' import { ifDefined } from 'lit/directives/if-defined.js' import { deriveSocialIcon, fetchHeaderFooterData, getExternalDomainLabel, getLinkDomainLabel, selectLocaleData, } from 'shared-utils/header-footer' import { type Booleanish, booleanishConverter, type IHeaderFooter, type IHeaderMenuLink, type IHeaderMenuSection, type IPktFooterLink, type THeaderFooterApi, type THeaderMenuLocale, } from 'shared-types' import { PktElement } from '@/base-elements/element' import '@/components/consent' import '@/components/icon' const CONSENT_TRIGGER_URL = '#cb-trigger' /** * Shared base for `pkt-footer` and `pkt-footer-simple`. * * - Fetching and caching global footer data * - Rendering the global footer band (blue) * - Rendering the consent dialog (if `includeConsent` is set) */ export abstract class PktFooterBaseElement extends PktElement { @property({ type: String, attribute: 'data-url' }) dataUrl?: string @property({ type: Object, attribute: false }) data?: THeaderFooterApi @property({ type: String }) locale: THeaderMenuLocale = 'nb-NO' @property({ type: Boolean, attribute: 'skip-global', converter: booleanishConverter }) skipGlobal: Booleanish = false @property({ type: Boolean, attribute: 'open-links-in-new-tab', converter: booleanishConverter }) openLinksInNewTab: Booleanish = false @property({ type: Boolean, attribute: 'include-consent', converter: booleanishConverter }) includeConsent: Booleanish = false @property({ type: String, attribute: 'hotjar-id' }) hotjarId: string | null = null @property({ type: String, attribute: 'google-analytics-id' }) googleAnalyticsId: string | null = null @property({ type: Boolean, attribute: 'dev-mode', converter: booleanishConverter }) devMode: Booleanish = false @property({ type: String, attribute: 'cookie-domain' }) cookieDomain: string | null = null @property({ type: String, attribute: 'cookie-secure' }) cookieSecure: string | null = null @property({ type: String, attribute: 'cookie-expiry-days' }) cookieExpiryDays: string | null = null @state() private fetchedData?: THeaderFooterApi private abortController?: AbortController connectedCallback() { super.connectedCallback() if (!this.data && !this.skipGlobal) { void this.loadData() } } disconnectedCallback() { super.disconnectedCallback() this.abortController?.abort() } updated(changedProperties: PropertyValues) { super.updated(changedProperties) if ( changedProperties.has('dataUrl') && changedProperties.get('dataUrl') !== undefined && !this.data ) { void this.loadData() } } private async loadData() { this.abortController?.abort() this.abortController = new AbortController() try { this.fetchedData = await fetchHeaderFooterData( this.dataUrl, this.abortController.signal, ) } catch (error) { if ((error as Error).name === 'AbortError') return console.warn('Failed to fetch header/footer data:', error) this.dispatchEvent( new CustomEvent('data-error', { detail: { error }, bubbles: true, composed: true }), ) } } private get globalFooterData(): IHeaderFooter | undefined { return selectLocaleData(this.data ?? this.fetchedData, this.locale)?.footer } protected linkTarget(link: IPktFooterLink): { target: string; rel?: string } { const newTab = link.openInNewTab || this.openLinksInNewTab return newTab ? { target: '_blank', rel: 'noopener noreferrer' } : { target: '_self' } } /** "(topdomain.tld)" display for external links */ protected renderLinkDomain(link: IPktFooterLink, className = 'pkt-footer__link-domain') { if (!link.external) return nothing const domain = getLinkDomainLabel(link.href) if (!domain) return nothing return html` (${domain})` } protected renderCustomLink(link: IPktFooterLink): TemplateResult { const { target, rel } = this.linkTarget(link) return html` ${link.text}${this.renderLinkDomain(link)} ` } protected renderConsent(): TemplateResult | typeof nothing { if (!this.includeConsent) return nothing return html` ` } private renderGlobalSectionLink(link: IHeaderMenuLink): TemplateResult | typeof nothing { if (link.url === CONSENT_TRIGGER_URL) { // Punkt's own consent dialog replaces the CDN cookie-banner trigger. if (!this.includeConsent) return nothing return html`
  • ` } const domain = getExternalDomainLabel(link.url) return html`
  • ${link.text}${domain ? html` (${domain})` : nothing}
  • ` } private renderGlobalSection(section: IHeaderMenuSection): TemplateResult { return html`

    ${section.title}

    ` } private renderSomeLink(entry: IHeaderMenuLink): TemplateResult { const icon = deriveSocialIcon(entry.url, entry.text) if (!icon) { return html`
  • ${entry.text}
  • ` } return html`
  • ` } private renderGlobalBottom(footer: IHeaderFooter): TemplateResult | typeof nothing { const hasLinks = footer.links?.length > 0 const hasSome = footer.some?.length > 0 if (!hasLinks && !hasSome) return nothing return html` ` } protected renderGlobalFooter(): TemplateResult | typeof nothing { if (this.skipGlobal) return nothing const footer = this.globalFooterData if (!footer) return nothing return html` ` } }