@brandlab-digital/site-next 3.14.0 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics.d.ts +1 -1
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +2 -0
- package/dist/analytics.js.map +1 -1
- package/dist/navigation/constants.d.ts +11 -2
- package/dist/navigation/constants.d.ts.map +1 -1
- package/dist/navigation/constants.js +11 -2
- package/dist/navigation/constants.js.map +1 -1
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +1 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/use-back-to-top.d.ts.map +1 -1
- package/dist/navigation/use-back-to-top.js +12 -4
- package/dist/navigation/use-back-to-top.js.map +1 -1
- package/dist/navigation/use-header-metrics.d.ts +1 -1
- package/dist/navigation/use-header-metrics.js +1 -1
- package/dist/navigation/use-header-metrics.js.map +1 -1
- package/dist/navigation/use-sticky-call-bar.d.ts +2 -1
- package/dist/navigation/use-sticky-call-bar.d.ts.map +1 -1
- package/dist/navigation/use-sticky-call-bar.js +11 -2
- package/dist/navigation/use-sticky-call-bar.js.map +1 -1
- package/dist/ui-kit/brandlab-skin.d.ts +26 -0
- package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
- package/dist/ui-kit/brandlab-skin.js.map +1 -1
- package/dist/ui-kit/prefetch.d.ts +19 -10
- package/dist/ui-kit/prefetch.d.ts.map +1 -1
- package/dist/ui-kit/prefetch.js +23 -10
- package/dist/ui-kit/prefetch.js.map +1 -1
- package/dist/ui-kit/primitives/index.d.ts +1 -1
- package/dist/ui-kit/primitives/index.d.ts.map +1 -1
- package/dist/ui-kit/primitives/index.js.map +1 -1
- package/dist/ui-kit/primitives/links.js +1 -1
- package/dist/ui-kit/primitives/links.js.map +1 -1
- package/dist/ui-kit/primitives/lists.d.ts +9 -1
- package/dist/ui-kit/primitives/lists.d.ts.map +1 -1
- package/dist/ui-kit/primitives/lists.js +1 -1
- package/dist/ui-kit/primitives/lists.js.map +1 -1
- package/dist/ui-kit/primitives/quote-button.d.ts +25 -2
- package/dist/ui-kit/primitives/quote-button.d.ts.map +1 -1
- package/dist/ui-kit/primitives/quote-button.js +25 -3
- package/dist/ui-kit/primitives/quote-button.js.map +1 -1
- package/dist/ui-kit/repeated-slots.d.ts +32 -0
- package/dist/ui-kit/repeated-slots.d.ts.map +1 -1
- package/dist/ui-kit/repeated-slots.js +16 -0
- package/dist/ui-kit/repeated-slots.js.map +1 -1
- package/dist/ui-kit/sections/area-map.d.ts +64 -0
- package/dist/ui-kit/sections/area-map.d.ts.map +1 -0
- package/dist/ui-kit/sections/area-map.js +142 -0
- package/dist/ui-kit/sections/area-map.js.map +1 -0
- package/dist/ui-kit/sections/before-after.d.ts +59 -0
- package/dist/ui-kit/sections/before-after.d.ts.map +1 -0
- package/dist/ui-kit/sections/before-after.js +42 -0
- package/dist/ui-kit/sections/before-after.js.map +1 -0
- package/dist/ui-kit/sections/council-rules.d.ts +64 -0
- package/dist/ui-kit/sections/council-rules.d.ts.map +1 -0
- package/dist/ui-kit/sections/council-rules.js +46 -0
- package/dist/ui-kit/sections/council-rules.js.map +1 -0
- package/dist/ui-kit/sections/coverage.d.ts +53 -0
- package/dist/ui-kit/sections/coverage.d.ts.map +1 -0
- package/dist/ui-kit/sections/coverage.js +57 -0
- package/dist/ui-kit/sections/coverage.js.map +1 -0
- package/dist/ui-kit/sections/emergency-band.d.ts +60 -0
- package/dist/ui-kit/sections/emergency-band.d.ts.map +1 -0
- package/dist/ui-kit/sections/emergency-band.js +43 -0
- package/dist/ui-kit/sections/emergency-band.js.map +1 -0
- package/dist/ui-kit/sections/gallery.d.ts +60 -0
- package/dist/ui-kit/sections/gallery.d.ts.map +1 -0
- package/dist/ui-kit/sections/gallery.js +39 -0
- package/dist/ui-kit/sections/gallery.js.map +1 -0
- package/dist/ui-kit/sections/hero.d.ts +5 -0
- package/dist/ui-kit/sections/hero.d.ts.map +1 -1
- package/dist/ui-kit/sections/hero.js +1 -1
- package/dist/ui-kit/sections/hero.js.map +1 -1
- package/dist/ui-kit/sections/index.d.ts +14 -1
- package/dist/ui-kit/sections/index.d.ts.map +1 -1
- package/dist/ui-kit/sections/index.js +14 -1
- package/dist/ui-kit/sections/index.js.map +1 -1
- package/dist/ui-kit/sections/licences.d.ts +72 -0
- package/dist/ui-kit/sections/licences.d.ts.map +1 -0
- package/dist/ui-kit/sections/licences.js +46 -0
- package/dist/ui-kit/sections/licences.js.map +1 -0
- package/dist/ui-kit/sections/local-facts.d.ts +78 -0
- package/dist/ui-kit/sections/local-facts.d.ts.map +1 -0
- package/dist/ui-kit/sections/local-facts.js +32 -0
- package/dist/ui-kit/sections/local-facts.js.map +1 -0
- package/dist/ui-kit/sections/nearby-areas.d.ts +50 -0
- package/dist/ui-kit/sections/nearby-areas.d.ts.map +1 -0
- package/dist/ui-kit/sections/nearby-areas.js +49 -0
- package/dist/ui-kit/sections/nearby-areas.js.map +1 -0
- package/dist/ui-kit/sections/nearby-jobs.d.ts +66 -0
- package/dist/ui-kit/sections/nearby-jobs.d.ts.map +1 -0
- package/dist/ui-kit/sections/nearby-jobs.js +49 -0
- package/dist/ui-kit/sections/nearby-jobs.js.map +1 -0
- package/dist/ui-kit/sections/projects.d.ts +79 -0
- package/dist/ui-kit/sections/projects.d.ts.map +1 -0
- package/dist/ui-kit/sections/projects.js +69 -0
- package/dist/ui-kit/sections/projects.js.map +1 -0
- package/dist/ui-kit/sections/quote-checklist.d.ts +43 -0
- package/dist/ui-kit/sections/quote-checklist.d.ts.map +1 -0
- package/dist/ui-kit/sections/quote-checklist.js +19 -0
- package/dist/ui-kit/sections/quote-checklist.js.map +1 -0
- package/dist/ui-kit/sections/related-services.d.ts +41 -0
- package/dist/ui-kit/sections/related-services.d.ts.map +1 -0
- package/dist/ui-kit/sections/related-services.js +52 -0
- package/dist/ui-kit/sections/related-services.js.map +1 -0
- package/dist/ui-kit/sections/reviews-band.d.ts +11 -0
- package/dist/ui-kit/sections/reviews-band.d.ts.map +1 -1
- package/dist/ui-kit/sections/reviews-band.js +11 -0
- package/dist/ui-kit/sections/reviews-band.js.map +1 -1
- package/dist/ui-kit/sections/services-grid.js +1 -1
- package/dist/ui-kit/sections/services-grid.js.map +1 -1
- package/dist/ui-kit/sections/work-photos.d.ts +15 -0
- package/dist/ui-kit/sections/work-photos.d.ts.map +1 -0
- package/dist/ui-kit/sections/work-photos.js +24 -0
- package/dist/ui-kit/sections/work-photos.js.map +1 -0
- package/dist/ui-kit/shell/back-to-top.js +1 -1
- package/dist/ui-kit/shell/back-to-top.js.map +1 -1
- package/dist/ui-kit/shell/current-link.d.ts +21 -5
- package/dist/ui-kit/shell/current-link.d.ts.map +1 -1
- package/dist/ui-kit/shell/current-link.js +16 -5
- package/dist/ui-kit/shell/current-link.js.map +1 -1
- package/dist/ui-kit/shell/drawer-client.js +1 -1
- package/dist/ui-kit/shell/drawer-client.js.map +1 -1
- package/dist/ui-kit/shell/intent-prefetch.d.ts.map +1 -1
- package/dist/ui-kit/shell/intent-prefetch.js +3 -2
- package/dist/ui-kit/shell/intent-prefetch.js.map +1 -1
- package/dist/ui-kit/shell/mobile-call-bar.d.ts +6 -1
- package/dist/ui-kit/shell/mobile-call-bar.d.ts.map +1 -1
- package/dist/ui-kit/shell/mobile-call-bar.js +6 -3
- package/dist/ui-kit/shell/mobile-call-bar.js.map +1 -1
- package/dist/ui-kit/shell/mobile-drawer.js +2 -2
- package/dist/ui-kit/shell/mobile-drawer.js.map +1 -1
- package/dist/ui-kit/shell/shell-quote.d.ts +10 -0
- package/dist/ui-kit/shell/shell-quote.d.ts.map +1 -0
- package/dist/ui-kit/shell/shell-quote.js +13 -0
- package/dist/ui-kit/shell/shell-quote.js.map +1 -0
- package/dist/ui-kit/shell/site-header.d.ts +1 -1
- package/dist/ui-kit/shell/site-header.js +4 -4
- package/dist/ui-kit/shell/site-header.js.map +1 -1
- package/dist/ui-kit/structure.css +410 -7
- package/package.json +2 -2
package/dist/analytics.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface SafeContactAnalyticsParams {
|
|
|
31
31
|
service_slug?: string;
|
|
32
32
|
location_slug?: string;
|
|
33
33
|
}
|
|
34
|
-
export declare const PHONE_CALL_PLACEMENTS: readonly ["header", "footer", "hero", "cta", "fab", "contact", "location", "page"];
|
|
34
|
+
export declare const PHONE_CALL_PLACEMENTS: readonly ["header", "footer", "hero", "cta", "fab", "contact", "location", "page", "emergency"];
|
|
35
35
|
export type PhoneCallPlacement = (typeof PHONE_CALL_PLACEMENTS)[number];
|
|
36
36
|
export declare function trackPhoneLinkClick(input: {
|
|
37
37
|
pagePath: string;
|
package/dist/analytics.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EACnB,MAAM,uCAAuC,CAAA;AAE9C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAA;AACnE,OAAO,EACL,cAAc,EAKf,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,cAAc,EAAE,CAAA;AAEzB,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,0EAA0E;IAC1E,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxC,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,oCAAoC;IACnD,iFAAiF;IACjF,qBAAqB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChD,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACxC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACjD,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACpD,mCAAmC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAChE;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
package/dist/analytics.js
CHANGED
|
@@ -11,6 +11,8 @@ export const PHONE_CALL_PLACEMENTS = [
|
|
|
11
11
|
'contact',
|
|
12
12
|
'location',
|
|
13
13
|
'page',
|
|
14
|
+
/** The after-hours `EmergencyBand` (3.15.0). */
|
|
15
|
+
'emergency',
|
|
14
16
|
];
|
|
15
17
|
export function trackPhoneLinkClick(input, config) {
|
|
16
18
|
if (!PHONE_CALL_PLACEMENTS.includes(input.placement))
|
package/dist/analytics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n defineAnalyticsContract,\n sendBrandlabAnalyticsEvent,\n type BrandlabAnalyticsContract,\n type GtagTransport,\n} from '@brandlab-digital/site-core/analytics'\nimport { resolveBrandlabDeployment } from '@brandlab-digital/site-core/site'\nimport type { BrandlabGoogleTagConfig } from './google-tag-plan.js'\nimport {\n getBrowserGtag,\n registerLeadBinding,\n safePagePath,\n safeSlug,\n type LeadAdsParams,\n} from './lead-registry.js'\n\nexport { getBrowserGtag }\n\nexport interface BrandlabBrowserAnalyticsConfig {\n enabled: boolean\n leadDiagnosticsEnabled?: boolean\n enhancedConversionsEnabled?: boolean\n /** Disclosed Enhanced Conversions: ad_user_data is granted by default. */\n adUserDataGrantedByDefault?: boolean\n measurementId?: string | null\n googleAdsConversionId?: string | null\n googleAdsConversionLabel?: string | null\n gtag?: GtagTransport\n}\n\n/**\n * Public analytics values that a client bundle must bind through direct,\n * statically analysable `process.env.NEXT_PUBLIC_*` property access. Ads keys\n * are bound only when the contract declares Google Ads (and website calls).\n */\nexport interface BrandlabBrowserAnalyticsPublicValues {\n /** `process.env.NEXT_PUBLIC_VERCEL_ENV`, inlined by defineBrandlabNextConfig. */\n deploymentEnvironment: string | null | undefined\n measurementId: string | null | undefined\n googleAdsConversionId?: string | null | undefined\n googleAdsConversionLabel?: string | null | undefined\n googleAdsWebsiteCallConversionLabel?: string | null | undefined\n}\n\nexport interface SafeContactAnalyticsParams {\n page_template?: string\n service_slug?: string\n location_slug?: string\n}\n\nexport const PHONE_CALL_PLACEMENTS = [\n 'header',\n 'footer',\n 'hero',\n 'cta',\n 'fab',\n 'contact',\n 'location',\n 'page',\n] as const\n\nexport type PhoneCallPlacement = (typeof PHONE_CALL_PLACEMENTS)[number]\n\nexport function trackPhoneLinkClick(\n input: { pagePath: string; placement: PhoneCallPlacement },\n config: Pick<BrandlabBrowserAnalyticsConfig, 'enabled' | 'measurementId' | 'gtag'>,\n) {\n if (!PHONE_CALL_PLACEMENTS.includes(input.placement)) return false\n return sendBrandlabAnalyticsEvent({\n ...config,\n gtag: config.gtag ?? getBrowserGtag(),\n eventName: 'phone_link_click',\n params: {\n event_category: 'contact_interaction',\n page_path: safePagePath(input.pagePath),\n placement: input.placement,\n },\n })\n}\n\nexport const LEAD_FORM_ERROR_STAGES = [\n 'validation',\n 'submission',\n 'delivery',\n 'unknown',\n] as const\nexport type LeadFormErrorStage = (typeof LEAD_FORM_ERROR_STAGES)[number]\n\n/**\n * Why a contact attempt failed, sent as lead_form_error's error_reason:\n * - `field`: the server rejected one or more fields (error_field names the first);\n * the visitor sees the field message and can correct it.\n * - `request`: the server refused the request itself with no field to correct\n * (origin, form shape, or a submission time outside the accepted window).\n * - `attachment`: the photos could not be attached.\n * - `delivery`: the server could not deliver the enquiry.\n * - `network`: the browser could not complete the request or read the answer\n * (offline, blocked, timed out, or a platform error page instead of JSON).\n */\nexport const LEAD_FORM_ERROR_REASONS = [\n 'field',\n 'request',\n 'attachment',\n 'delivery',\n 'network',\n] as const\nexport type LeadFormErrorReason = (typeof LEAD_FORM_ERROR_REASONS)[number]\n\nexport interface LeadFormErrorInput {\n pagePath: string\n formId: string\n stage: LeadFormErrorStage\n errorCount: number\n /** Optional failure reason; an unknown value drops the event. */\n reason?: LeadFormErrorReason\n /** The first contract field key the server rejected; sent only with reason `field`. Never a value. */\n field?: string\n}\n\nexport const LEAD_CTA_PLACEMENTS = [\n 'header',\n 'footer',\n 'hero',\n 'section',\n 'navigation',\n 'floating',\n 'page',\n] as const\nexport type LeadCtaPlacement = (typeof LEAD_CTA_PLACEMENTS)[number]\n\ntype LeadDiagnosticConfig = Pick<\n BrandlabBrowserAnalyticsConfig,\n 'enabled' | 'leadDiagnosticsEnabled' | 'measurementId' | 'gtag'\n>\n\nexport function trackLeadFormStart(\n input: { pagePath: string; formId: string; pageTemplate?: string },\n config: LeadDiagnosticConfig,\n) {\n const formId = safeSlug(input.formId)\n if (!formId) return false\n return sendLeadDiagnosticEvent('lead_form_start', {\n form_id: formId,\n page_path: safePagePath(input.pagePath),\n ...(safeSlug(input.pageTemplate) ? { page_template: safeSlug(input.pageTemplate) } : {}),\n }, config)\n}\n\nexport function trackLeadFormError(\n input: LeadFormErrorInput,\n config: LeadDiagnosticConfig,\n) {\n const formId = safeSlug(input.formId)\n if (\n !formId ||\n !LEAD_FORM_ERROR_STAGES.includes(input.stage) ||\n !Number.isInteger(input.errorCount) ||\n input.errorCount < 1 ||\n input.errorCount > 50 ||\n (input.reason !== undefined && !LEAD_FORM_ERROR_REASONS.includes(input.reason))\n ) {\n return false\n }\n const field = input.reason === 'field' ? safeSlug(input.field) : undefined\n return sendLeadDiagnosticEvent('lead_form_error', {\n form_id: formId,\n page_path: safePagePath(input.pagePath),\n error_stage: input.stage,\n error_count: input.errorCount,\n ...(input.reason ? { error_reason: input.reason } : {}),\n ...(field ? { error_field: field } : {}),\n }, config)\n}\n\nexport function trackLeadCtaClick(\n input: {\n pagePath: string\n ctaId: string\n placement: LeadCtaPlacement\n },\n config: LeadDiagnosticConfig,\n) {\n const ctaId = safeSlug(input.ctaId)\n if (!ctaId || !LEAD_CTA_PLACEMENTS.includes(input.placement)) return false\n return sendLeadDiagnosticEvent('lead_cta_click', {\n cta_id: ctaId,\n page_path: safePagePath(input.pagePath),\n placement: input.placement,\n }, config)\n}\n\nfunction sendLeadDiagnosticEvent(\n eventName: 'lead_form_start' | 'lead_form_error' | 'lead_cta_click',\n params: Record<string, unknown>,\n config: LeadDiagnosticConfig,\n) {\n return sendBrandlabAnalyticsEvent({\n enabled: config.enabled && config.leadDiagnosticsEnabled === true,\n measurementId: config.measurementId,\n gtag: config.gtag ?? getBrowserGtag(),\n eventName,\n params,\n })\n}\n\nfunction safeAdditionalEventValue(\n value: unknown,\n policy: NonNullable<BrandlabAnalyticsContract['additionalEvents']>[number]['parameters'][number],\n) {\n if (policy.type === 'boolean') return typeof value === 'boolean' ? value : undefined\n if (policy.type === 'count') {\n const minimum = policy.minimum ?? 0\n const maximum = policy.maximum ?? 100_000\n return typeof value === 'number' && Number.isInteger(value) && value >= minimum && value <= maximum\n ? value\n : undefined\n }\n if (typeof value !== 'string') return undefined\n const normalized = value.trim()\n if (policy.type === 'enum') return policy.values.includes(normalized) ? normalized : undefined\n if (!/^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/.test(normalized) || normalized.length > 80) {\n return undefined\n }\n const digits = normalized.replace(/\\D/g, '')\n return digits.length >= 4 || /^[a-f0-9]{24,}$/.test(normalized)\n ? undefined\n : normalized\n}\n\nexport function trackBrandlabBrowserEvent(\n eventName: string,\n params: Record<string, unknown>,\n config: Pick<BrandlabBrowserAnalyticsConfig, 'enabled' | 'measurementId' | 'gtag'> & {\n contract: BrandlabAnalyticsContract\n },\n) {\n defineAnalyticsContract(config.contract)\n const event = config.contract.additionalEvents?.find(\n (candidate) => candidate.name === eventName,\n )\n if (!event) return false\n const safeParams = Object.fromEntries(\n Object.entries(params).flatMap(([key, value]) => {\n const policy = event.parameters.find((candidate) => candidate.key === key)\n if (!policy) return []\n const safeValue = safeAdditionalEventValue(value, policy)\n return safeValue === undefined ? [] : [[key, safeValue]]\n }),\n )\n return sendBrandlabAnalyticsEvent({\n ...config,\n gtag: config.gtag ?? getBrowserGtag(),\n eventName,\n params: safeParams,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Project binding: src/config/analytics.ts\n// ---------------------------------------------------------------------------\n\ntype PageDimension = 'service_slug' | 'location_slug'\n\nconst PAGE_DIMENSIONS: readonly PageDimension[] = ['service_slug', 'location_slug']\nconst MAX_PAGE_TEMPLATE_ROWS = 5_000\n\n/**\n * One row of the page-template table. Rows are tried in order against the\n * page path's segments (query, hash, empty and trailing segments ignored) and\n * the first match wins; no match reports no page dimensions.\n *\n * `path` segments: a literal matches itself; `:name` matches any one segment,\n * and `:service_slug` / `:location_slug` also report it (lowercased, or\n * omitted when it is not a safe slug, without failing the row); `**` matches\n * zero or more segments and may appear once, anywhere.\n */\nexport interface BrandlabAnalyticsPageTemplate {\n path: string\n template: string\n /** Constant dimensions for every page the row matches. */\n params?: Readonly<Partial<Record<PageDimension, string>>>\n /**\n * Constraints on `:name` segments, tested against the segment exactly as it\n * appears in the path: a list of allowed values or a regular expression. A\n * segment that fails its constraint skips the row.\n */\n values?: Readonly<Record<string, readonly string[] | RegExp>>\n}\n\ntype PageTemplateSegment =\n | { kind: 'literal'; value: string }\n | {\n kind: 'parameter'\n name: string\n dimension?: PageDimension\n allowed?: ReadonlySet<string> | RegExp\n }\n\ninterface CompiledPageTemplate {\n head: readonly PageTemplateSegment[]\n /** Segments after `**`; undefined when the row has no `**`. */\n tail?: readonly PageTemplateSegment[]\n template: string\n params: SafeContactAnalyticsParams\n}\n\nfunction compilePageTemplate(row: BrandlabAnalyticsPageTemplate, index: number): CompiledPageTemplate {\n const field = `pageTemplates[${index}]`\n if (!row || typeof row !== 'object') throw new Error(`${field} must be a page-template row.`)\n const unsupported = Object.keys(row).filter((key) => !['path', 'template', 'params', 'values'].includes(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.`)\n }\n if (typeof row.path !== 'string' || !row.path.startsWith('/') || row.path.includes('//')) {\n throw new Error(`${field}.path must be a root-relative pattern.`)\n }\n const template = safeSlug(row.template)\n if (!template || template !== row.template) {\n throw new Error(`${field}.template must be a lowercase slug.`)\n }\n const parts = row.path === '/' ? [] : row.path.replace(/\\/$/, '').slice(1).split('/')\n if (parts.filter((part) => part === '**').length > 1) {\n throw new Error(`${field}.path may use ** only once.`)\n }\n const names = new Set<string>()\n const segments = parts.map((part): PageTemplateSegment | '**' => {\n if (part === '**') return '**'\n if (part.startsWith(':')) {\n const name = part.slice(1)\n if (!/^[a-z_][a-z0-9_]*$/.test(name)) throw new Error(`${field}.path has an invalid parameter \"${part}\".`)\n if (names.has(name)) throw new Error(`${field}.path repeats ${part}.`)\n names.add(name)\n return PAGE_DIMENSIONS.includes(name as PageDimension)\n ? { kind: 'parameter', name, dimension: name as PageDimension }\n : { kind: 'parameter', name }\n }\n if (!/^[A-Za-z0-9._~-]+$/.test(part)) throw new Error(`${field}.path has an invalid segment \"${part}\".`)\n return { kind: 'literal', value: part }\n })\n const params: SafeContactAnalyticsParams = {}\n for (const [key, value] of Object.entries(row.params ?? {})) {\n const dimension = key as PageDimension\n if (!PAGE_DIMENSIONS.includes(dimension) || names.has(dimension)) {\n throw new Error(`${field}.params.${key} is not an uncaptured page dimension.`)\n }\n if (typeof value !== 'string' || safeSlug(value) !== value) {\n throw new Error(`${field}.params.${key} must be a lowercase slug.`)\n }\n params[dimension] = value\n }\n for (const [name, allowed] of Object.entries(row.values ?? {})) {\n if (!names.has(name)) throw new Error(`${field}.values.${name} needs :${name} in the path.`)\n let constraint: ReadonlySet<string> | RegExp\n if (allowed instanceof RegExp) {\n if (allowed.global || allowed.sticky) {\n throw new Error(`${field}.values.${name} must be a regular expression without the g or y flag.`)\n }\n constraint = allowed\n } else if (Array.isArray(allowed) && allowed.every((value) => typeof value === 'string' && value.length > 0)) {\n constraint = new Set(allowed)\n } else {\n throw new Error(`${field}.values.${name} must list segment values or be a regular expression.`)\n }\n for (const segment of segments) {\n if (segment !== '**' && segment.kind === 'parameter' && segment.name === name) segment.allowed = constraint\n }\n }\n const rest = segments.indexOf('**')\n const compiled = segments.filter((segment): segment is PageTemplateSegment => segment !== '**')\n return {\n head: rest === -1 ? compiled : compiled.slice(0, rest),\n ...(rest === -1 ? {} : { tail: compiled.slice(rest) }),\n template,\n params,\n }\n}\n\nfunction pathSegments(pagePath: string) {\n return String(pagePath).split(/[?#]/, 1)[0]!.split('/').filter(Boolean)\n}\n\nfunction matchSegment(\n segment: PageTemplateSegment,\n part: string,\n params: SafeContactAnalyticsParams,\n) {\n if (segment.kind === 'literal') return segment.value === part\n const { allowed } = segment\n if (allowed && !(allowed instanceof RegExp ? allowed.test(part) : allowed.has(part))) return false\n if (segment.dimension) {\n const value = safeSlug(part)\n if (value) params[segment.dimension] = value\n }\n return true\n}\n\nfunction matchPageTemplate(row: CompiledPageTemplate, parts: readonly string[]) {\n const fixed = row.head.length + (row.tail?.length ?? 0)\n if (row.tail ? parts.length < fixed : parts.length !== fixed) return undefined\n const params: SafeContactAnalyticsParams = { page_template: row.template, ...row.params }\n for (let index = 0; index < row.head.length; index += 1) {\n if (!matchSegment(row.head[index]!, parts[index]!, params)) return undefined\n }\n const offset = parts.length - (row.tail?.length ?? 0)\n for (let index = 0; index < (row.tail?.length ?? 0); index += 1) {\n if (!matchSegment(row.tail![index]!, parts[offset + index]!, params)) return undefined\n }\n return params\n}\n\n/** What a declared lead value sees for one accepted enquiry. */\nexport interface BrandlabLeadContext {\n /** The form's page path, as useBrandlabContactForm received it. */\n readonly pagePath: string\n /** The page's contact_form dimensions from the page-template table. */\n readonly pageParams: SafeContactAnalyticsParams\n /** The accepted enquiry's fields; they are never sent to analytics. */\n readonly formData: FormData\n}\n\nexport interface BrandlabGoogleAdsLeadConversion {\n /**\n * Conversion value: a number, or a function evaluated for each accepted\n * enquiry. A function that throws or returns anything but a finite,\n * non-negative number sends the conversion without a value.\n */\n value?: number | ((lead: BrandlabLeadContext) => number | undefined)\n /** ISO 4217 currency code, for example `AUD`. */\n currency?: string\n}\n\n/**\n * How the site reports an accepted enquiry, declared once. Every accepted\n * enquiry sends one GA4 `contact_form` (page dimensions from the page-template\n * table) and, when the contract declares Google Ads, one form conversion with\n * `send_to` and `transaction_id` plus the value and currency declared here.\n */\nexport interface BrandlabLeadReporting {\n googleAdsConversion?: BrandlabGoogleAdsLeadConversion\n}\n\nfunction compileLeadReporting(\n contract: BrandlabAnalyticsContract,\n leads: BrandlabLeadReporting | undefined,\n): (lead: BrandlabLeadContext) => LeadAdsParams {\n if (leads === undefined) return () => ({})\n if (!leads || typeof leads !== 'object' || Array.isArray(leads)) {\n throw new Error('leads must be a lead-reporting declaration.')\n }\n const unsupported = Object.keys(leads).filter((key) => key !== 'googleAdsConversion')\n if (unsupported.length > 0) throw new Error(`leads contains unsupported keys: ${unsupported.join(', ')}.`)\n const conversion = leads.googleAdsConversion\n if (conversion === undefined) return () => ({})\n if (!conversion || typeof conversion !== 'object' || Array.isArray(conversion)) {\n throw new Error('leads.googleAdsConversion must declare value and/or currency.')\n }\n if (!contract.googleAds) {\n throw new Error('leads.googleAdsConversion needs googleAds in the analytics contract.')\n }\n const unsupportedConversion = Object.keys(conversion).filter((key) => key !== 'value' && key !== 'currency')\n if (unsupportedConversion.length > 0) {\n throw new Error(`leads.googleAdsConversion contains unsupported keys: ${unsupportedConversion.join(', ')}.`)\n }\n const { value, currency } = conversion\n if (currency !== undefined && (typeof currency !== 'string' || !/^[A-Z]{3}$/.test(currency))) {\n throw new Error('leads.googleAdsConversion.currency must be an ISO 4217 code such as AUD.')\n }\n if (\n value !== undefined &&\n typeof value !== 'function' &&\n (typeof value !== 'number' || !Number.isFinite(value) || value < 0)\n ) {\n throw new Error('leads.googleAdsConversion.value must be a non-negative number or a function.')\n }\n return (lead) => {\n let resolved: unknown = value\n if (typeof value === 'function') {\n try {\n resolved = value(lead)\n } catch {\n resolved = undefined\n }\n }\n return {\n ...(currency ? { currency } : {}),\n ...(typeof resolved === 'number' ? { value: resolved } : {}),\n }\n }\n}\n\nexport interface DefineBrandlabAnalyticsOptions {\n contract: BrandlabAnalyticsContract\n publicValues: BrandlabBrowserAnalyticsPublicValues\n /** Classifies page paths for contact_form and lead_form_start dimensions. */\n pageTemplates: readonly BrandlabAnalyticsPageTemplate[]\n /** The Google Ads form-conversion value and currency, if the site sends them. */\n leads?: BrandlabLeadReporting\n}\n\nexport interface BrandlabAnalytics {\n readonly contract: BrandlabAnalyticsContract\n /** True only on the Vercel production deployment. */\n readonly enabled: boolean\n /** Props for the single `<BrandlabGoogleTag {...analytics.googleTag} />` in the root layout. */\n readonly googleTag: BrandlabGoogleTagConfig\n /** Tracking configuration; the page's gtag function is resolved when an event is sent. */\n readonly config: BrandlabBrowserAnalyticsConfig\n /** Privacy-safe page_template, service_slug and location_slug for a path. */\n pageParams(pagePath: string): SafeContactAnalyticsParams\n trackPhoneLinkClick(input: { pagePath: string; placement: PhoneCallPlacement }): boolean\n trackLeadFormStart(input: { pagePath: string; formId: string }): boolean\n trackLeadFormError(input: LeadFormErrorInput): boolean\n trackLeadCtaClick(input: { pagePath: string; ctaId: string; placement: LeadCtaPlacement }): boolean\n /** Sends an event declared in the contract's additionalEvents, filtered by its parameter policy. */\n trackEvent(eventName: string, params: Record<string, unknown>): boolean\n}\n\nconst PUBLIC_VALUE_KEYS = [\n 'deploymentEnvironment',\n 'measurementId',\n 'googleAdsConversionId',\n 'googleAdsConversionLabel',\n 'googleAdsWebsiteCallConversionLabel',\n] as const\n\nfunction assertPublicValues(\n contract: BrandlabAnalyticsContract,\n publicValues: BrandlabBrowserAnalyticsPublicValues,\n) {\n if (!publicValues || typeof publicValues !== 'object' || Array.isArray(publicValues)) {\n throw new Error('publicValues must bind the NEXT_PUBLIC_* analytics values.')\n }\n const required = new Set<string>(['deploymentEnvironment', 'measurementId'])\n if (contract.googleAds) {\n required.add('googleAdsConversionId')\n required.add('googleAdsConversionLabel')\n if (contract.googleAds.websiteCalls) required.add('googleAdsWebsiteCallConversionLabel')\n }\n for (const key of Object.keys(publicValues)) {\n if (!(PUBLIC_VALUE_KEYS as readonly string[]).includes(key)) {\n throw new Error(`publicValues contains unsupported key \"${key}\".`)\n }\n if (!required.has(key)) {\n throw new Error(`publicValues.${key} is bound but the analytics contract does not declare it.`)\n }\n }\n for (const key of required) {\n if (!Object.hasOwn(publicValues, key)) {\n throw new Error(`publicValues.${key} must be bound with literal process.env property access.`)\n }\n const value = (publicValues as unknown as Record<string, unknown>)[key]\n if (value !== undefined && value !== null && typeof value !== 'string') {\n throw new Error(`publicValues.${key} must be a string or undefined.`)\n }\n }\n const browserEnvKeys = [\n contract.measurementIdEnv,\n contract.googleAds?.conversionIdEnv,\n contract.googleAds?.contactConversionLabelEnv,\n contract.googleAds?.websiteCalls?.conversionLabelEnv,\n ].filter((key): key is string => Boolean(key))\n const privateKey = browserEnvKeys.find((key) => !key.startsWith('NEXT_PUBLIC_'))\n if (privateKey) {\n throw new Error(`${privateKey} is read in the browser and must be a NEXT_PUBLIC_ variable.`)\n }\n}\n\nfunction publicValue(value: string | null | undefined) {\n const trimmed = value?.trim()\n return trimmed || undefined\n}\n\n/**\n * The project's single analytics binding (`src/config/analytics.ts`). It is\n * safe in server and browser code: the root layout renders\n * `analytics.googleTag`, `src/instrumentation-client.ts` installs the phone\n * listener, and `useBrandlabContactForm` reports each accepted enquiry exactly\n * as `pageTemplates` and `leads` declare, plus the lead diagnostics. Analytics\n * runs only on the Vercel production deployment.\n */\nexport function defineBrandlabAnalytics(options: DefineBrandlabAnalyticsOptions): BrandlabAnalytics {\n if (!options || typeof options !== 'object') {\n throw new TypeError('defineBrandlabAnalytics requires contract, publicValues and pageTemplates.')\n }\n const contract = defineAnalyticsContract(options.contract)\n assertPublicValues(contract, options.publicValues)\n if (!Array.isArray(options.pageTemplates) || options.pageTemplates.length > MAX_PAGE_TEMPLATE_ROWS) {\n throw new Error(`pageTemplates must list at most ${MAX_PAGE_TEMPLATE_ROWS} rows.`)\n }\n const pageTemplates = options.pageTemplates.map(compilePageTemplate)\n const leadAdsParams = compileLeadReporting(contract, options.leads)\n const values = options.publicValues\n const enabled =\n resolveBrandlabDeployment({ NEXT_PUBLIC_VERCEL_ENV: publicValue(values.deploymentEnvironment) })\n .environment === 'production'\n const measurementId = publicValue(values.measurementId)\n const googleAdsConversionId = contract.googleAds ? publicValue(values.googleAdsConversionId) : undefined\n const enhancedConversionsMode = contract.googleAds?.enhancedConversions?.mode\n const adUserDataGrantedByDefault = enhancedConversionsMode === 'disclosed'\n const config: BrandlabBrowserAnalyticsConfig = Object.freeze({\n enabled,\n // Privacy-safe lead_form_start / lead_form_error diagnostics are on for every site.\n leadDiagnosticsEnabled: true,\n enhancedConversionsEnabled:\n enhancedConversionsMode === 'consent-required' || enhancedConversionsMode === 'disclosed',\n adUserDataGrantedByDefault,\n measurementId,\n googleAdsConversionId,\n googleAdsConversionLabel: contract.googleAds\n ? publicValue(values.googleAdsConversionLabel)\n : undefined,\n })\n const googleTag: BrandlabGoogleTagConfig = Object.freeze({\n enabled,\n measurementId,\n googleAdsConversionId,\n googleAdsWebsiteCallConversionLabel: contract.googleAds?.websiteCalls\n ? publicValue(values.googleAdsWebsiteCallConversionLabel)\n : undefined,\n adUserData: adUserDataGrantedByDefault ? 'granted' : 'denied',\n })\n const pageParams = (pagePath: string): SafeContactAnalyticsParams => {\n const parts = pathSegments(pagePath)\n for (const row of pageTemplates) {\n const params = matchPageTemplate(row, parts)\n if (params) return params\n }\n return {}\n }\n\n const binding: BrandlabAnalytics = Object.freeze({\n contract,\n enabled,\n googleTag,\n config,\n pageParams,\n trackPhoneLinkClick: (input: { pagePath: string; placement: PhoneCallPlacement }) =>\n trackPhoneLinkClick(input, config),\n trackLeadFormStart: (input: { pagePath: string; formId: string }) =>\n trackLeadFormStart({ ...input, pageTemplate: pageParams(input.pagePath).page_template }, config),\n trackLeadFormError: (input: LeadFormErrorInput) => trackLeadFormError(input, config),\n trackLeadCtaClick: (input: { pagePath: string; ctaId: string; placement: LeadCtaPlacement }) =>\n trackLeadCtaClick(input, config),\n trackEvent: (eventName: string, params: Record<string, unknown>) =>\n trackBrandlabBrowserEvent(eventName, params, { ...config, contract }),\n })\n registerLeadBinding(binding, { config, pageParams, adsParams: leadAdsParams })\n return binding\n}\n"]}
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n defineAnalyticsContract,\n sendBrandlabAnalyticsEvent,\n type BrandlabAnalyticsContract,\n type GtagTransport,\n} from '@brandlab-digital/site-core/analytics'\nimport { resolveBrandlabDeployment } from '@brandlab-digital/site-core/site'\nimport type { BrandlabGoogleTagConfig } from './google-tag-plan.js'\nimport {\n getBrowserGtag,\n registerLeadBinding,\n safePagePath,\n safeSlug,\n type LeadAdsParams,\n} from './lead-registry.js'\n\nexport { getBrowserGtag }\n\nexport interface BrandlabBrowserAnalyticsConfig {\n enabled: boolean\n leadDiagnosticsEnabled?: boolean\n enhancedConversionsEnabled?: boolean\n /** Disclosed Enhanced Conversions: ad_user_data is granted by default. */\n adUserDataGrantedByDefault?: boolean\n measurementId?: string | null\n googleAdsConversionId?: string | null\n googleAdsConversionLabel?: string | null\n gtag?: GtagTransport\n}\n\n/**\n * Public analytics values that a client bundle must bind through direct,\n * statically analysable `process.env.NEXT_PUBLIC_*` property access. Ads keys\n * are bound only when the contract declares Google Ads (and website calls).\n */\nexport interface BrandlabBrowserAnalyticsPublicValues {\n /** `process.env.NEXT_PUBLIC_VERCEL_ENV`, inlined by defineBrandlabNextConfig. */\n deploymentEnvironment: string | null | undefined\n measurementId: string | null | undefined\n googleAdsConversionId?: string | null | undefined\n googleAdsConversionLabel?: string | null | undefined\n googleAdsWebsiteCallConversionLabel?: string | null | undefined\n}\n\nexport interface SafeContactAnalyticsParams {\n page_template?: string\n service_slug?: string\n location_slug?: string\n}\n\nexport const PHONE_CALL_PLACEMENTS = [\n 'header',\n 'footer',\n 'hero',\n 'cta',\n 'fab',\n 'contact',\n 'location',\n 'page',\n /** The after-hours `EmergencyBand` (3.15.0). */\n 'emergency',\n] as const\n\nexport type PhoneCallPlacement = (typeof PHONE_CALL_PLACEMENTS)[number]\n\nexport function trackPhoneLinkClick(\n input: { pagePath: string; placement: PhoneCallPlacement },\n config: Pick<BrandlabBrowserAnalyticsConfig, 'enabled' | 'measurementId' | 'gtag'>,\n) {\n if (!PHONE_CALL_PLACEMENTS.includes(input.placement)) return false\n return sendBrandlabAnalyticsEvent({\n ...config,\n gtag: config.gtag ?? getBrowserGtag(),\n eventName: 'phone_link_click',\n params: {\n event_category: 'contact_interaction',\n page_path: safePagePath(input.pagePath),\n placement: input.placement,\n },\n })\n}\n\nexport const LEAD_FORM_ERROR_STAGES = [\n 'validation',\n 'submission',\n 'delivery',\n 'unknown',\n] as const\nexport type LeadFormErrorStage = (typeof LEAD_FORM_ERROR_STAGES)[number]\n\n/**\n * Why a contact attempt failed, sent as lead_form_error's error_reason:\n * - `field`: the server rejected one or more fields (error_field names the first);\n * the visitor sees the field message and can correct it.\n * - `request`: the server refused the request itself with no field to correct\n * (origin, form shape, or a submission time outside the accepted window).\n * - `attachment`: the photos could not be attached.\n * - `delivery`: the server could not deliver the enquiry.\n * - `network`: the browser could not complete the request or read the answer\n * (offline, blocked, timed out, or a platform error page instead of JSON).\n */\nexport const LEAD_FORM_ERROR_REASONS = [\n 'field',\n 'request',\n 'attachment',\n 'delivery',\n 'network',\n] as const\nexport type LeadFormErrorReason = (typeof LEAD_FORM_ERROR_REASONS)[number]\n\nexport interface LeadFormErrorInput {\n pagePath: string\n formId: string\n stage: LeadFormErrorStage\n errorCount: number\n /** Optional failure reason; an unknown value drops the event. */\n reason?: LeadFormErrorReason\n /** The first contract field key the server rejected; sent only with reason `field`. Never a value. */\n field?: string\n}\n\nexport const LEAD_CTA_PLACEMENTS = [\n 'header',\n 'footer',\n 'hero',\n 'section',\n 'navigation',\n 'floating',\n 'page',\n] as const\nexport type LeadCtaPlacement = (typeof LEAD_CTA_PLACEMENTS)[number]\n\ntype LeadDiagnosticConfig = Pick<\n BrandlabBrowserAnalyticsConfig,\n 'enabled' | 'leadDiagnosticsEnabled' | 'measurementId' | 'gtag'\n>\n\nexport function trackLeadFormStart(\n input: { pagePath: string; formId: string; pageTemplate?: string },\n config: LeadDiagnosticConfig,\n) {\n const formId = safeSlug(input.formId)\n if (!formId) return false\n return sendLeadDiagnosticEvent('lead_form_start', {\n form_id: formId,\n page_path: safePagePath(input.pagePath),\n ...(safeSlug(input.pageTemplate) ? { page_template: safeSlug(input.pageTemplate) } : {}),\n }, config)\n}\n\nexport function trackLeadFormError(\n input: LeadFormErrorInput,\n config: LeadDiagnosticConfig,\n) {\n const formId = safeSlug(input.formId)\n if (\n !formId ||\n !LEAD_FORM_ERROR_STAGES.includes(input.stage) ||\n !Number.isInteger(input.errorCount) ||\n input.errorCount < 1 ||\n input.errorCount > 50 ||\n (input.reason !== undefined && !LEAD_FORM_ERROR_REASONS.includes(input.reason))\n ) {\n return false\n }\n const field = input.reason === 'field' ? safeSlug(input.field) : undefined\n return sendLeadDiagnosticEvent('lead_form_error', {\n form_id: formId,\n page_path: safePagePath(input.pagePath),\n error_stage: input.stage,\n error_count: input.errorCount,\n ...(input.reason ? { error_reason: input.reason } : {}),\n ...(field ? { error_field: field } : {}),\n }, config)\n}\n\nexport function trackLeadCtaClick(\n input: {\n pagePath: string\n ctaId: string\n placement: LeadCtaPlacement\n },\n config: LeadDiagnosticConfig,\n) {\n const ctaId = safeSlug(input.ctaId)\n if (!ctaId || !LEAD_CTA_PLACEMENTS.includes(input.placement)) return false\n return sendLeadDiagnosticEvent('lead_cta_click', {\n cta_id: ctaId,\n page_path: safePagePath(input.pagePath),\n placement: input.placement,\n }, config)\n}\n\nfunction sendLeadDiagnosticEvent(\n eventName: 'lead_form_start' | 'lead_form_error' | 'lead_cta_click',\n params: Record<string, unknown>,\n config: LeadDiagnosticConfig,\n) {\n return sendBrandlabAnalyticsEvent({\n enabled: config.enabled && config.leadDiagnosticsEnabled === true,\n measurementId: config.measurementId,\n gtag: config.gtag ?? getBrowserGtag(),\n eventName,\n params,\n })\n}\n\nfunction safeAdditionalEventValue(\n value: unknown,\n policy: NonNullable<BrandlabAnalyticsContract['additionalEvents']>[number]['parameters'][number],\n) {\n if (policy.type === 'boolean') return typeof value === 'boolean' ? value : undefined\n if (policy.type === 'count') {\n const minimum = policy.minimum ?? 0\n const maximum = policy.maximum ?? 100_000\n return typeof value === 'number' && Number.isInteger(value) && value >= minimum && value <= maximum\n ? value\n : undefined\n }\n if (typeof value !== 'string') return undefined\n const normalized = value.trim()\n if (policy.type === 'enum') return policy.values.includes(normalized) ? normalized : undefined\n if (!/^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/.test(normalized) || normalized.length > 80) {\n return undefined\n }\n const digits = normalized.replace(/\\D/g, '')\n return digits.length >= 4 || /^[a-f0-9]{24,}$/.test(normalized)\n ? undefined\n : normalized\n}\n\nexport function trackBrandlabBrowserEvent(\n eventName: string,\n params: Record<string, unknown>,\n config: Pick<BrandlabBrowserAnalyticsConfig, 'enabled' | 'measurementId' | 'gtag'> & {\n contract: BrandlabAnalyticsContract\n },\n) {\n defineAnalyticsContract(config.contract)\n const event = config.contract.additionalEvents?.find(\n (candidate) => candidate.name === eventName,\n )\n if (!event) return false\n const safeParams = Object.fromEntries(\n Object.entries(params).flatMap(([key, value]) => {\n const policy = event.parameters.find((candidate) => candidate.key === key)\n if (!policy) return []\n const safeValue = safeAdditionalEventValue(value, policy)\n return safeValue === undefined ? [] : [[key, safeValue]]\n }),\n )\n return sendBrandlabAnalyticsEvent({\n ...config,\n gtag: config.gtag ?? getBrowserGtag(),\n eventName,\n params: safeParams,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Project binding: src/config/analytics.ts\n// ---------------------------------------------------------------------------\n\ntype PageDimension = 'service_slug' | 'location_slug'\n\nconst PAGE_DIMENSIONS: readonly PageDimension[] = ['service_slug', 'location_slug']\nconst MAX_PAGE_TEMPLATE_ROWS = 5_000\n\n/**\n * One row of the page-template table. Rows are tried in order against the\n * page path's segments (query, hash, empty and trailing segments ignored) and\n * the first match wins; no match reports no page dimensions.\n *\n * `path` segments: a literal matches itself; `:name` matches any one segment,\n * and `:service_slug` / `:location_slug` also report it (lowercased, or\n * omitted when it is not a safe slug, without failing the row); `**` matches\n * zero or more segments and may appear once, anywhere.\n */\nexport interface BrandlabAnalyticsPageTemplate {\n path: string\n template: string\n /** Constant dimensions for every page the row matches. */\n params?: Readonly<Partial<Record<PageDimension, string>>>\n /**\n * Constraints on `:name` segments, tested against the segment exactly as it\n * appears in the path: a list of allowed values or a regular expression. A\n * segment that fails its constraint skips the row.\n */\n values?: Readonly<Record<string, readonly string[] | RegExp>>\n}\n\ntype PageTemplateSegment =\n | { kind: 'literal'; value: string }\n | {\n kind: 'parameter'\n name: string\n dimension?: PageDimension\n allowed?: ReadonlySet<string> | RegExp\n }\n\ninterface CompiledPageTemplate {\n head: readonly PageTemplateSegment[]\n /** Segments after `**`; undefined when the row has no `**`. */\n tail?: readonly PageTemplateSegment[]\n template: string\n params: SafeContactAnalyticsParams\n}\n\nfunction compilePageTemplate(row: BrandlabAnalyticsPageTemplate, index: number): CompiledPageTemplate {\n const field = `pageTemplates[${index}]`\n if (!row || typeof row !== 'object') throw new Error(`${field} must be a page-template row.`)\n const unsupported = Object.keys(row).filter((key) => !['path', 'template', 'params', 'values'].includes(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.`)\n }\n if (typeof row.path !== 'string' || !row.path.startsWith('/') || row.path.includes('//')) {\n throw new Error(`${field}.path must be a root-relative pattern.`)\n }\n const template = safeSlug(row.template)\n if (!template || template !== row.template) {\n throw new Error(`${field}.template must be a lowercase slug.`)\n }\n const parts = row.path === '/' ? [] : row.path.replace(/\\/$/, '').slice(1).split('/')\n if (parts.filter((part) => part === '**').length > 1) {\n throw new Error(`${field}.path may use ** only once.`)\n }\n const names = new Set<string>()\n const segments = parts.map((part): PageTemplateSegment | '**' => {\n if (part === '**') return '**'\n if (part.startsWith(':')) {\n const name = part.slice(1)\n if (!/^[a-z_][a-z0-9_]*$/.test(name)) throw new Error(`${field}.path has an invalid parameter \"${part}\".`)\n if (names.has(name)) throw new Error(`${field}.path repeats ${part}.`)\n names.add(name)\n return PAGE_DIMENSIONS.includes(name as PageDimension)\n ? { kind: 'parameter', name, dimension: name as PageDimension }\n : { kind: 'parameter', name }\n }\n if (!/^[A-Za-z0-9._~-]+$/.test(part)) throw new Error(`${field}.path has an invalid segment \"${part}\".`)\n return { kind: 'literal', value: part }\n })\n const params: SafeContactAnalyticsParams = {}\n for (const [key, value] of Object.entries(row.params ?? {})) {\n const dimension = key as PageDimension\n if (!PAGE_DIMENSIONS.includes(dimension) || names.has(dimension)) {\n throw new Error(`${field}.params.${key} is not an uncaptured page dimension.`)\n }\n if (typeof value !== 'string' || safeSlug(value) !== value) {\n throw new Error(`${field}.params.${key} must be a lowercase slug.`)\n }\n params[dimension] = value\n }\n for (const [name, allowed] of Object.entries(row.values ?? {})) {\n if (!names.has(name)) throw new Error(`${field}.values.${name} needs :${name} in the path.`)\n let constraint: ReadonlySet<string> | RegExp\n if (allowed instanceof RegExp) {\n if (allowed.global || allowed.sticky) {\n throw new Error(`${field}.values.${name} must be a regular expression without the g or y flag.`)\n }\n constraint = allowed\n } else if (Array.isArray(allowed) && allowed.every((value) => typeof value === 'string' && value.length > 0)) {\n constraint = new Set(allowed)\n } else {\n throw new Error(`${field}.values.${name} must list segment values or be a regular expression.`)\n }\n for (const segment of segments) {\n if (segment !== '**' && segment.kind === 'parameter' && segment.name === name) segment.allowed = constraint\n }\n }\n const rest = segments.indexOf('**')\n const compiled = segments.filter((segment): segment is PageTemplateSegment => segment !== '**')\n return {\n head: rest === -1 ? compiled : compiled.slice(0, rest),\n ...(rest === -1 ? {} : { tail: compiled.slice(rest) }),\n template,\n params,\n }\n}\n\nfunction pathSegments(pagePath: string) {\n return String(pagePath).split(/[?#]/, 1)[0]!.split('/').filter(Boolean)\n}\n\nfunction matchSegment(\n segment: PageTemplateSegment,\n part: string,\n params: SafeContactAnalyticsParams,\n) {\n if (segment.kind === 'literal') return segment.value === part\n const { allowed } = segment\n if (allowed && !(allowed instanceof RegExp ? allowed.test(part) : allowed.has(part))) return false\n if (segment.dimension) {\n const value = safeSlug(part)\n if (value) params[segment.dimension] = value\n }\n return true\n}\n\nfunction matchPageTemplate(row: CompiledPageTemplate, parts: readonly string[]) {\n const fixed = row.head.length + (row.tail?.length ?? 0)\n if (row.tail ? parts.length < fixed : parts.length !== fixed) return undefined\n const params: SafeContactAnalyticsParams = { page_template: row.template, ...row.params }\n for (let index = 0; index < row.head.length; index += 1) {\n if (!matchSegment(row.head[index]!, parts[index]!, params)) return undefined\n }\n const offset = parts.length - (row.tail?.length ?? 0)\n for (let index = 0; index < (row.tail?.length ?? 0); index += 1) {\n if (!matchSegment(row.tail![index]!, parts[offset + index]!, params)) return undefined\n }\n return params\n}\n\n/** What a declared lead value sees for one accepted enquiry. */\nexport interface BrandlabLeadContext {\n /** The form's page path, as useBrandlabContactForm received it. */\n readonly pagePath: string\n /** The page's contact_form dimensions from the page-template table. */\n readonly pageParams: SafeContactAnalyticsParams\n /** The accepted enquiry's fields; they are never sent to analytics. */\n readonly formData: FormData\n}\n\nexport interface BrandlabGoogleAdsLeadConversion {\n /**\n * Conversion value: a number, or a function evaluated for each accepted\n * enquiry. A function that throws or returns anything but a finite,\n * non-negative number sends the conversion without a value.\n */\n value?: number | ((lead: BrandlabLeadContext) => number | undefined)\n /** ISO 4217 currency code, for example `AUD`. */\n currency?: string\n}\n\n/**\n * How the site reports an accepted enquiry, declared once. Every accepted\n * enquiry sends one GA4 `contact_form` (page dimensions from the page-template\n * table) and, when the contract declares Google Ads, one form conversion with\n * `send_to` and `transaction_id` plus the value and currency declared here.\n */\nexport interface BrandlabLeadReporting {\n googleAdsConversion?: BrandlabGoogleAdsLeadConversion\n}\n\nfunction compileLeadReporting(\n contract: BrandlabAnalyticsContract,\n leads: BrandlabLeadReporting | undefined,\n): (lead: BrandlabLeadContext) => LeadAdsParams {\n if (leads === undefined) return () => ({})\n if (!leads || typeof leads !== 'object' || Array.isArray(leads)) {\n throw new Error('leads must be a lead-reporting declaration.')\n }\n const unsupported = Object.keys(leads).filter((key) => key !== 'googleAdsConversion')\n if (unsupported.length > 0) throw new Error(`leads contains unsupported keys: ${unsupported.join(', ')}.`)\n const conversion = leads.googleAdsConversion\n if (conversion === undefined) return () => ({})\n if (!conversion || typeof conversion !== 'object' || Array.isArray(conversion)) {\n throw new Error('leads.googleAdsConversion must declare value and/or currency.')\n }\n if (!contract.googleAds) {\n throw new Error('leads.googleAdsConversion needs googleAds in the analytics contract.')\n }\n const unsupportedConversion = Object.keys(conversion).filter((key) => key !== 'value' && key !== 'currency')\n if (unsupportedConversion.length > 0) {\n throw new Error(`leads.googleAdsConversion contains unsupported keys: ${unsupportedConversion.join(', ')}.`)\n }\n const { value, currency } = conversion\n if (currency !== undefined && (typeof currency !== 'string' || !/^[A-Z]{3}$/.test(currency))) {\n throw new Error('leads.googleAdsConversion.currency must be an ISO 4217 code such as AUD.')\n }\n if (\n value !== undefined &&\n typeof value !== 'function' &&\n (typeof value !== 'number' || !Number.isFinite(value) || value < 0)\n ) {\n throw new Error('leads.googleAdsConversion.value must be a non-negative number or a function.')\n }\n return (lead) => {\n let resolved: unknown = value\n if (typeof value === 'function') {\n try {\n resolved = value(lead)\n } catch {\n resolved = undefined\n }\n }\n return {\n ...(currency ? { currency } : {}),\n ...(typeof resolved === 'number' ? { value: resolved } : {}),\n }\n }\n}\n\nexport interface DefineBrandlabAnalyticsOptions {\n contract: BrandlabAnalyticsContract\n publicValues: BrandlabBrowserAnalyticsPublicValues\n /** Classifies page paths for contact_form and lead_form_start dimensions. */\n pageTemplates: readonly BrandlabAnalyticsPageTemplate[]\n /** The Google Ads form-conversion value and currency, if the site sends them. */\n leads?: BrandlabLeadReporting\n}\n\nexport interface BrandlabAnalytics {\n readonly contract: BrandlabAnalyticsContract\n /** True only on the Vercel production deployment. */\n readonly enabled: boolean\n /** Props for the single `<BrandlabGoogleTag {...analytics.googleTag} />` in the root layout. */\n readonly googleTag: BrandlabGoogleTagConfig\n /** Tracking configuration; the page's gtag function is resolved when an event is sent. */\n readonly config: BrandlabBrowserAnalyticsConfig\n /** Privacy-safe page_template, service_slug and location_slug for a path. */\n pageParams(pagePath: string): SafeContactAnalyticsParams\n trackPhoneLinkClick(input: { pagePath: string; placement: PhoneCallPlacement }): boolean\n trackLeadFormStart(input: { pagePath: string; formId: string }): boolean\n trackLeadFormError(input: LeadFormErrorInput): boolean\n trackLeadCtaClick(input: { pagePath: string; ctaId: string; placement: LeadCtaPlacement }): boolean\n /** Sends an event declared in the contract's additionalEvents, filtered by its parameter policy. */\n trackEvent(eventName: string, params: Record<string, unknown>): boolean\n}\n\nconst PUBLIC_VALUE_KEYS = [\n 'deploymentEnvironment',\n 'measurementId',\n 'googleAdsConversionId',\n 'googleAdsConversionLabel',\n 'googleAdsWebsiteCallConversionLabel',\n] as const\n\nfunction assertPublicValues(\n contract: BrandlabAnalyticsContract,\n publicValues: BrandlabBrowserAnalyticsPublicValues,\n) {\n if (!publicValues || typeof publicValues !== 'object' || Array.isArray(publicValues)) {\n throw new Error('publicValues must bind the NEXT_PUBLIC_* analytics values.')\n }\n const required = new Set<string>(['deploymentEnvironment', 'measurementId'])\n if (contract.googleAds) {\n required.add('googleAdsConversionId')\n required.add('googleAdsConversionLabel')\n if (contract.googleAds.websiteCalls) required.add('googleAdsWebsiteCallConversionLabel')\n }\n for (const key of Object.keys(publicValues)) {\n if (!(PUBLIC_VALUE_KEYS as readonly string[]).includes(key)) {\n throw new Error(`publicValues contains unsupported key \"${key}\".`)\n }\n if (!required.has(key)) {\n throw new Error(`publicValues.${key} is bound but the analytics contract does not declare it.`)\n }\n }\n for (const key of required) {\n if (!Object.hasOwn(publicValues, key)) {\n throw new Error(`publicValues.${key} must be bound with literal process.env property access.`)\n }\n const value = (publicValues as unknown as Record<string, unknown>)[key]\n if (value !== undefined && value !== null && typeof value !== 'string') {\n throw new Error(`publicValues.${key} must be a string or undefined.`)\n }\n }\n const browserEnvKeys = [\n contract.measurementIdEnv,\n contract.googleAds?.conversionIdEnv,\n contract.googleAds?.contactConversionLabelEnv,\n contract.googleAds?.websiteCalls?.conversionLabelEnv,\n ].filter((key): key is string => Boolean(key))\n const privateKey = browserEnvKeys.find((key) => !key.startsWith('NEXT_PUBLIC_'))\n if (privateKey) {\n throw new Error(`${privateKey} is read in the browser and must be a NEXT_PUBLIC_ variable.`)\n }\n}\n\nfunction publicValue(value: string | null | undefined) {\n const trimmed = value?.trim()\n return trimmed || undefined\n}\n\n/**\n * The project's single analytics binding (`src/config/analytics.ts`). It is\n * safe in server and browser code: the root layout renders\n * `analytics.googleTag`, `src/instrumentation-client.ts` installs the phone\n * listener, and `useBrandlabContactForm` reports each accepted enquiry exactly\n * as `pageTemplates` and `leads` declare, plus the lead diagnostics. Analytics\n * runs only on the Vercel production deployment.\n */\nexport function defineBrandlabAnalytics(options: DefineBrandlabAnalyticsOptions): BrandlabAnalytics {\n if (!options || typeof options !== 'object') {\n throw new TypeError('defineBrandlabAnalytics requires contract, publicValues and pageTemplates.')\n }\n const contract = defineAnalyticsContract(options.contract)\n assertPublicValues(contract, options.publicValues)\n if (!Array.isArray(options.pageTemplates) || options.pageTemplates.length > MAX_PAGE_TEMPLATE_ROWS) {\n throw new Error(`pageTemplates must list at most ${MAX_PAGE_TEMPLATE_ROWS} rows.`)\n }\n const pageTemplates = options.pageTemplates.map(compilePageTemplate)\n const leadAdsParams = compileLeadReporting(contract, options.leads)\n const values = options.publicValues\n const enabled =\n resolveBrandlabDeployment({ NEXT_PUBLIC_VERCEL_ENV: publicValue(values.deploymentEnvironment) })\n .environment === 'production'\n const measurementId = publicValue(values.measurementId)\n const googleAdsConversionId = contract.googleAds ? publicValue(values.googleAdsConversionId) : undefined\n const enhancedConversionsMode = contract.googleAds?.enhancedConversions?.mode\n const adUserDataGrantedByDefault = enhancedConversionsMode === 'disclosed'\n const config: BrandlabBrowserAnalyticsConfig = Object.freeze({\n enabled,\n // Privacy-safe lead_form_start / lead_form_error diagnostics are on for every site.\n leadDiagnosticsEnabled: true,\n enhancedConversionsEnabled:\n enhancedConversionsMode === 'consent-required' || enhancedConversionsMode === 'disclosed',\n adUserDataGrantedByDefault,\n measurementId,\n googleAdsConversionId,\n googleAdsConversionLabel: contract.googleAds\n ? publicValue(values.googleAdsConversionLabel)\n : undefined,\n })\n const googleTag: BrandlabGoogleTagConfig = Object.freeze({\n enabled,\n measurementId,\n googleAdsConversionId,\n googleAdsWebsiteCallConversionLabel: contract.googleAds?.websiteCalls\n ? publicValue(values.googleAdsWebsiteCallConversionLabel)\n : undefined,\n adUserData: adUserDataGrantedByDefault ? 'granted' : 'denied',\n })\n const pageParams = (pagePath: string): SafeContactAnalyticsParams => {\n const parts = pathSegments(pagePath)\n for (const row of pageTemplates) {\n const params = matchPageTemplate(row, parts)\n if (params) return params\n }\n return {}\n }\n\n const binding: BrandlabAnalytics = Object.freeze({\n contract,\n enabled,\n googleTag,\n config,\n pageParams,\n trackPhoneLinkClick: (input: { pagePath: string; placement: PhoneCallPlacement }) =>\n trackPhoneLinkClick(input, config),\n trackLeadFormStart: (input: { pagePath: string; formId: string }) =>\n trackLeadFormStart({ ...input, pageTemplate: pageParams(input.pagePath).page_template }, config),\n trackLeadFormError: (input: LeadFormErrorInput) => trackLeadFormError(input, config),\n trackLeadCtaClick: (input: { pagePath: string; ctaId: string; placement: LeadCtaPlacement }) =>\n trackLeadCtaClick(input, config),\n trackEvent: (eventName: string, params: Record<string, unknown>) =>\n trackBrandlabBrowserEvent(eventName, params, { ...config, contract }),\n })\n registerLeadBinding(binding, { config, pageParams, adsParams: leadAdsParams })\n return binding\n}\n"]}
|
|
@@ -7,8 +7,11 @@ export declare const NAV_HOVER_OPEN_DELAY_MS = 100;
|
|
|
7
7
|
export declare const NAV_HOVER_CLOSE_DELAY_MS = 200;
|
|
8
8
|
/** Only a fine mouse pointer that can hover opens panels on hover. */
|
|
9
9
|
export declare const NAV_HOVER_MEDIA_QUERY = "(hover: hover) and (pointer: fine)";
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* F2: header modes by the header's own width. The full menu starts at 1200 px (3.15.0; 1280 before), so a 1280 px
|
|
12
|
+
* window with a classic scrollbar (a 1265 px header) shows the menu, not the Menu button.
|
|
13
|
+
*/
|
|
14
|
+
export declare const HEADER_DESKTOP_MIN_WIDTH = 1200;
|
|
12
15
|
export declare const HEADER_COMPACT_MIN_WIDTH = 1024;
|
|
13
16
|
/** With `compactAfterScroll`, the header compacts once the page has scrolled this far. */
|
|
14
17
|
export declare const HEADER_COMPACT_AFTER_SCROLL_PX = 80;
|
|
@@ -16,6 +19,12 @@ export type HeaderMode = 'phone' | 'compact' | 'desktop';
|
|
|
16
19
|
export declare function headerModeFor(width: number): HeaderMode;
|
|
17
20
|
/** F6: the call bar shows below this viewport width. */
|
|
18
21
|
export declare const CALL_BAR_MEDIA_QUERY = "(max-width: 1023.98px)";
|
|
22
|
+
/**
|
|
23
|
+
* F6: while the call bar is mounted, the root element carries this attribute (`shown` or `hidden`) and the document gets
|
|
24
|
+
* a `CALL_BAR_STATE_EVENT` (`detail.shown`) on every change, so back-to-top stays hidden while the bar shows (3.15.0).
|
|
25
|
+
*/
|
|
26
|
+
export declare const CALL_BAR_STATE_ATTRIBUTE = "data-bl-callbar";
|
|
27
|
+
export declare const CALL_BAR_STATE_EVENT = "bl:callbar";
|
|
19
28
|
/** F6: the hero's own call and quote buttons (and any other call path) carry this marker. */
|
|
20
29
|
export declare const CALL_PATH_SELECTOR = "[data-bl-call-path]";
|
|
21
30
|
/** F7: back-to-top shows after this many screens of scrolling. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/navigation/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,0GAA0G;AAC1G,eAAO,MAAM,uBAAuB,MAAM,CAAA;AAC1C,eAAO,MAAM,wBAAwB,MAAM,CAAA;AAC3C,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,uCAAuC,CAAA;AAEzE
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/navigation/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,0GAA0G;AAC1G,eAAO,MAAM,uBAAuB,MAAM,CAAA;AAC1C,eAAO,MAAM,wBAAwB,MAAM,CAAA;AAC3C,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,uCAAuC,CAAA;AAEzE;;;GAGG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAA;AAC5C,eAAO,MAAM,wBAAwB,OAAO,CAAA;AAC5C,0FAA0F;AAC1F,eAAO,MAAM,8BAA8B,KAAK,CAAA;AAEhD,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;AAExD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAIvD;AAED,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,2BAA2B,CAAA;AAC5D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,oBAAoB,CAAA;AACzD,eAAO,MAAM,oBAAoB,eAAe,CAAA;AAChD,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,wBAAwB,CAAA;AAEvD,kEAAkE;AAClE,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAC5C,iEAAiE;AACjE,eAAO,MAAM,eAAe,SAAS,CAAA"}
|
|
@@ -7,8 +7,11 @@ export const NAV_HOVER_OPEN_DELAY_MS = 100;
|
|
|
7
7
|
export const NAV_HOVER_CLOSE_DELAY_MS = 200;
|
|
8
8
|
/** Only a fine mouse pointer that can hover opens panels on hover. */
|
|
9
9
|
export const NAV_HOVER_MEDIA_QUERY = '(hover: hover) and (pointer: fine)';
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* F2: header modes by the header's own width. The full menu starts at 1200 px (3.15.0; 1280 before), so a 1280 px
|
|
12
|
+
* window with a classic scrollbar (a 1265 px header) shows the menu, not the Menu button.
|
|
13
|
+
*/
|
|
14
|
+
export const HEADER_DESKTOP_MIN_WIDTH = 1200;
|
|
12
15
|
export const HEADER_COMPACT_MIN_WIDTH = 1024;
|
|
13
16
|
/** With `compactAfterScroll`, the header compacts once the page has scrolled this far. */
|
|
14
17
|
export const HEADER_COMPACT_AFTER_SCROLL_PX = 80;
|
|
@@ -21,6 +24,12 @@ export function headerModeFor(width) {
|
|
|
21
24
|
}
|
|
22
25
|
/** F6: the call bar shows below this viewport width. */
|
|
23
26
|
export const CALL_BAR_MEDIA_QUERY = '(max-width: 1023.98px)';
|
|
27
|
+
/**
|
|
28
|
+
* F6: while the call bar is mounted, the root element carries this attribute (`shown` or `hidden`) and the document gets
|
|
29
|
+
* a `CALL_BAR_STATE_EVENT` (`detail.shown`) on every change, so back-to-top stays hidden while the bar shows (3.15.0).
|
|
30
|
+
*/
|
|
31
|
+
export const CALL_BAR_STATE_ATTRIBUTE = 'data-bl-callbar';
|
|
32
|
+
export const CALL_BAR_STATE_EVENT = 'bl:callbar';
|
|
24
33
|
/** F6: the hero's own call and quote buttons (and any other call path) carry this marker. */
|
|
25
34
|
export const CALL_PATH_SELECTOR = '[data-bl-call-path]';
|
|
26
35
|
/** F7: back-to-top shows after this many screens of scrolling. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/navigation/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,0GAA0G;AAC1G,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AAC1C,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAA;AAC3C,sEAAsE;AACtE,MAAM,CAAC,MAAM,qBAAqB,GAAG,oCAAoC,CAAA;AAEzE
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/navigation/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,0GAA0G;AAC1G,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AAC1C,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAA;AAC3C,sEAAsE;AACtE,MAAM,CAAC,MAAM,qBAAqB,GAAG,oCAAoC,CAAA;AAEzE;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAA;AAC5C,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAA;AAC5C,0FAA0F;AAC1F,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAA;AAIhD,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,KAAK,IAAI,wBAAwB;QAAE,OAAO,SAAS,CAAA;IACvD,IAAI,KAAK,IAAI,wBAAwB;QAAE,OAAO,SAAS,CAAA;IACvD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,wBAAwB,CAAA;AAC5D;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,iBAAiB,CAAA;AACzD,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAA;AAChD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,kBAAkB,GAAG,qBAAqB,CAAA;AAEvD,kEAAkE;AAClE,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAC5C,iEAAiE;AACjE,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAA","sourcesContent":["/**\n * The shell's timings, breakpoints and hooks as plain values (no React), so server components can read them. A site\n * can't change them.\n */\n\n/** F3: a fine mouse opens a panel 100 ms after it enters an item and closes it 200 ms after it leaves. */\nexport const NAV_HOVER_OPEN_DELAY_MS = 100\nexport const NAV_HOVER_CLOSE_DELAY_MS = 200\n/** Only a fine mouse pointer that can hover opens panels on hover. */\nexport const NAV_HOVER_MEDIA_QUERY = '(hover: hover) and (pointer: fine)'\n\n/**\n * F2: header modes by the header's own width. The full menu starts at 1200 px (3.15.0; 1280 before), so a 1280 px\n * window with a classic scrollbar (a 1265 px header) shows the menu, not the Menu button.\n */\nexport const HEADER_DESKTOP_MIN_WIDTH = 1200\nexport const HEADER_COMPACT_MIN_WIDTH = 1024\n/** With `compactAfterScroll`, the header compacts once the page has scrolled this far. */\nexport const HEADER_COMPACT_AFTER_SCROLL_PX = 80\n\nexport type HeaderMode = 'phone' | 'compact' | 'desktop'\n\nexport function headerModeFor(width: number): HeaderMode {\n if (width >= HEADER_DESKTOP_MIN_WIDTH) return 'desktop'\n if (width >= HEADER_COMPACT_MIN_WIDTH) return 'compact'\n return 'phone'\n}\n\n/** F6: the call bar shows below this viewport width. */\nexport const CALL_BAR_MEDIA_QUERY = '(max-width: 1023.98px)'\n/**\n * F6: while the call bar is mounted, the root element carries this attribute (`shown` or `hidden`) and the document gets\n * a `CALL_BAR_STATE_EVENT` (`detail.shown`) on every change, so back-to-top stays hidden while the bar shows (3.15.0).\n */\nexport const CALL_BAR_STATE_ATTRIBUTE = 'data-bl-callbar'\nexport const CALL_BAR_STATE_EVENT = 'bl:callbar'\n/** F6: the hero's own call and quote buttons (and any other call path) carry this marker. */\nexport const CALL_PATH_SELECTOR = '[data-bl-call-path]'\n\n/** F7: back-to-top shows after this many screens of scrolling. */\nexport const BACK_TO_TOP_AFTER_SCREENS = 1.5\n/** F12: the skip-link target, which back-to-top also focuses. */\nexport const MAIN_CONTENT_ID = 'main'\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* The ui-kit components use the hooks; a site uses them directly only in a recorded bespoke header. The hook modules
|
|
4
4
|
* are client modules; the constants are plain values a server component can read.
|
|
5
5
|
*/
|
|
6
|
-
export { BACK_TO_TOP_AFTER_SCREENS, CALL_BAR_MEDIA_QUERY, CALL_PATH_SELECTOR, HEADER_COMPACT_AFTER_SCROLL_PX, HEADER_COMPACT_MIN_WIDTH, HEADER_DESKTOP_MIN_WIDTH, MAIN_CONTENT_ID, NAV_HOVER_CLOSE_DELAY_MS, NAV_HOVER_MEDIA_QUERY, NAV_HOVER_OPEN_DELAY_MS, headerModeFor, type HeaderMode, } from './constants.js';
|
|
6
|
+
export { BACK_TO_TOP_AFTER_SCREENS, CALL_BAR_MEDIA_QUERY, CALL_BAR_STATE_ATTRIBUTE, CALL_BAR_STATE_EVENT, CALL_PATH_SELECTOR, HEADER_COMPACT_AFTER_SCROLL_PX, HEADER_COMPACT_MIN_WIDTH, HEADER_DESKTOP_MIN_WIDTH, MAIN_CONTENT_ID, NAV_HOVER_CLOSE_DELAY_MS, NAV_HOVER_MEDIA_QUERY, NAV_HOVER_OPEN_DELAY_MS, headerModeFor, type HeaderMode, } from './constants.js';
|
|
7
7
|
export { useNavigationMenu, type NavigationMenu, type NavigationOpenedBy } from './use-navigation-menu.js';
|
|
8
8
|
export { useHeaderMetrics, type HeaderMetrics } from './use-header-metrics.js';
|
|
9
9
|
export { useStickyCallBar } from './use-sticky-call-bar.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,kBAAkB,EAClB,8BAA8B,EAC9B,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAC1G,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,8BAA8B,EAC9B,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAC1G,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/navigation/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* The ui-kit components use the hooks; a site uses them directly only in a recorded bespoke header. The hook modules
|
|
4
4
|
* are client modules; the constants are plain values a server component can read.
|
|
5
5
|
*/
|
|
6
|
-
export { BACK_TO_TOP_AFTER_SCREENS, CALL_BAR_MEDIA_QUERY, CALL_PATH_SELECTOR, HEADER_COMPACT_AFTER_SCROLL_PX, HEADER_COMPACT_MIN_WIDTH, HEADER_DESKTOP_MIN_WIDTH, MAIN_CONTENT_ID, NAV_HOVER_CLOSE_DELAY_MS, NAV_HOVER_MEDIA_QUERY, NAV_HOVER_OPEN_DELAY_MS, headerModeFor, } from './constants.js';
|
|
6
|
+
export { BACK_TO_TOP_AFTER_SCREENS, CALL_BAR_MEDIA_QUERY, CALL_BAR_STATE_ATTRIBUTE, CALL_BAR_STATE_EVENT, CALL_PATH_SELECTOR, HEADER_COMPACT_AFTER_SCROLL_PX, HEADER_COMPACT_MIN_WIDTH, HEADER_DESKTOP_MIN_WIDTH, MAIN_CONTENT_ID, NAV_HOVER_CLOSE_DELAY_MS, NAV_HOVER_MEDIA_QUERY, NAV_HOVER_OPEN_DELAY_MS, headerModeFor, } from './constants.js';
|
|
7
7
|
export { useNavigationMenu } from './use-navigation-menu.js';
|
|
8
8
|
export { useHeaderMetrics } from './use-header-metrics.js';
|
|
9
9
|
export { useStickyCallBar } from './use-sticky-call-bar.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,kBAAkB,EAClB,8BAA8B,EAC9B,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,GAEd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAgD,MAAM,0BAA0B,CAAA;AAC1G,OAAO,EAAE,gBAAgB,EAAsB,MAAM,yBAAyB,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA","sourcesContent":["/**\n * `@brandlab-digital/site-next/navigation`: the client hooks behind the ui-kit shell, and its timings and breakpoints.\n * The ui-kit components use the hooks; a site uses them directly only in a recorded bespoke header. The hook modules\n * are client modules; the constants are plain values a server component can read.\n */\nexport {\n BACK_TO_TOP_AFTER_SCREENS,\n CALL_BAR_MEDIA_QUERY,\n CALL_PATH_SELECTOR,\n HEADER_COMPACT_AFTER_SCROLL_PX,\n HEADER_COMPACT_MIN_WIDTH,\n HEADER_DESKTOP_MIN_WIDTH,\n MAIN_CONTENT_ID,\n NAV_HOVER_CLOSE_DELAY_MS,\n NAV_HOVER_MEDIA_QUERY,\n NAV_HOVER_OPEN_DELAY_MS,\n headerModeFor,\n type HeaderMode,\n} from './constants.js'\nexport { useNavigationMenu, type NavigationMenu, type NavigationOpenedBy } from './use-navigation-menu.js'\nexport { useHeaderMetrics, type HeaderMetrics } from './use-header-metrics.js'\nexport { useStickyCallBar } from './use-sticky-call-bar.js'\nexport { useBackToTop } from './use-back-to-top.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,8BAA8B,EAC9B,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,GAEd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAgD,MAAM,0BAA0B,CAAA;AAC1G,OAAO,EAAE,gBAAgB,EAAsB,MAAM,yBAAyB,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA","sourcesContent":["/**\n * `@brandlab-digital/site-next/navigation`: the client hooks behind the ui-kit shell, and its timings and breakpoints.\n * The ui-kit components use the hooks; a site uses them directly only in a recorded bespoke header. The hook modules\n * are client modules; the constants are plain values a server component can read.\n */\nexport {\n BACK_TO_TOP_AFTER_SCREENS,\n CALL_BAR_MEDIA_QUERY,\n CALL_BAR_STATE_ATTRIBUTE,\n CALL_BAR_STATE_EVENT,\n CALL_PATH_SELECTOR,\n HEADER_COMPACT_AFTER_SCROLL_PX,\n HEADER_COMPACT_MIN_WIDTH,\n HEADER_DESKTOP_MIN_WIDTH,\n MAIN_CONTENT_ID,\n NAV_HOVER_CLOSE_DELAY_MS,\n NAV_HOVER_MEDIA_QUERY,\n NAV_HOVER_OPEN_DELAY_MS,\n headerModeFor,\n type HeaderMode,\n} from './constants.js'\nexport { useNavigationMenu, type NavigationMenu, type NavigationOpenedBy } from './use-navigation-menu.js'\nexport { useHeaderMetrics, type HeaderMetrics } from './use-header-metrics.js'\nexport { useStickyCallBar } from './use-sticky-call-bar.js'\nexport { useBackToTop } from './use-back-to-top.js'\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-back-to-top.d.ts","sourceRoot":"","sources":["../../src/navigation/use-back-to-top.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-back-to-top.d.ts","sourceRoot":"","sources":["../../src/navigation/use-back-to-top.ts"],"names":[],"mappings":"AAWA,wBAAgB,YAAY,IAAI;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,IAAI,IAAI,CAAA;CAAE,CA8B/E"}
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
/**
|
|
3
3
|
* F7: back-to-top. It shows after one and a half screens of scrolling; it scrolls smoothly unless the visitor prefers
|
|
4
|
-
* reduced motion, and moves focus to the skip-link target (`#main`), so keyboard users start again at the top.
|
|
4
|
+
* reduced motion, and moves focus to the skip-link target (`#main`), so keyboard users start again at the top. It stays
|
|
5
|
+
* hidden while the phone call bar shows (3.15.0): stacked above the bar it covered the page's content on a phone, and
|
|
6
|
+
* the bar already sits within one tap. It shows again once the bar hides (over the footer, for example).
|
|
5
7
|
*/
|
|
6
8
|
import { useCallback, useEffect, useState } from 'react';
|
|
7
|
-
import { BACK_TO_TOP_AFTER_SCREENS, MAIN_CONTENT_ID } from './constants.js';
|
|
9
|
+
import { BACK_TO_TOP_AFTER_SCREENS, CALL_BAR_STATE_ATTRIBUTE, CALL_BAR_STATE_EVENT, MAIN_CONTENT_ID } from './constants.js';
|
|
8
10
|
export function useBackToTop() {
|
|
9
|
-
const [
|
|
11
|
+
const [scrolled, setScrolled] = useState(false);
|
|
12
|
+
const [callBarShown, setCallBarShown] = useState(false);
|
|
10
13
|
useEffect(() => {
|
|
11
|
-
const update = () =>
|
|
14
|
+
const update = () => setScrolled(window.scrollY > window.innerHeight * BACK_TO_TOP_AFTER_SCREENS);
|
|
15
|
+
const onCallBar = (event) => setCallBarShown(event.detail?.shown === true);
|
|
12
16
|
update();
|
|
17
|
+
setCallBarShown(document.documentElement.getAttribute(CALL_BAR_STATE_ATTRIBUTE) === 'shown');
|
|
13
18
|
window.addEventListener('scroll', update, { passive: true });
|
|
14
19
|
window.addEventListener('resize', update);
|
|
20
|
+
document.addEventListener(CALL_BAR_STATE_EVENT, onCallBar);
|
|
15
21
|
return () => {
|
|
16
22
|
window.removeEventListener('scroll', update);
|
|
17
23
|
window.removeEventListener('resize', update);
|
|
24
|
+
document.removeEventListener(CALL_BAR_STATE_EVENT, onCallBar);
|
|
18
25
|
};
|
|
19
26
|
}, []);
|
|
27
|
+
const shown = scrolled && !callBarShown;
|
|
20
28
|
const scrollToTop = useCallback(() => {
|
|
21
29
|
const reduced = typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
22
30
|
window.scrollTo({ top: 0, behavior: reduced ? 'auto' : 'smooth' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-back-to-top.js","sourceRoot":"","sources":["../../src/navigation/use-back-to-top.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ
|
|
1
|
+
{"version":3,"file":"use-back-to-top.js","sourceRoot":"","sources":["../../src/navigation/use-back-to-top.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ;;;;;GAKG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACxD,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAE3H,MAAM,UAAU,YAAY;IAC1B,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/C,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEvD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,WAAW,GAAG,yBAAyB,CAAC,CAAA;QACjG,MAAM,SAAS,GAAG,CAAC,KAAY,EAAE,EAAE,CAAC,eAAe,CAAE,KAA0C,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC,CAAA;QACvH,MAAM,EAAE,CAAA;QACR,eAAe,CAAC,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,wBAAwB,CAAC,KAAK,OAAO,CAAC,CAAA;QAC5F,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5D,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACzC,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAA;QAC1D,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;YAC5C,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;YAC5C,QAAQ,CAAC,mBAAmB,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAA;QAC/D,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,MAAM,KAAK,GAAG,QAAQ,IAAI,CAAC,YAAY,CAAA;IAEvC,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,MAAM,OAAO,GAAG,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAC,OAAO,CAAA;QACxH,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAA;QAClE,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,eAAe,CAAC,CAAA;QACrD,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;YAAE,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;QACvE,IAAI,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IACrC,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;AAC/B,CAAC","sourcesContent":["'use client'\n\n/**\n * F7: back-to-top. It shows after one and a half screens of scrolling; it scrolls smoothly unless the visitor prefers\n * reduced motion, and moves focus to the skip-link target (`#main`), so keyboard users start again at the top. It stays\n * hidden while the phone call bar shows (3.15.0): stacked above the bar it covered the page's content on a phone, and\n * the bar already sits within one tap. It shows again once the bar hides (over the footer, for example).\n */\nimport { useCallback, useEffect, useState } from 'react'\nimport { BACK_TO_TOP_AFTER_SCREENS, CALL_BAR_STATE_ATTRIBUTE, CALL_BAR_STATE_EVENT, MAIN_CONTENT_ID } from './constants.js'\n\nexport function useBackToTop(): { readonly shown: boolean; scrollToTop(): void } {\n const [scrolled, setScrolled] = useState(false)\n const [callBarShown, setCallBarShown] = useState(false)\n\n useEffect(() => {\n const update = () => setScrolled(window.scrollY > window.innerHeight * BACK_TO_TOP_AFTER_SCREENS)\n const onCallBar = (event: Event) => setCallBarShown((event as CustomEvent<{ shown?: boolean }>).detail?.shown === true)\n update()\n setCallBarShown(document.documentElement.getAttribute(CALL_BAR_STATE_ATTRIBUTE) === 'shown')\n window.addEventListener('scroll', update, { passive: true })\n window.addEventListener('resize', update)\n document.addEventListener(CALL_BAR_STATE_EVENT, onCallBar)\n return () => {\n window.removeEventListener('scroll', update)\n window.removeEventListener('resize', update)\n document.removeEventListener(CALL_BAR_STATE_EVENT, onCallBar)\n }\n }, [])\n const shown = scrolled && !callBarShown\n\n const scrollToTop = useCallback(() => {\n const reduced = typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches\n window.scrollTo({ top: 0, behavior: reduced ? 'auto' : 'smooth' })\n const main = document.getElementById(MAIN_CONTENT_ID)\n if (!main) return\n if (!main.hasAttribute('tabindex')) main.setAttribute('tabindex', '-1')\n main.focus({ preventScroll: true })\n }, [])\n\n return { shown, scrollToTop }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* F2: the header's measured state. The mode follows the header's own width (so a framed specimen behaves like a real
|
|
3
|
-
* window):
|
|
3
|
+
* window): 1200 px and up `desktop`, 1024 to 1199 px `compact`, below 1024 px `phone`. The hook writes the frozen
|
|
4
4
|
* hooks on the header root (`data-mode`, `data-hydrated`, `data-compact`) and `--bl-header-height` on the document,
|
|
5
5
|
* which drives `scroll-padding-top` (WCAG 2.4.11).
|
|
6
6
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
/**
|
|
3
3
|
* F2: the header's measured state. The mode follows the header's own width (so a framed specimen behaves like a real
|
|
4
|
-
* window):
|
|
4
|
+
* window): 1200 px and up `desktop`, 1024 to 1199 px `compact`, below 1024 px `phone`. The hook writes the frozen
|
|
5
5
|
* hooks on the header root (`data-mode`, `data-hydrated`, `data-compact`) and `--bl-header-height` on the document,
|
|
6
6
|
* which drives `scroll-padding-top` (WCAG 2.4.11).
|
|
7
7
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-header-metrics.js","sourceRoot":"","sources":["../../src/navigation/use-header-metrics.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAA;AAC3D,OAAO,EAAE,8BAA8B,EAAE,aAAa,EAAmB,MAAM,gBAAgB,CAAA;AAQ/F,MAAM,UAAU,gBAAgB,CAAC,OAAsC,EAAE,UAA4C,EAAE;IACrH,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,KAAK,CAAA;IAC9D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IAEtF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAA;QAC5B,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,CAAA;QACrC,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAA;YACxC,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACrC,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;YACxB,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,oBAAoB,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAC3E,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC3G,CAAC,CAAA;QACD,MAAM,QAAQ,GAAG,GAAG,EAAE;YACpB,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG,8BAA8B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA;QAC3F,CAAC,CAAA;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAA;QAC9B,OAAO,EAAE,CAAA;QACT,IAAI,QAAoC,CAAA;QACxC,IAAI,OAAO,cAAc,KAAK,UAAU,EAAE,CAAC;YACzC,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAA;YACtC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC5C,CAAC;QACD,IAAI,kBAAkB,EAAE,CAAC;YACvB,QAAQ,EAAE,CAAA;YACV,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAChE,CAAC;QACD,OAAO,GAAG,EAAE;YACV,QAAQ,EAAE,UAAU,EAAE,CAAA;YACtB,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;YAC7C,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;YAC9C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAA;YAC3B,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;QACjD,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAA;IAEjC,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["'use client'\n\n/**\n * F2: the header's measured state. The mode follows the header's own width (so a framed specimen behaves like a real\n * window):
|
|
1
|
+
{"version":3,"file":"use-header-metrics.js","sourceRoot":"","sources":["../../src/navigation/use-header-metrics.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAA;AAC3D,OAAO,EAAE,8BAA8B,EAAE,aAAa,EAAmB,MAAM,gBAAgB,CAAA;AAQ/F,MAAM,UAAU,gBAAgB,CAAC,OAAsC,EAAE,UAA4C,EAAE;IACrH,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,KAAK,CAAA;IAC9D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IAEtF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAA;QAC5B,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,CAAA;QACrC,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAA;YACxC,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACrC,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;YACxB,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,oBAAoB,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAC3E,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC3G,CAAC,CAAA;QACD,MAAM,QAAQ,GAAG,GAAG,EAAE;YACpB,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG,8BAA8B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA;QAC3F,CAAC,CAAA;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAA;QAC9B,OAAO,EAAE,CAAA;QACT,IAAI,QAAoC,CAAA;QACxC,IAAI,OAAO,cAAc,KAAK,UAAU,EAAE,CAAC;YACzC,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAA;YACtC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC5C,CAAC;QACD,IAAI,kBAAkB,EAAE,CAAC;YACvB,QAAQ,EAAE,CAAA;YACV,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAChE,CAAC;QACD,OAAO,GAAG,EAAE;YACV,QAAQ,EAAE,UAAU,EAAE,CAAA;YACtB,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;YAC7C,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;YAC9C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAA;YAC3B,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;QACjD,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAA;IAEjC,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["'use client'\n\n/**\n * F2: the header's measured state. The mode follows the header's own width (so a framed specimen behaves like a real\n * window): 1200 px and up `desktop`, 1024 to 1199 px `compact`, below 1024 px `phone`. The hook writes the frozen\n * hooks on the header root (`data-mode`, `data-hydrated`, `data-compact`) and `--bl-header-height` on the document,\n * which drives `scroll-padding-top` (WCAG 2.4.11).\n */\nimport { useEffect, useState, type RefObject } from 'react'\nimport { HEADER_COMPACT_AFTER_SCROLL_PX, headerModeFor, type HeaderMode } from './constants.js'\n\nexport interface HeaderMetrics {\n /** Null until the header has been measured after hydration. */\n readonly mode: HeaderMode | null\n readonly hydrated: boolean\n}\n\nexport function useHeaderMetrics(rootRef: RefObject<HTMLElement | null>, options: { compactAfterScroll?: boolean } = {}): HeaderMetrics {\n const compactAfterScroll = options.compactAfterScroll ?? false\n const [metrics, setMetrics] = useState<HeaderMetrics>({ mode: null, hydrated: false })\n\n useEffect(() => {\n const root = rootRef.current\n if (!root) return\n const html = document.documentElement\n const measure = () => {\n const box = root.getBoundingClientRect()\n const mode = headerModeFor(box.width)\n root.dataset.mode = mode\n html.style.setProperty('--bl-header-height', `${Math.round(box.height)}px`)\n setMetrics((current) => (current.mode === mode && current.hydrated ? current : { mode, hydrated: true }))\n }\n const onScroll = () => {\n root.dataset.compact = window.scrollY > HEADER_COMPACT_AFTER_SCROLL_PX ? 'true' : 'false'\n }\n root.dataset.hydrated = 'true'\n measure()\n let observer: ResizeObserver | undefined\n if (typeof ResizeObserver === 'function') {\n observer = new ResizeObserver(measure)\n observer.observe(root)\n } else {\n window.addEventListener('resize', measure)\n }\n if (compactAfterScroll) {\n onScroll()\n window.addEventListener('scroll', onScroll, { passive: true })\n }\n return () => {\n observer?.disconnect()\n window.removeEventListener('resize', measure)\n window.removeEventListener('scroll', onScroll)\n delete root.dataset.compact\n html.style.removeProperty('--bl-header-height')\n }\n }, [rootRef, compactAfterScroll])\n\n return metrics\n}\n"]}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* screen (the hero's own call and quote buttons, marked `data-bl-call-path`) has scrolled out, and hides again while
|
|
4
4
|
* a call path, the page footer or an open dialog (the drawer, the quote dialog) is on screen, while a form field has
|
|
5
5
|
* focus (v2) and while focus is inside the footer or a call path. The hook keeps `--bl-callbar-height` at the bar's height while it shows and `0px` otherwise, which
|
|
6
|
-
* drives `scroll-padding-bottom` (WCAG 2.4.11) and
|
|
6
|
+
* drives `scroll-padding-bottom` (WCAG 2.4.11), and publishes its state (`data-bl-callbar` on the root element and a
|
|
7
|
+
* `bl:callbar` event) so back-to-top stays hidden while the bar shows: on a phone the button never covers the bar.
|
|
7
8
|
*/
|
|
8
9
|
import { type RefObject } from 'react';
|
|
9
10
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-sticky-call-bar.d.ts","sourceRoot":"","sources":["../../src/navigation/use-sticky-call-bar.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"use-sticky-call-bar.d.ts","sourceRoot":"","sources":["../../src/navigation/use-sticky-call-bar.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAyCjE;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAmEnG"}
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* screen (the hero's own call and quote buttons, marked `data-bl-call-path`) has scrolled out, and hides again while
|
|
5
5
|
* a call path, the page footer or an open dialog (the drawer, the quote dialog) is on screen, while a form field has
|
|
6
6
|
* focus (v2) and while focus is inside the footer or a call path. The hook keeps `--bl-callbar-height` at the bar's height while it shows and `0px` otherwise, which
|
|
7
|
-
* drives `scroll-padding-bottom` (WCAG 2.4.11) and
|
|
7
|
+
* drives `scroll-padding-bottom` (WCAG 2.4.11), and publishes its state (`data-bl-callbar` on the root element and a
|
|
8
|
+
* `bl:callbar` event) so back-to-top stays hidden while the bar shows: on a phone the button never covers the bar.
|
|
8
9
|
*/
|
|
9
10
|
import { useLayoutEffect, useState } from 'react';
|
|
10
|
-
import { CALL_BAR_MEDIA_QUERY, CALL_PATH_SELECTOR } from './constants.js';
|
|
11
|
+
import { CALL_BAR_MEDIA_QUERY, CALL_BAR_STATE_ATTRIBUTE, CALL_BAR_STATE_EVENT, CALL_PATH_SELECTOR } from './constants.js';
|
|
11
12
|
const FIELD_SELECTOR = [
|
|
12
13
|
'input:not([type="button"], [type="submit"], [type="reset"], [type="checkbox"], [type="radio"], [type="hidden"])',
|
|
13
14
|
'textarea',
|
|
@@ -58,10 +59,16 @@ export function useStickyCallBar(rootRef, resetKey) {
|
|
|
58
59
|
const onScreen = new Set();
|
|
59
60
|
let fieldFocused = isField(document.activeElement);
|
|
60
61
|
let focusHidden = inHideZone(document.activeElement);
|
|
62
|
+
let published;
|
|
61
63
|
const update = () => {
|
|
62
64
|
const next = (narrow?.matches ?? false) && onScreen.size === 0 && !fieldFocused && !focusHidden && !document.querySelector('dialog[open]');
|
|
63
65
|
setShown(next);
|
|
64
66
|
html.style.setProperty('--bl-callbar-height', next ? `${Math.round(root.getBoundingClientRect().height)}px` : '0px');
|
|
67
|
+
if (next === published)
|
|
68
|
+
return;
|
|
69
|
+
published = next;
|
|
70
|
+
html.setAttribute(CALL_BAR_STATE_ATTRIBUTE, next ? 'shown' : 'hidden');
|
|
71
|
+
document.dispatchEvent(new CustomEvent(CALL_BAR_STATE_EVENT, { detail: { shown: next } }));
|
|
65
72
|
};
|
|
66
73
|
let intersections;
|
|
67
74
|
if (typeof IntersectionObserver === 'function') {
|
|
@@ -103,6 +110,8 @@ export function useStickyCallBar(rootRef, resetKey) {
|
|
|
103
110
|
document.removeEventListener('focusout', onFocusOut);
|
|
104
111
|
narrow?.removeEventListener('change', update);
|
|
105
112
|
html.style.removeProperty('--bl-callbar-height');
|
|
113
|
+
html.removeAttribute(CALL_BAR_STATE_ATTRIBUTE);
|
|
114
|
+
document.dispatchEvent(new CustomEvent(CALL_BAR_STATE_EVENT, { detail: { shown: false } }));
|
|
106
115
|
};
|
|
107
116
|
}, [rootRef, resetKey]);
|
|
108
117
|
return shown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-sticky-call-bar.js","sourceRoot":"","sources":["../../src/navigation/use-sticky-call-bar.ts"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\n/**\n * F6: when the phone call bar shows. TotalCare's logic, ported: below 1024 px the bar shows once every call path on\n * screen (the hero's own call and quote buttons, marked `data-bl-call-path`) has scrolled out, and hides again while\n * a call path, the page footer or an open dialog (the drawer, the quote dialog) is on screen, while a form field has\n * focus (v2) and while focus is inside the footer or a call path. The hook keeps `--bl-callbar-height` at the bar's height while it shows and `0px` otherwise, which\n * drives `scroll-padding-bottom` (WCAG 2.4.11) and lifts back-to-top above the bar.\n */\nimport { useLayoutEffect, useState, type RefObject } from 'react'\nimport { CALL_BAR_MEDIA_QUERY, CALL_PATH_SELECTOR } from './constants.js'\n\nconst FIELD_SELECTOR = [\n 'input:not([type=\"button\"], [type=\"submit\"], [type=\"reset\"], [type=\"checkbox\"], [type=\"radio\"], [type=\"hidden\"])',\n 'textarea',\n 'select',\n '[contenteditable=\"\"]',\n '[contenteditable=\"true\"]',\n].join(', ')\n\nfunction isField(target: EventTarget | null): boolean {\n return target instanceof Element && target.matches(FIELD_SELECTOR)\n}\n\n/** The page footer: the shared footer, or a `<footer>` that isn't inside an article, aside, main, nav or section. */\nfunction isPageFooter(element: Element): boolean {\n return element.matches('[data-bl=\"footer\"]') || !element.parentElement?.closest('article, aside, main, nav, section')\n}\n\nfunction pageFooters(): Element[] {\n return Array.from(document.querySelectorAll('footer, [data-bl=\"footer\"]')).filter(isPageFooter)\n}\n\n/**\n * Focus inside the footer or a call path hides the bar at once, before the observer reports (fast Tab stops would\n * otherwise sit under the bar, K5).\n */\nfunction inHideZone(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false\n if (target.closest(CALL_PATH_SELECTOR)) return true\n const footer = target.closest('footer, [data-bl=\"footer\"]')\n return footer !== null && isPageFooter(footer)\n}\n\n/** True when any part of the element's box is inside the viewport. */\nfunction inViewport(element: Element): boolean {\n const box = element.getBoundingClientRect()\n return box.bottom > 0 && box.right > 0 && box.top < window.innerHeight && box.left < window.innerWidth\n}\n\n/**\n * True while the bar should show. `resetKey` (the route) re-collects the call paths and the footer. The first state\n * is seeded from their boxes before paint, so the bar never flashes on load or on a client navigation while the\n * observer's first report is pending.\n */\nexport function useStickyCallBar(rootRef: RefObject<HTMLElement | null>, resetKey: unknown): boolean {\n const [shown, setShown] = useState(false)\n\n useLayoutEffect(() => {\n const root = rootRef.current\n if (!root) return\n const html = document.documentElement\n const narrow = typeof window.matchMedia === 'function' ? window.matchMedia(CALL_BAR_MEDIA_QUERY) : null\n const onScreen = new Set<Element>()\n let fieldFocused = isField(document.activeElement)\n let focusHidden = inHideZone(document.activeElement)\n\n const update = () => {\n const next = (narrow?.matches ?? false) && onScreen.size === 0 && !fieldFocused && !focusHidden && !document.querySelector('dialog[open]')\n setShown(next)\n html.style.setProperty('--bl-callbar-height', next ? `${Math.round(root.getBoundingClientRect().height)}px` : '0px')\n }\n\n let intersections: IntersectionObserver | undefined\n if (typeof IntersectionObserver === 'function') {\n intersections = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) onScreen.add(entry.target)\n else onScreen.delete(entry.target)\n }\n update()\n })\n }\n for (const target of [...document.querySelectorAll(CALL_PATH_SELECTOR), ...pageFooters()]) {\n if (inViewport(target)) onScreen.add(target)\n intersections?.observe(target)\n }\n const dialogs = new MutationObserver(update)\n dialogs.observe(document.body, { subtree: true, attributes: true, attributeFilter: ['open'] })\n const onFocusIn = (event: FocusEvent) => {\n fieldFocused = isField(event.target)\n focusHidden = inHideZone(event.target)\n update()\n }\n const onFocusOut = (event: FocusEvent) => {\n fieldFocused = isField(event.relatedTarget)\n focusHidden = inHideZone(event.relatedTarget)\n update()\n }\n document.addEventListener('focusin', onFocusIn)\n document.addEventListener('focusout', onFocusOut)\n narrow?.addEventListener('change', update)\n update()\n\n return () => {\n intersections?.disconnect()\n dialogs.disconnect()\n document.removeEventListener('focusin', onFocusIn)\n document.removeEventListener('focusout', onFocusOut)\n narrow?.removeEventListener('change', update)\n html.style.removeProperty('--bl-callbar-height')\n }\n }, [rootRef, resetKey])\n\n return shown\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-sticky-call-bar.js","sourceRoot":"","sources":["../../src/navigation/use-sticky-call-bar.ts"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\n/**\n * F6: when the phone call bar shows. TotalCare's logic, ported: below 1024 px the bar shows once every call path on\n * screen (the hero's own call and quote buttons, marked `data-bl-call-path`) has scrolled out, and hides again while\n * a call path, the page footer or an open dialog (the drawer, the quote dialog) is on screen, while a form field has\n * focus (v2) and while focus is inside the footer or a call path. The hook keeps `--bl-callbar-height` at the bar's height while it shows and `0px` otherwise, which\n * drives `scroll-padding-bottom` (WCAG 2.4.11), and publishes its state (`data-bl-callbar` on the root element and a\n * `bl:callbar` event) so back-to-top stays hidden while the bar shows: on a phone the button never covers the bar.\n */\nimport { useLayoutEffect, useState, type RefObject } from 'react'\nimport { CALL_BAR_MEDIA_QUERY, CALL_BAR_STATE_ATTRIBUTE, CALL_BAR_STATE_EVENT, CALL_PATH_SELECTOR } from './constants.js'\n\nconst FIELD_SELECTOR = [\n 'input:not([type=\"button\"], [type=\"submit\"], [type=\"reset\"], [type=\"checkbox\"], [type=\"radio\"], [type=\"hidden\"])',\n 'textarea',\n 'select',\n '[contenteditable=\"\"]',\n '[contenteditable=\"true\"]',\n].join(', ')\n\nfunction isField(target: EventTarget | null): boolean {\n return target instanceof Element && target.matches(FIELD_SELECTOR)\n}\n\n/** The page footer: the shared footer, or a `<footer>` that isn't inside an article, aside, main, nav or section. */\nfunction isPageFooter(element: Element): boolean {\n return element.matches('[data-bl=\"footer\"]') || !element.parentElement?.closest('article, aside, main, nav, section')\n}\n\nfunction pageFooters(): Element[] {\n return Array.from(document.querySelectorAll('footer, [data-bl=\"footer\"]')).filter(isPageFooter)\n}\n\n/**\n * Focus inside the footer or a call path hides the bar at once, before the observer reports (fast Tab stops would\n * otherwise sit under the bar, K5).\n */\nfunction inHideZone(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false\n if (target.closest(CALL_PATH_SELECTOR)) return true\n const footer = target.closest('footer, [data-bl=\"footer\"]')\n return footer !== null && isPageFooter(footer)\n}\n\n/** True when any part of the element's box is inside the viewport. */\nfunction inViewport(element: Element): boolean {\n const box = element.getBoundingClientRect()\n return box.bottom > 0 && box.right > 0 && box.top < window.innerHeight && box.left < window.innerWidth\n}\n\n/**\n * True while the bar should show. `resetKey` (the route) re-collects the call paths and the footer. The first state\n * is seeded from their boxes before paint, so the bar never flashes on load or on a client navigation while the\n * observer's first report is pending.\n */\nexport function useStickyCallBar(rootRef: RefObject<HTMLElement | null>, resetKey: unknown): boolean {\n const [shown, setShown] = useState(false)\n\n useLayoutEffect(() => {\n const root = rootRef.current\n if (!root) return\n const html = document.documentElement\n const narrow = typeof window.matchMedia === 'function' ? window.matchMedia(CALL_BAR_MEDIA_QUERY) : null\n const onScreen = new Set<Element>()\n let fieldFocused = isField(document.activeElement)\n let focusHidden = inHideZone(document.activeElement)\n\n let published: boolean | undefined\n const update = () => {\n const next = (narrow?.matches ?? false) && onScreen.size === 0 && !fieldFocused && !focusHidden && !document.querySelector('dialog[open]')\n setShown(next)\n html.style.setProperty('--bl-callbar-height', next ? `${Math.round(root.getBoundingClientRect().height)}px` : '0px')\n if (next === published) return\n published = next\n html.setAttribute(CALL_BAR_STATE_ATTRIBUTE, next ? 'shown' : 'hidden')\n document.dispatchEvent(new CustomEvent(CALL_BAR_STATE_EVENT, { detail: { shown: next } }))\n }\n\n let intersections: IntersectionObserver | undefined\n if (typeof IntersectionObserver === 'function') {\n intersections = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) onScreen.add(entry.target)\n else onScreen.delete(entry.target)\n }\n update()\n })\n }\n for (const target of [...document.querySelectorAll(CALL_PATH_SELECTOR), ...pageFooters()]) {\n if (inViewport(target)) onScreen.add(target)\n intersections?.observe(target)\n }\n const dialogs = new MutationObserver(update)\n dialogs.observe(document.body, { subtree: true, attributes: true, attributeFilter: ['open'] })\n const onFocusIn = (event: FocusEvent) => {\n fieldFocused = isField(event.target)\n focusHidden = inHideZone(event.target)\n update()\n }\n const onFocusOut = (event: FocusEvent) => {\n fieldFocused = isField(event.relatedTarget)\n focusHidden = inHideZone(event.relatedTarget)\n update()\n }\n document.addEventListener('focusin', onFocusIn)\n document.addEventListener('focusout', onFocusOut)\n narrow?.addEventListener('change', update)\n update()\n\n return () => {\n intersections?.disconnect()\n dialogs.disconnect()\n document.removeEventListener('focusin', onFocusIn)\n document.removeEventListener('focusout', onFocusOut)\n narrow?.removeEventListener('change', update)\n html.style.removeProperty('--bl-callbar-height')\n html.removeAttribute(CALL_BAR_STATE_ATTRIBUTE)\n document.dispatchEvent(new CustomEvent(CALL_BAR_STATE_EVENT, { detail: { shown: false } }))\n }\n }, [rootRef, resetKey])\n\n return shown\n}\n"]}
|