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`
`
}
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`
`
}
}