@brandlab-digital/site-next 3.12.1 → 3.13.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/README.md +53 -0
- package/dist/estate-segment.d.ts +48 -0
- package/dist/estate-segment.d.ts.map +1 -0
- package/dist/estate-segment.js +54 -0
- package/dist/estate-segment.js.map +1 -0
- package/dist/google-business-reviews-revalidate.d.ts +61 -0
- package/dist/google-business-reviews-revalidate.d.ts.map +1 -0
- package/dist/google-business-reviews-revalidate.js +151 -0
- package/dist/google-business-reviews-revalidate.js.map +1 -0
- package/dist/google-business-reviews-server.d.ts +21 -2
- package/dist/google-business-reviews-server.d.ts.map +1 -1
- package/dist/google-business-reviews-server.js +21 -4
- package/dist/google-business-reviews-server.js.map +1 -1
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +6 -0
- package/dist/headers.js.map +1 -1
- package/dist/robots.d.ts +26 -4
- package/dist/robots.d.ts.map +1 -1
- package/dist/robots.js +49 -2
- package/dist/robots.js.map +1 -1
- package/dist/ui-kit/prefetch.d.ts +50 -0
- package/dist/ui-kit/prefetch.d.ts.map +1 -0
- package/dist/ui-kit/prefetch.js +50 -0
- package/dist/ui-kit/prefetch.js.map +1 -0
- package/dist/ui-kit/primitives/index.d.ts +1 -0
- package/dist/ui-kit/primitives/index.d.ts.map +1 -1
- package/dist/ui-kit/primitives/index.js +1 -0
- package/dist/ui-kit/primitives/index.js.map +1 -1
- package/dist/ui-kit/primitives/links.d.ts +13 -2
- package/dist/ui-kit/primitives/links.d.ts.map +1 -1
- package/dist/ui-kit/primitives/links.js +7 -6
- package/dist/ui-kit/primitives/links.js.map +1 -1
- package/dist/ui-kit/primitives/quote-button.d.ts +4 -1
- package/dist/ui-kit/primitives/quote-button.d.ts.map +1 -1
- package/dist/ui-kit/primitives/quote-button.js +3 -2
- package/dist/ui-kit/primitives/quote-button.js.map +1 -1
- package/dist/ui-kit/sections/site-footer.d.ts.map +1 -1
- package/dist/ui-kit/sections/site-footer.js +5 -2
- package/dist/ui-kit/sections/site-footer.js.map +1 -1
- package/dist/ui-kit/shell/current-link.d.ts +15 -1
- package/dist/ui-kit/shell/current-link.d.ts.map +1 -1
- package/dist/ui-kit/shell/current-link.js +10 -1
- package/dist/ui-kit/shell/current-link.js.map +1 -1
- package/dist/ui-kit/shell/index.d.ts +1 -0
- package/dist/ui-kit/shell/index.d.ts.map +1 -1
- package/dist/ui-kit/shell/index.js +1 -0
- package/dist/ui-kit/shell/index.js.map +1 -1
- package/dist/ui-kit/shell/intent-prefetch.d.ts +4 -0
- package/dist/ui-kit/shell/intent-prefetch.d.ts.map +1 -0
- package/dist/ui-kit/shell/intent-prefetch.js +61 -0
- package/dist/ui-kit/shell/intent-prefetch.js.map +1 -0
- package/dist/ui-kit/shell/site-header.d.ts.map +1 -1
- package/dist/ui-kit/shell/site-header.js +2 -3
- package/dist/ui-kit/shell/site-header.js.map +1 -1
- package/package.json +6 -2
package/dist/headers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Brandlab security/response headers: one canonical header set for every\n * client site, emitted by `defineBrandlabNextConfig`.\n *\n * The deployment decides everything environment-specific:\n * - a production build (`next build`, on Vercel or locally) enforces the\n * production Content Security Policy and sends HSTS;\n * - `next dev` sends a report-only development policy without HSTS;\n * - a Vercel Preview additionally sends `noindex, nofollow, noarchive` and\n * `private, no-store`, so previews never reach search results or caches.\n *\n * The production X-Robots-Tag carries snippet-display controls only; per-page\n * indexation stays owned by Metadata.robots.\n *\n * Declared public folders of static files (`staticAssetCache`) are served with\n * `public, max-age=31536000, immutable` by production builds; Previews and\n * `next dev` never cache them.\n */\n\nimport {\n isBrandlabHostname,\n type BrandlabDeployment,\n type BrandlabDeploymentEnvironment,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabHeader {\n key: string\n value: string\n}\n\nexport interface BrandlabHeaderRule {\n source: string\n headers: BrandlabHeader[]\n /** The rule applies only when none of these match (Next `missing`: host values are anchored regular expressions). */\n missing?: { type: 'host'; value: string }[]\n}\n\nexport const BRANDLAB_DEFAULT_PERMISSIONS_POLICY =\n 'camera=(), microphone=(), geolocation=()'\n\nexport const BRANDLAB_DEFAULT_X_ROBOTS_TAG =\n 'max-snippet:-1, max-image-preview:large, max-video-preview:-1'\n\n/**\n * What a production deployment answers on any host but its canonical one (3.2.1): the `<project>.vercel.app` alias\n * and the deployment's own URLs serve the full site, so they tell crawlers not to index it. Alias hosts redirect.\n */\nexport const BRANDLAB_NON_CANONICAL_HOST_X_ROBOTS_TAG = 'noindex'\n\n/** Host patterns a production build answers normally besides its canonical host: a local `next start` (CI, the harness). */\nconst LOCAL_HOST_PATTERNS = ['localhost', '127\\\\.0\\\\.0\\\\.1']\n\n/** A host as an exact pattern (Next anchors `has`/`missing` values as regular expressions). */\nfunction exactHostPattern(host: string): string {\n return host.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Production deployments answer every host but the canonical one (and a local server's) with\n * `X-Robots-Tag: noindex`: the `*.vercel.app` production alias and deployment URLs serve the full site. The rule\n * comes last, so it overrides the default X-Robots-Tag there; the canonical host keeps the default.\n */\nexport function buildNonCanonicalHostRule(canonicalHost: string): BrandlabHeaderRule {\n // Next compares a request's host lowercased and without its port: a host in any other form would match no request,\n // and every host, the canonical one included, would be told noindex.\n const host = typeof canonicalHost === 'string' ? canonicalHost.toLowerCase() : canonicalHost\n if (!isBrandlabHostname(host)) {\n throw new Error(`The canonical host must be a bare hostname (no scheme, port, path or trailing dot): \"${String(canonicalHost)}\".`)\n }\n return {\n source: '/:path*',\n missing: [exactHostPattern(host), ...LOCAL_HOST_PATTERNS].map((value) => ({ type: 'host' as const, value })),\n headers: [{ key: 'X-Robots-Tag', value: BRANDLAB_NON_CANONICAL_HOST_X_ROBOTS_TAG }],\n }\n}\n\nexport const BRANDLAB_PREVIEW_X_ROBOTS_TAG = 'noindex, nofollow, noarchive'\n\nexport const BRANDLAB_PREVIEW_CACHE_CONTROL = 'private, no-store, max-age=0'\n\nexport const BRANDLAB_HSTS_VALUE =\n 'max-age=31536000'\n\n/** Cache-Control for the declared static public folders on production builds. */\nexport const BRANDLAB_STATIC_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable'\n\nexport interface BrandlabHstsExpansion {\n includeSubDomains: true\n preload?: true\n /** Reviewed proof that every affected subdomain is HTTPS-ready. */\n readinessEvidence: string\n}\n\nconst HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\nconst PROTECTED_HEADER_NAMES = new Set([\n 'cache-control',\n 'content-security-policy',\n 'content-security-policy-report-only',\n 'x-frame-options',\n 'x-content-type-options',\n 'referrer-policy',\n 'permissions-policy',\n 'x-robots-tag',\n 'strict-transport-security',\n])\nconst DEPLOYMENT_ENVIRONMENTS = new Set<BrandlabDeploymentEnvironment>([\n 'production',\n 'preview',\n 'development',\n])\n\nexport interface BrandlabContentSecurityPolicyExtensions {\n scriptSrc?: readonly string[]\n connectSrc?: readonly string[]\n frameSrc?: readonly string[]\n}\n\nexport interface BrandlabSecurityHeaderOptions {\n /** From resolveBrandlabDeployment(process.env). */\n deployment: BrandlabDeployment\n hsts?: BrandlabHstsExpansion\n /** Reviewed HTTPS origins needed by an approved project integration. */\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n /** Reviewed project exception, for example `geolocation=(self)` for a location feature. */\n permissionsPolicy?: string\n /** Reviewed project headers; they cannot replace a protected header. */\n extraHeaders?: readonly BrandlabHeader[]\n}\n\nfunction assertDeployment(deployment: unknown): asserts deployment is BrandlabDeployment {\n const candidate = deployment as Partial<BrandlabDeployment> | undefined\n if (\n !candidate ||\n typeof candidate !== 'object' ||\n !DEPLOYMENT_ENVIRONMENTS.has(candidate.environment as BrandlabDeploymentEnvironment) ||\n typeof candidate.productionBuild !== 'boolean'\n ) {\n throw new Error('Header rules require the deployment from resolveBrandlabDeployment(process.env).')\n }\n}\n\nexport function buildBrandlabContentSecurityPolicy(options: {\n deployment: BrandlabDeployment\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n}) {\n assertDeployment(options.deployment)\n const productionBuild = options.deployment.productionBuild\n\n const scriptSources = [\n \"'self'\",\n \"'unsafe-inline'\",\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://www.google.com',\n 'https://www.gstatic.com',\n 'https://pagead2.googlesyndication.com',\n 'https://googleads.g.doubleclick.net',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.scriptSrc, 'scriptSrc'),\n ...(productionBuild ? [] : [\"'unsafe-eval'\", 'http://localhost:*']),\n ]\n const connectSources = [\n \"'self'\",\n 'https://www.google-analytics.com',\n 'https://*.google-analytics.com',\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://*.doubleclick.net',\n 'https://*.google.com',\n 'https://google.com',\n 'https://www.google.com.au',\n 'https://pagead2.googlesyndication.com',\n 'https://*.googletagmanager.com',\n 'https://*.analytics.google.com',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.connectSrc, 'connectSrc'),\n ...(productionBuild ? [] : ['http://localhost:*', 'ws://localhost:*']),\n ]\n const directives = [\n \"default-src 'self'\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n \"frame-ancestors 'none'\",\n \"form-action 'self'\",\n `script-src ${scriptSources.join(' ')}`,\n \"style-src 'self' 'unsafe-inline'\",\n \"font-src 'self' data:\",\n \"img-src 'self' data: blob: https:\",\n `connect-src ${connectSources.join(' ')}`,\n `frame-src 'self' https://challenges.cloudflare.com https://www.googletagmanager.com https://www.google.com ${normalizeCspExtensions(\n options.contentSecurityPolicyExtensions?.frameSrc,\n 'frameSrc',\n ).join(' ')}`.trim(),\n \"manifest-src 'self'\",\n \"worker-src 'self' blob:\",\n ...(productionBuild ? ['upgrade-insecure-requests'] : []),\n ]\n return directives.join('; ')\n}\n\nfunction normalizeCspExtensions(\n sources: readonly string[] | undefined,\n field: keyof BrandlabContentSecurityPolicyExtensions,\n) {\n if (!sources) return []\n const normalized = sources.map((source) => {\n const value = source.trim().toLowerCase()\n if (!/^https:\\/\\/(?:\\*\\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::\\d{1,5})?$/.test(value)) {\n throw new Error(`${field} entries must be HTTPS origins without paths or CSP keywords.`)\n }\n return value\n })\n if (new Set(normalized).size !== normalized.length) {\n throw new Error(`${field} entries must be unique.`)\n }\n return normalized\n}\n\nfunction assertHeaderValue(value: string, field: string) {\n if (!value || value.length > 4_096 || /[\\u0000-\\u001F\\u007F]/.test(value)) {\n throw new Error(`${field} must be a bounded single-line header value.`)\n }\n}\n\nfunction validateExtraHeaders(headers: readonly BrandlabHeader[]) {\n const names = new Set<string>()\n headers.forEach((header, index) => {\n if (!HEADER_NAME_PATTERN.test(header.key)) {\n throw new Error(`extraHeaders[${index}].key is invalid.`)\n }\n const name = header.key.toLowerCase()\n if (PROTECTED_HEADER_NAMES.has(name) || names.has(name)) {\n throw new Error(`extraHeaders[${index}] duplicates a protected or existing header.`)\n }\n names.add(name)\n assertHeaderValue(header.value, `extraHeaders[${index}].value`)\n })\n}\n\nconst HEADER_OPTION_KEYS = new Set([\n 'deployment',\n 'hsts',\n 'contentSecurityPolicyExtensions',\n 'permissionsPolicy',\n 'extraHeaders',\n])\n\nexport function buildBrandlabSecurityHeaders(\n options: BrandlabSecurityHeaderOptions,\n): BrandlabHeader[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const unsupported = Object.keys(options).filter((key) => !HEADER_OPTION_KEYS.has(key))\n if (unsupported.length > 0) {\n throw new Error(\n `Header options contain unsupported keys: ${unsupported.join(', ')}. The deployment decides CSP, HSTS and X-Robots-Tag.`,\n )\n }\n assertDeployment(options.deployment)\n const { environment, productionBuild } = options.deployment\n const permissionsPolicy =\n options.permissionsPolicy ?? BRANDLAB_DEFAULT_PERMISSIONS_POLICY\n assertHeaderValue(permissionsPolicy, 'permissionsPolicy')\n validateExtraHeaders(options.extraHeaders ?? [])\n if (options.hsts) {\n assertHeaderValue(options.hsts.readinessEvidence, 'hsts.readinessEvidence')\n if (options.hsts.preload && !options.hsts.includeSubDomains) {\n throw new Error('HSTS preload requires includeSubDomains readiness.')\n }\n }\n const hstsValue = options.hsts\n ? `${BRANDLAB_HSTS_VALUE}; includeSubDomains${options.hsts.preload ? '; preload' : ''}`\n : BRANDLAB_HSTS_VALUE\n const preview = environment === 'preview'\n\n return [\n { key: 'X-Frame-Options', value: 'DENY' },\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n {\n key: 'Permissions-Policy',\n value: permissionsPolicy,\n },\n {\n key: 'X-Robots-Tag',\n value: preview ? BRANDLAB_PREVIEW_X_ROBOTS_TAG : BRANDLAB_DEFAULT_X_ROBOTS_TAG,\n },\n ...(productionBuild\n ? [{ key: 'Strict-Transport-Security', value: hstsValue }]\n : []),\n {\n key: productionBuild\n ? 'Content-Security-Policy'\n : 'Content-Security-Policy-Report-Only',\n value: buildBrandlabContentSecurityPolicy(options),\n },\n ...(options.extraHeaders ?? []).map((header) => ({ ...header })),\n ...(preview ? [{ key: 'Cache-Control', value: BRANDLAB_PREVIEW_CACHE_CONTROL }] : []),\n ]\n}\n\nconst STATIC_ASSET_SOURCE = /^\\/((?:[A-Za-z0-9][A-Za-z0-9._-]*\\/){1,4}):([A-Za-z_][A-Za-z0-9_]*)\\*$/\nconst STATIC_ASSET_RESERVED = new Set(['api', '_next', '_vercel'])\nconst STATIC_ASSET_MAX_SOURCES = 10\nconst STATIC_ASSET_MAX_LENGTH = 100\n\nexport interface BrandlabStaticAssetFolder {\n /** The declared source, for example `/images/:path*`. */\n source: string\n /** The public folder's path segments, for example `['images']`. */\n folder: readonly string[]\n /**\n * The emitted rule's source: file names with an extension below the folder,\n * such as `/images/:path(.+\\.[A-Za-z0-9]+)`. The folder itself and\n * extensionless paths, where a page would answer, never match.\n */\n fileSource: string\n}\n\n/**\n * Validates `staticAssetCache`: at most 10 public folders, each written as Next's\n * source `/<folder>/:path*` (one to four literal segments, then one catch-all\n * parameter, at most 100 characters). `/`, `/api`, `/_next` and `/_vercel` are\n * rejected.\n */\nexport function parseBrandlabStaticAssetCache(sources: unknown): BrandlabStaticAssetFolder[] {\n if (!Array.isArray(sources)) {\n throw new Error('staticAssetCache must list public folders such as /images/:path*.')\n }\n if (sources.length > STATIC_ASSET_MAX_SOURCES) {\n throw new Error(`staticAssetCache lists at most ${STATIC_ASSET_MAX_SOURCES} public folders.`)\n }\n const seen = new Set<string>()\n return sources.map((source: unknown, index) => {\n const field = `staticAssetCache[${index}]`\n if (typeof source !== 'string' || source.length > STATIC_ASSET_MAX_LENGTH) {\n throw new Error(`${field} must be a source of at most ${STATIC_ASSET_MAX_LENGTH} characters.`)\n }\n if (STATIC_ASSET_RESERVED.has(source.split('/')[1]?.toLowerCase() ?? '')) {\n throw new Error(`${field} \"${source}\" is a Next or API path, not a public folder.`)\n }\n const match = STATIC_ASSET_SOURCE.exec(source)\n if (!match) {\n throw new Error(\n `${field} \"${source}\" must name a public folder as /<folder>/:path* (literal folder segments, then one catch-all parameter).`,\n )\n }\n const folder = match[1]!.slice(0, -1).split('/')\n const key = folder.join('/').toLowerCase()\n if (seen.has(key)) throw new Error(`${field} repeats the /${folder.join('/')} folder.`)\n seen.add(key)\n return { source, folder, fileSource: `/${folder.join('/')}/:${match[2]}(.+\\\\.[A-Za-z0-9]+)` }\n })\n}\n\nexport interface BrandlabHeaderRuleOptions extends BrandlabSecurityHeaderOptions {\n /**\n * Public folders of static files that never change in place, for example\n * `['/images/:path*', '/fonts/:path*']`: production builds serve their files with\n * `public, max-age=31536000, immutable`. A file replaced under the same name stays\n * cached in browsers for up to a year, so give a changed file a new name.\n */\n staticAssetCache?: readonly string[]\n /**\n * The canonical host (3.2.1): a production deployment answers every other host (the `*.vercel.app` alias, the\n * deployment URLs) with `X-Robots-Tag: noindex`.\n */\n canonicalHost?: string\n}\n\nexport function buildBrandlabHeaderRules(options: BrandlabHeaderRuleOptions): BrandlabHeaderRule[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const { staticAssetCache, canonicalHost, ...security } = options\n const folders = staticAssetCache === undefined ? [] : parseBrandlabStaticAssetCache(staticAssetCache)\n const rules: BrandlabHeaderRule[] = [\n {\n source: '/:path*',\n headers: buildBrandlabSecurityHeaders(security),\n },\n ]\n // A production build outside a Preview; Previews keep private, no-store everywhere.\n const { environment, productionBuild } = options.deployment\n if (productionBuild && environment !== 'preview') {\n for (const folder of folders) {\n rules.push({\n source: folder.fileSource,\n headers: [{ key: 'Cache-Control', value: BRANDLAB_STATIC_ASSET_CACHE_CONTROL }],\n })\n }\n }\n // Only a real production deployment: Previews already send noindex everywhere, and development has no crawlers.\n if (environment === 'production' && canonicalHost) rules.push(buildNonCanonicalHostRule(canonicalHost))\n return rules\n}\n"]}
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Brandlab security/response headers: one canonical header set for every\n * client site, emitted by `defineBrandlabNextConfig`.\n *\n * The deployment decides everything environment-specific:\n * - a production build (`next build`, on Vercel or locally) enforces the\n * production Content Security Policy and sends HSTS;\n * - `next dev` sends a report-only development policy without HSTS;\n * - a Vercel Preview additionally sends `noindex, nofollow, noarchive` and\n * `private, no-store`, so previews never reach search results or caches.\n *\n * The production X-Robots-Tag carries snippet-display controls only; per-page\n * indexation stays owned by Metadata.robots.\n *\n * Declared public folders of static files (`staticAssetCache`) are served with\n * `public, max-age=31536000, immutable` by production builds; Previews and\n * `next dev` never cache them.\n */\n\nimport {\n isBrandlabHostname,\n type BrandlabDeployment,\n type BrandlabDeploymentEnvironment,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabHeader {\n key: string\n value: string\n}\n\nexport interface BrandlabHeaderRule {\n source: string\n headers: BrandlabHeader[]\n /** The rule applies only when none of these match (Next `missing`: host values are anchored regular expressions). */\n missing?: { type: 'host'; value: string }[]\n}\n\nexport const BRANDLAB_DEFAULT_PERMISSIONS_POLICY =\n 'camera=(), microphone=(), geolocation=()'\n\nexport const BRANDLAB_DEFAULT_X_ROBOTS_TAG =\n 'max-snippet:-1, max-image-preview:large, max-video-preview:-1'\n\n/**\n * What a production deployment answers on any host but its canonical one (3.2.1): the `<project>.vercel.app` alias\n * and the deployment's own URLs serve the full site, so they tell crawlers not to index it. Alias hosts redirect.\n */\nexport const BRANDLAB_NON_CANONICAL_HOST_X_ROBOTS_TAG = 'noindex'\n\n/** Host patterns a production build answers normally besides its canonical host: a local `next start` (CI, the harness). */\nconst LOCAL_HOST_PATTERNS = ['localhost', '127\\\\.0\\\\.0\\\\.1']\n\n/** A host as an exact pattern (Next anchors `has`/`missing` values as regular expressions). */\nfunction exactHostPattern(host: string): string {\n return host.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Production deployments answer every host but the canonical one (and a local server's) with\n * `X-Robots-Tag: noindex`: the `*.vercel.app` production alias and deployment URLs serve the full site. The rule\n * comes last, so it overrides the default X-Robots-Tag there; the canonical host keeps the default.\n */\nexport function buildNonCanonicalHostRule(canonicalHost: string): BrandlabHeaderRule {\n // Next compares a request's host lowercased and without its port: a host in any other form would match no request,\n // and every host, the canonical one included, would be told noindex.\n const host = typeof canonicalHost === 'string' ? canonicalHost.toLowerCase() : canonicalHost\n if (!isBrandlabHostname(host)) {\n throw new Error(`The canonical host must be a bare hostname (no scheme, port, path or trailing dot): \"${String(canonicalHost)}\".`)\n }\n return {\n source: '/:path*',\n missing: [exactHostPattern(host), ...LOCAL_HOST_PATTERNS].map((value) => ({ type: 'host' as const, value })),\n headers: [{ key: 'X-Robots-Tag', value: BRANDLAB_NON_CANONICAL_HOST_X_ROBOTS_TAG }],\n }\n}\n\nexport const BRANDLAB_PREVIEW_X_ROBOTS_TAG = 'noindex, nofollow, noarchive'\n\nexport const BRANDLAB_PREVIEW_CACHE_CONTROL = 'private, no-store, max-age=0'\n\nexport const BRANDLAB_HSTS_VALUE =\n 'max-age=31536000'\n\n/** Cache-Control for the declared static public folders on production builds. */\nexport const BRANDLAB_STATIC_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable'\n\nexport interface BrandlabHstsExpansion {\n includeSubDomains: true\n preload?: true\n /** Reviewed proof that every affected subdomain is HTTPS-ready. */\n readinessEvidence: string\n}\n\nconst HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\n/**\n * Headers a project's `extraHeaders` may not set. The CDN cache headers (3.13.0) sit beside `cache-control`: a\n * hand-set `CDN-Cache-Control` or `Vercel-CDN-Cache-Control` would change what Vercel caches without review.\n */\nconst PROTECTED_HEADER_NAMES = new Set([\n 'cache-control',\n 'cdn-cache-control',\n 'vercel-cdn-cache-control',\n 'content-security-policy',\n 'content-security-policy-report-only',\n 'x-frame-options',\n 'x-content-type-options',\n 'referrer-policy',\n 'permissions-policy',\n 'x-robots-tag',\n 'strict-transport-security',\n])\nconst DEPLOYMENT_ENVIRONMENTS = new Set<BrandlabDeploymentEnvironment>([\n 'production',\n 'preview',\n 'development',\n])\n\nexport interface BrandlabContentSecurityPolicyExtensions {\n scriptSrc?: readonly string[]\n connectSrc?: readonly string[]\n frameSrc?: readonly string[]\n}\n\nexport interface BrandlabSecurityHeaderOptions {\n /** From resolveBrandlabDeployment(process.env). */\n deployment: BrandlabDeployment\n hsts?: BrandlabHstsExpansion\n /** Reviewed HTTPS origins needed by an approved project integration. */\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n /** Reviewed project exception, for example `geolocation=(self)` for a location feature. */\n permissionsPolicy?: string\n /** Reviewed project headers; they cannot replace a protected header. */\n extraHeaders?: readonly BrandlabHeader[]\n}\n\nfunction assertDeployment(deployment: unknown): asserts deployment is BrandlabDeployment {\n const candidate = deployment as Partial<BrandlabDeployment> | undefined\n if (\n !candidate ||\n typeof candidate !== 'object' ||\n !DEPLOYMENT_ENVIRONMENTS.has(candidate.environment as BrandlabDeploymentEnvironment) ||\n typeof candidate.productionBuild !== 'boolean'\n ) {\n throw new Error('Header rules require the deployment from resolveBrandlabDeployment(process.env).')\n }\n}\n\nexport function buildBrandlabContentSecurityPolicy(options: {\n deployment: BrandlabDeployment\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n}) {\n assertDeployment(options.deployment)\n const productionBuild = options.deployment.productionBuild\n\n const scriptSources = [\n \"'self'\",\n \"'unsafe-inline'\",\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://www.google.com',\n 'https://www.gstatic.com',\n 'https://pagead2.googlesyndication.com',\n 'https://googleads.g.doubleclick.net',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.scriptSrc, 'scriptSrc'),\n ...(productionBuild ? [] : [\"'unsafe-eval'\", 'http://localhost:*']),\n ]\n const connectSources = [\n \"'self'\",\n 'https://www.google-analytics.com',\n 'https://*.google-analytics.com',\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://*.doubleclick.net',\n 'https://*.google.com',\n 'https://google.com',\n 'https://www.google.com.au',\n 'https://pagead2.googlesyndication.com',\n 'https://*.googletagmanager.com',\n 'https://*.analytics.google.com',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.connectSrc, 'connectSrc'),\n ...(productionBuild ? [] : ['http://localhost:*', 'ws://localhost:*']),\n ]\n const directives = [\n \"default-src 'self'\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n \"frame-ancestors 'none'\",\n \"form-action 'self'\",\n `script-src ${scriptSources.join(' ')}`,\n \"style-src 'self' 'unsafe-inline'\",\n \"font-src 'self' data:\",\n \"img-src 'self' data: blob: https:\",\n `connect-src ${connectSources.join(' ')}`,\n `frame-src 'self' https://challenges.cloudflare.com https://www.googletagmanager.com https://www.google.com ${normalizeCspExtensions(\n options.contentSecurityPolicyExtensions?.frameSrc,\n 'frameSrc',\n ).join(' ')}`.trim(),\n \"manifest-src 'self'\",\n \"worker-src 'self' blob:\",\n ...(productionBuild ? ['upgrade-insecure-requests'] : []),\n ]\n return directives.join('; ')\n}\n\nfunction normalizeCspExtensions(\n sources: readonly string[] | undefined,\n field: keyof BrandlabContentSecurityPolicyExtensions,\n) {\n if (!sources) return []\n const normalized = sources.map((source) => {\n const value = source.trim().toLowerCase()\n if (!/^https:\\/\\/(?:\\*\\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::\\d{1,5})?$/.test(value)) {\n throw new Error(`${field} entries must be HTTPS origins without paths or CSP keywords.`)\n }\n return value\n })\n if (new Set(normalized).size !== normalized.length) {\n throw new Error(`${field} entries must be unique.`)\n }\n return normalized\n}\n\nfunction assertHeaderValue(value: string, field: string) {\n if (!value || value.length > 4_096 || /[\\u0000-\\u001F\\u007F]/.test(value)) {\n throw new Error(`${field} must be a bounded single-line header value.`)\n }\n}\n\nfunction validateExtraHeaders(headers: readonly BrandlabHeader[]) {\n const names = new Set<string>()\n headers.forEach((header, index) => {\n if (!HEADER_NAME_PATTERN.test(header.key)) {\n throw new Error(`extraHeaders[${index}].key is invalid.`)\n }\n const name = header.key.toLowerCase()\n if (PROTECTED_HEADER_NAMES.has(name) || names.has(name)) {\n throw new Error(`extraHeaders[${index}] duplicates a protected or existing header.`)\n }\n names.add(name)\n assertHeaderValue(header.value, `extraHeaders[${index}].value`)\n })\n}\n\nconst HEADER_OPTION_KEYS = new Set([\n 'deployment',\n 'hsts',\n 'contentSecurityPolicyExtensions',\n 'permissionsPolicy',\n 'extraHeaders',\n])\n\nexport function buildBrandlabSecurityHeaders(\n options: BrandlabSecurityHeaderOptions,\n): BrandlabHeader[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const unsupported = Object.keys(options).filter((key) => !HEADER_OPTION_KEYS.has(key))\n if (unsupported.length > 0) {\n throw new Error(\n `Header options contain unsupported keys: ${unsupported.join(', ')}. The deployment decides CSP, HSTS and X-Robots-Tag.`,\n )\n }\n assertDeployment(options.deployment)\n const { environment, productionBuild } = options.deployment\n const permissionsPolicy =\n options.permissionsPolicy ?? BRANDLAB_DEFAULT_PERMISSIONS_POLICY\n assertHeaderValue(permissionsPolicy, 'permissionsPolicy')\n validateExtraHeaders(options.extraHeaders ?? [])\n if (options.hsts) {\n assertHeaderValue(options.hsts.readinessEvidence, 'hsts.readinessEvidence')\n if (options.hsts.preload && !options.hsts.includeSubDomains) {\n throw new Error('HSTS preload requires includeSubDomains readiness.')\n }\n }\n const hstsValue = options.hsts\n ? `${BRANDLAB_HSTS_VALUE}; includeSubDomains${options.hsts.preload ? '; preload' : ''}`\n : BRANDLAB_HSTS_VALUE\n const preview = environment === 'preview'\n\n return [\n { key: 'X-Frame-Options', value: 'DENY' },\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n {\n key: 'Permissions-Policy',\n value: permissionsPolicy,\n },\n {\n key: 'X-Robots-Tag',\n value: preview ? BRANDLAB_PREVIEW_X_ROBOTS_TAG : BRANDLAB_DEFAULT_X_ROBOTS_TAG,\n },\n ...(productionBuild\n ? [{ key: 'Strict-Transport-Security', value: hstsValue }]\n : []),\n {\n key: productionBuild\n ? 'Content-Security-Policy'\n : 'Content-Security-Policy-Report-Only',\n value: buildBrandlabContentSecurityPolicy(options),\n },\n ...(options.extraHeaders ?? []).map((header) => ({ ...header })),\n ...(preview ? [{ key: 'Cache-Control', value: BRANDLAB_PREVIEW_CACHE_CONTROL }] : []),\n ]\n}\n\nconst STATIC_ASSET_SOURCE = /^\\/((?:[A-Za-z0-9][A-Za-z0-9._-]*\\/){1,4}):([A-Za-z_][A-Za-z0-9_]*)\\*$/\nconst STATIC_ASSET_RESERVED = new Set(['api', '_next', '_vercel'])\nconst STATIC_ASSET_MAX_SOURCES = 10\nconst STATIC_ASSET_MAX_LENGTH = 100\n\nexport interface BrandlabStaticAssetFolder {\n /** The declared source, for example `/images/:path*`. */\n source: string\n /** The public folder's path segments, for example `['images']`. */\n folder: readonly string[]\n /**\n * The emitted rule's source: file names with an extension below the folder,\n * such as `/images/:path(.+\\.[A-Za-z0-9]+)`. The folder itself and\n * extensionless paths, where a page would answer, never match.\n */\n fileSource: string\n}\n\n/**\n * Validates `staticAssetCache`: at most 10 public folders, each written as Next's\n * source `/<folder>/:path*` (one to four literal segments, then one catch-all\n * parameter, at most 100 characters). `/`, `/api`, `/_next` and `/_vercel` are\n * rejected.\n */\nexport function parseBrandlabStaticAssetCache(sources: unknown): BrandlabStaticAssetFolder[] {\n if (!Array.isArray(sources)) {\n throw new Error('staticAssetCache must list public folders such as /images/:path*.')\n }\n if (sources.length > STATIC_ASSET_MAX_SOURCES) {\n throw new Error(`staticAssetCache lists at most ${STATIC_ASSET_MAX_SOURCES} public folders.`)\n }\n const seen = new Set<string>()\n return sources.map((source: unknown, index) => {\n const field = `staticAssetCache[${index}]`\n if (typeof source !== 'string' || source.length > STATIC_ASSET_MAX_LENGTH) {\n throw new Error(`${field} must be a source of at most ${STATIC_ASSET_MAX_LENGTH} characters.`)\n }\n if (STATIC_ASSET_RESERVED.has(source.split('/')[1]?.toLowerCase() ?? '')) {\n throw new Error(`${field} \"${source}\" is a Next or API path, not a public folder.`)\n }\n const match = STATIC_ASSET_SOURCE.exec(source)\n if (!match) {\n throw new Error(\n `${field} \"${source}\" must name a public folder as /<folder>/:path* (literal folder segments, then one catch-all parameter).`,\n )\n }\n const folder = match[1]!.slice(0, -1).split('/')\n const key = folder.join('/').toLowerCase()\n if (seen.has(key)) throw new Error(`${field} repeats the /${folder.join('/')} folder.`)\n seen.add(key)\n return { source, folder, fileSource: `/${folder.join('/')}/:${match[2]}(.+\\\\.[A-Za-z0-9]+)` }\n })\n}\n\nexport interface BrandlabHeaderRuleOptions extends BrandlabSecurityHeaderOptions {\n /**\n * Public folders of static files that never change in place, for example\n * `['/images/:path*', '/fonts/:path*']`: production builds serve their files with\n * `public, max-age=31536000, immutable`. A file replaced under the same name stays\n * cached in browsers for up to a year, so give a changed file a new name.\n */\n staticAssetCache?: readonly string[]\n /**\n * The canonical host (3.2.1): a production deployment answers every other host (the `*.vercel.app` alias, the\n * deployment URLs) with `X-Robots-Tag: noindex`.\n */\n canonicalHost?: string\n}\n\nexport function buildBrandlabHeaderRules(options: BrandlabHeaderRuleOptions): BrandlabHeaderRule[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const { staticAssetCache, canonicalHost, ...security } = options\n const folders = staticAssetCache === undefined ? [] : parseBrandlabStaticAssetCache(staticAssetCache)\n const rules: BrandlabHeaderRule[] = [\n {\n source: '/:path*',\n headers: buildBrandlabSecurityHeaders(security),\n },\n ]\n // A production build outside a Preview; Previews keep private, no-store everywhere.\n const { environment, productionBuild } = options.deployment\n if (productionBuild && environment !== 'preview') {\n for (const folder of folders) {\n rules.push({\n source: folder.fileSource,\n headers: [{ key: 'Cache-Control', value: BRANDLAB_STATIC_ASSET_CACHE_CONTROL }],\n })\n }\n }\n // Only a real production deployment: Previews already send noindex everywhere, and development has no crawlers.\n if (environment === 'production' && canonicalHost) rules.push(buildNonCanonicalHostRule(canonicalHost))\n return rules\n}\n"]}
|
package/dist/robots.d.ts
CHANGED
|
@@ -1,12 +1,36 @@
|
|
|
1
1
|
import { type BrandlabDeployment, type BrandlabEnvironment, type BrandlabSite } from '@brandlab-digital/site-core/site';
|
|
2
2
|
export interface BrandlabRobotsMetadata {
|
|
3
3
|
rules: Array<{
|
|
4
|
-
userAgent: string;
|
|
4
|
+
userAgent: string | string[];
|
|
5
5
|
allow?: string;
|
|
6
6
|
disallow?: string | string[];
|
|
7
|
+
crawlDelay?: number;
|
|
7
8
|
}>;
|
|
8
9
|
sitemap?: string;
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* A named robots.txt group (3.13.0), for a crawler that needs its own rules, such as Meta's under a recorded
|
|
13
|
+
* decision. The group keeps every shared disallow and adds its own. A search or answer crawler
|
|
14
|
+
* (`BRANDLAB_SEARCH_CRAWLERS`) can never be shut out of the whole site, and the ready gate (W10) checks the built file.
|
|
15
|
+
*/
|
|
16
|
+
export interface BrandlabRobotsBotPolicy {
|
|
17
|
+
/** The crawler's robots.txt token or tokens, for example `meta-externalagent`. Never `*`. */
|
|
18
|
+
userAgent: string | readonly string[];
|
|
19
|
+
/** Paths kept off for these crawlers on top of the shared disallows, each starting with "/". */
|
|
20
|
+
disallow?: readonly string[];
|
|
21
|
+
/**
|
|
22
|
+
* Seconds between requests, only with the id of a recorded decision. Google ignores Crawl-delay and Bing honours
|
|
23
|
+
* it, so it slows Bing indexing; a delay on a search or answer crawler also needs a Romie-owned W10 exception.
|
|
24
|
+
*/
|
|
25
|
+
crawlDelay?: {
|
|
26
|
+
seconds: number;
|
|
27
|
+
decision: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface BrandlabRobotsOptions {
|
|
31
|
+
environment?: BrandlabEnvironment;
|
|
32
|
+
botPolicies?: readonly BrandlabRobotsBotPolicy[];
|
|
33
|
+
}
|
|
10
34
|
/**
|
|
11
35
|
* Crawlers may index any production build except a Vercel Preview: the real
|
|
12
36
|
* production deployment and a local `next start`. `next dev` and every
|
|
@@ -19,7 +43,5 @@ export declare function isBrandlabIndexableDeployment(deployment: BrandlabDeploy
|
|
|
19
43
|
* everything but the shared API routes and names the canonical sitemap; every
|
|
20
44
|
* other deployment keeps `Disallow: /`.
|
|
21
45
|
*/
|
|
22
|
-
export declare function createBrandlabRobots(site: BrandlabSite, options?:
|
|
23
|
-
environment?: BrandlabEnvironment;
|
|
24
|
-
}): () => BrandlabRobotsMetadata;
|
|
46
|
+
export declare function createBrandlabRobots(site: BrandlabSite, options?: BrandlabRobotsOptions): () => BrandlabRobotsMetadata;
|
|
25
47
|
//# sourceMappingURL=robots.d.ts.map
|
package/dist/robots.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EAClB,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,6FAA6F;IAC7F,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IACrC,gGAAgG;IAChG,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CACnD;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,mBAAmB,CAAA;IACjC,WAAW,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAA;CACjD;AAYD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,kBAAkB,WAG3E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,OAAO,GAAE,qBAA0B,SAOT,sBAAsB,CAajD"}
|
package/dist/robots.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
1
|
+
import { brandlabSearchCrawlerNamed, resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
2
2
|
/**
|
|
3
3
|
* The shared API routes crawlers have no use for, kept out of a production
|
|
4
4
|
* robots.txt (from 3.3.0): the contact endpoint. A robots.txt path is a prefix, so
|
|
@@ -25,6 +25,11 @@ export function isBrandlabIndexableDeployment(deployment) {
|
|
|
25
25
|
*/
|
|
26
26
|
export function createBrandlabRobots(site, options = {}) {
|
|
27
27
|
const sitemap = `${site.canonicalUrl}/sitemap.xml`;
|
|
28
|
+
const groups = (options.botPolicies ?? []).map(botPolicyRule);
|
|
29
|
+
const named = groups.flatMap((group) => [group.userAgent].flat().map((token) => token.toLowerCase()));
|
|
30
|
+
const repeated = named.find((token, index) => named.indexOf(token) !== index);
|
|
31
|
+
if (repeated)
|
|
32
|
+
throw new Error(`botPolicies names "${repeated}" twice; give each crawler one group.`);
|
|
28
33
|
return function robots() {
|
|
29
34
|
const deployment = resolveBrandlabDeployment(options.environment ?? process.env);
|
|
30
35
|
if (!isBrandlabIndexableDeployment(deployment)) {
|
|
@@ -34,9 +39,51 @@ export function createBrandlabRobots(site, options = {}) {
|
|
|
34
39
|
// by Google and Bing; canonical-host consolidation is owned by permanent
|
|
35
40
|
// redirects and canonical URLs.
|
|
36
41
|
return {
|
|
37
|
-
rules: [{ userAgent: '*', allow: '/', disallow: [...SHARED_API_DISALLOW] }],
|
|
42
|
+
rules: [{ userAgent: '*', allow: '/', disallow: [...SHARED_API_DISALLOW] }, ...groups],
|
|
38
43
|
sitemap,
|
|
39
44
|
};
|
|
40
45
|
};
|
|
41
46
|
}
|
|
47
|
+
function botPolicyRule(policy, index) {
|
|
48
|
+
const field = `botPolicies[${index}]`;
|
|
49
|
+
if (!policy || typeof policy !== 'object')
|
|
50
|
+
throw new TypeError(`${field} must be an object.`);
|
|
51
|
+
const unknown = Object.keys(policy).filter((key) => !['userAgent', 'disallow', 'crawlDelay'].includes(key));
|
|
52
|
+
if (unknown.length > 0)
|
|
53
|
+
throw new Error(`${field} contains unsupported keys: ${unknown.join(', ')}.`);
|
|
54
|
+
const tokens = typeof policy.userAgent === 'string' ? [policy.userAgent] : [...(policy.userAgent ?? [])];
|
|
55
|
+
if (tokens.length === 0)
|
|
56
|
+
throw new Error(`${field}.userAgent must name a crawler.`);
|
|
57
|
+
for (const token of tokens) {
|
|
58
|
+
if (typeof token !== 'string' || !/^[A-Za-z0-9._-]+$/.test(token.trim()) || token !== token.trim()) {
|
|
59
|
+
throw new Error(`${field}.userAgent must be robots.txt crawler tokens such as "meta-externalagent" (found ${JSON.stringify(token)}); the shared group already covers "*".`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const disallow = [...(policy.disallow ?? [])];
|
|
63
|
+
for (const path of disallow) {
|
|
64
|
+
if (typeof path !== 'string' || !path.startsWith('/') || /\s/.test(path)) {
|
|
65
|
+
throw new Error(`${field}.disallow paths must start with "/" (found ${JSON.stringify(path)}).`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const protectedCrawler = tokens.map(brandlabSearchCrawlerNamed).find(Boolean);
|
|
69
|
+
if (protectedCrawler && disallow.some((path) => path === '/' || path === '/*')) {
|
|
70
|
+
throw new Error(`${field} shuts ${protectedCrawler} out of the whole site; search and answer crawlers stay allowed.`);
|
|
71
|
+
}
|
|
72
|
+
const rule = {
|
|
73
|
+
userAgent: tokens.length === 1 ? tokens[0] : tokens,
|
|
74
|
+
allow: '/',
|
|
75
|
+
disallow: [...new Set([...SHARED_API_DISALLOW, ...disallow])],
|
|
76
|
+
};
|
|
77
|
+
if (policy.crawlDelay !== undefined) {
|
|
78
|
+
const { seconds, decision } = policy.crawlDelay ?? {};
|
|
79
|
+
if (typeof decision !== 'string' || !decision.trim()) {
|
|
80
|
+
throw new Error(`${field}.crawlDelay needs the id of a recorded decision; Crawl-delay slows Bing indexing and Google ignores it.`);
|
|
81
|
+
}
|
|
82
|
+
if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds <= 0 || seconds > 60) {
|
|
83
|
+
throw new Error(`${field}.crawlDelay.seconds must be a number of seconds from above 0 to 60.`);
|
|
84
|
+
}
|
|
85
|
+
rule.crawlDelay = seconds;
|
|
86
|
+
}
|
|
87
|
+
return rule;
|
|
88
|
+
}
|
|
42
89
|
//# sourceMappingURL=robots.js.map
|
package/dist/robots.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,GAI1B,MAAM,kCAAkC,CAAA;AAOzC;;;;;;;GAOG;AACH,MAAM,mBAAmB,GAAG,CAAC,cAAc,CAAC,CAAA;AAE5C;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,UAA8B;IAC1E,OAAO,UAAU,CAAC,WAAW,KAAK,YAAY;QAC5C,CAAC,UAAU,CAAC,WAAW,KAAK,aAAa,IAAI,UAAU,CAAC,eAAe,CAAC,CAAA;AAC5E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAkB,EAClB,UAAiD,EAAE;IAEnD,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,YAAY,cAAc,CAAA;IAClD,OAAO,SAAS,MAAM;QACpB,MAAM,UAAU,GAAG,yBAAyB,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,CAAA;QAChF,IAAI,CAAC,6BAA6B,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;QACvD,CAAC;QACD,yEAAyE;QACzE,yEAAyE;QACzE,gCAAgC;QAChC,OAAO;YACL,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,GAAG,mBAAmB,CAAC,EAAE,CAAC;YAC3E,OAAO;SACR,CAAA;IACH,CAAC,CAAA;AACH,CAAC","sourcesContent":["import {\n resolveBrandlabDeployment,\n type BrandlabDeployment,\n type BrandlabEnvironment,\n type BrandlabSite,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabRobotsMetadata {\n rules: Array<{ userAgent: string; allow?: string; disallow?: string | string[] }>\n sitemap?: string\n}\n\n/**\n * The shared API routes crawlers have no use for, kept out of a production\n * robots.txt (from 3.3.0): the contact endpoint. A robots.txt path is a prefix, so\n * it covers its subpaths. Only this: not a blanket `/api/`, because a site may serve\n * something public there. From 3.5.0 `/api/google-reviews` is no longer listed:\n * sites read Google reviews from the Brandlab reviews feed on the server\n * (`site-next/google-business-reviews/server`) and no longer serve that route.\n */\nconst SHARED_API_DISALLOW = ['/api/contact']\n\n/**\n * Crawlers may index any production build except a Vercel Preview: the real\n * production deployment and a local `next start`. `next dev` and every\n * Preview are closed. There is no environment override.\n */\nexport function isBrandlabIndexableDeployment(deployment: BrandlabDeployment) {\n return deployment.environment === 'production' ||\n (deployment.environment === 'development' && deployment.productionBuild)\n}\n\n/**\n * `src/app/robots.ts`: `export default createBrandlabRobots(site)`.\n * The deployment is resolved when Next renders robots.txt. Production allows\n * everything but the shared API routes and names the canonical sitemap; every\n * other deployment keeps `Disallow: /`.\n */\nexport function createBrandlabRobots(\n site: BrandlabSite,\n options: { environment?: BrandlabEnvironment } = {},\n) {\n const sitemap = `${site.canonicalUrl}/sitemap.xml`\n return function robots(): BrandlabRobotsMetadata {\n const deployment = resolveBrandlabDeployment(options.environment ?? process.env)\n if (!isBrandlabIndexableDeployment(deployment)) {\n return { rules: [{ userAgent: '*', disallow: '/' }] }\n }\n // No `host` directive: it is a non-standard robots.txt extension ignored\n // by Google and Bing; canonical-host consolidation is owned by permanent\n // redirects and canonical URLs.\n return {\n rules: [{ userAgent: '*', allow: '/', disallow: [...SHARED_API_DISALLOW] }],\n sitemap,\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n brandlabSearchCrawlerNamed,\n resolveBrandlabDeployment,\n type BrandlabDeployment,\n type BrandlabEnvironment,\n type BrandlabSite,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabRobotsMetadata {\n rules: Array<{ userAgent: string | string[]; allow?: string; disallow?: string | string[]; crawlDelay?: number }>\n sitemap?: string\n}\n\n/**\n * A named robots.txt group (3.13.0), for a crawler that needs its own rules, such as Meta's under a recorded\n * decision. The group keeps every shared disallow and adds its own. A search or answer crawler\n * (`BRANDLAB_SEARCH_CRAWLERS`) can never be shut out of the whole site, and the ready gate (W10) checks the built file.\n */\nexport interface BrandlabRobotsBotPolicy {\n /** The crawler's robots.txt token or tokens, for example `meta-externalagent`. Never `*`. */\n userAgent: string | readonly string[]\n /** Paths kept off for these crawlers on top of the shared disallows, each starting with \"/\". */\n disallow?: readonly string[]\n /**\n * Seconds between requests, only with the id of a recorded decision. Google ignores Crawl-delay and Bing honours\n * it, so it slows Bing indexing; a delay on a search or answer crawler also needs a Romie-owned W10 exception.\n */\n crawlDelay?: { seconds: number; decision: string }\n}\n\nexport interface BrandlabRobotsOptions {\n environment?: BrandlabEnvironment\n botPolicies?: readonly BrandlabRobotsBotPolicy[]\n}\n\n/**\n * The shared API routes crawlers have no use for, kept out of a production\n * robots.txt (from 3.3.0): the contact endpoint. A robots.txt path is a prefix, so\n * it covers its subpaths. Only this: not a blanket `/api/`, because a site may serve\n * something public there. From 3.5.0 `/api/google-reviews` is no longer listed:\n * sites read Google reviews from the Brandlab reviews feed on the server\n * (`site-next/google-business-reviews/server`) and no longer serve that route.\n */\nconst SHARED_API_DISALLOW = ['/api/contact']\n\n/**\n * Crawlers may index any production build except a Vercel Preview: the real\n * production deployment and a local `next start`. `next dev` and every\n * Preview are closed. There is no environment override.\n */\nexport function isBrandlabIndexableDeployment(deployment: BrandlabDeployment) {\n return deployment.environment === 'production' ||\n (deployment.environment === 'development' && deployment.productionBuild)\n}\n\n/**\n * `src/app/robots.ts`: `export default createBrandlabRobots(site)`.\n * The deployment is resolved when Next renders robots.txt. Production allows\n * everything but the shared API routes and names the canonical sitemap; every\n * other deployment keeps `Disallow: /`.\n */\nexport function createBrandlabRobots(\n site: BrandlabSite,\n options: BrandlabRobotsOptions = {},\n) {\n const sitemap = `${site.canonicalUrl}/sitemap.xml`\n const groups = (options.botPolicies ?? []).map(botPolicyRule)\n const named = groups.flatMap((group) => [group.userAgent].flat().map((token) => token.toLowerCase()))\n const repeated = named.find((token, index) => named.indexOf(token) !== index)\n if (repeated) throw new Error(`botPolicies names \"${repeated}\" twice; give each crawler one group.`)\n return function robots(): BrandlabRobotsMetadata {\n const deployment = resolveBrandlabDeployment(options.environment ?? process.env)\n if (!isBrandlabIndexableDeployment(deployment)) {\n return { rules: [{ userAgent: '*', disallow: '/' }] }\n }\n // No `host` directive: it is a non-standard robots.txt extension ignored\n // by Google and Bing; canonical-host consolidation is owned by permanent\n // redirects and canonical URLs.\n return {\n rules: [{ userAgent: '*', allow: '/', disallow: [...SHARED_API_DISALLOW] }, ...groups],\n sitemap,\n }\n }\n}\n\nfunction botPolicyRule(policy: BrandlabRobotsBotPolicy, index: number): BrandlabRobotsMetadata['rules'][number] {\n const field = `botPolicies[${index}]`\n if (!policy || typeof policy !== 'object') throw new TypeError(`${field} must be an object.`)\n const unknown = Object.keys(policy).filter((key) => !['userAgent', 'disallow', 'crawlDelay'].includes(key))\n if (unknown.length > 0) throw new Error(`${field} contains unsupported keys: ${unknown.join(', ')}.`)\n const tokens = typeof policy.userAgent === 'string' ? [policy.userAgent] : [...(policy.userAgent ?? [])]\n if (tokens.length === 0) throw new Error(`${field}.userAgent must name a crawler.`)\n for (const token of tokens) {\n if (typeof token !== 'string' || !/^[A-Za-z0-9._-]+$/.test(token.trim()) || token !== token.trim()) {\n throw new Error(`${field}.userAgent must be robots.txt crawler tokens such as \"meta-externalagent\" (found ${JSON.stringify(token)}); the shared group already covers \"*\".`)\n }\n }\n const disallow = [...(policy.disallow ?? [])]\n for (const path of disallow) {\n if (typeof path !== 'string' || !path.startsWith('/') || /\\s/.test(path)) {\n throw new Error(`${field}.disallow paths must start with \"/\" (found ${JSON.stringify(path)}).`)\n }\n }\n const protectedCrawler = tokens.map(brandlabSearchCrawlerNamed).find(Boolean)\n if (protectedCrawler && disallow.some((path) => path === '/' || path === '/*')) {\n throw new Error(`${field} shuts ${protectedCrawler} out of the whole site; search and answer crawlers stay allowed.`)\n }\n const rule: BrandlabRobotsMetadata['rules'][number] = {\n userAgent: tokens.length === 1 ? tokens[0]! : tokens,\n allow: '/',\n disallow: [...new Set([...SHARED_API_DISALLOW, ...disallow])],\n }\n if (policy.crawlDelay !== undefined) {\n const { seconds, decision } = policy.crawlDelay ?? ({} as { seconds?: unknown; decision?: unknown })\n if (typeof decision !== 'string' || !decision.trim()) {\n throw new Error(`${field}.crawlDelay needs the id of a recorded decision; Crawl-delay slows Bing indexing and Google ignores it.`)\n }\n if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds <= 0 || seconds > 60) {\n throw new Error(`${field}.crawlDelay.seconds must be a number of seconds from above 0 to 60.`)\n }\n rule.crawlDelay = seconds\n }\n return rule\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefetch by link role (W2, 3.13.0). Next's default `<Link>` prefetches the whole route when the link comes within
|
|
3
|
+
* about 200 px of the viewport, so a long page of suburb, footer and map links fetched 16 to 45 pages on a full scroll
|
|
4
|
+
* while visitors read 1.0 to 1.3 pages a session. Each link role now has one setting:
|
|
5
|
+
*
|
|
6
|
+
* - `auto` (Next's default): the top-level header links (7 at most, never the current page) and the main quote or
|
|
7
|
+
* contact action;
|
|
8
|
+
* - `intent`: in-content service, project and blog cards (8 at most, matching K11). They render `prefetch={false}`
|
|
9
|
+
* and `data-prefetch="intent"`; `IntentPrefetch` (mounted once in the root layout) prefetches one of them when a
|
|
10
|
+
* visitor points at, focuses or touches it;
|
|
11
|
+
* - `false`: header panels, the drawer, the footer, area and suburb lists, nearby suburbs, area maps, suburb finder
|
|
12
|
+
* results, other services in this suburb and any link inside a hidden container.
|
|
13
|
+
*
|
|
14
|
+
* The ready gate's W2 journey fails a page that prefetches more than 8 destinations on landing or 12 after a full
|
|
15
|
+
* scroll, or prefetches from an off role, a hidden link or the current page. It finds a site's own off-role
|
|
16
|
+
* containers by the shared section ids (`coverage`, `nearby-areas`, `area-map`), the area map's `data-area-map` or
|
|
17
|
+
* `data-bl-links` with one of `area`, `nearby`, `map`, `finder` or `other-services` on the list's container.
|
|
18
|
+
*/
|
|
19
|
+
/** A link's prefetch setting: Next's default, on intent only, or off. */
|
|
20
|
+
export type LinkPrefetch = 'auto' | 'intent' | false;
|
|
21
|
+
export declare const LINK_PREFETCH_POLICY: Readonly<{
|
|
22
|
+
readonly headerTopLevel: "auto";
|
|
23
|
+
readonly primaryAction: "auto";
|
|
24
|
+
readonly contentCard: "intent";
|
|
25
|
+
readonly headerPanel: false;
|
|
26
|
+
readonly drawer: false;
|
|
27
|
+
readonly footer: false;
|
|
28
|
+
readonly areaList: false;
|
|
29
|
+
readonly nearbySuburbs: false;
|
|
30
|
+
readonly areaMap: false;
|
|
31
|
+
readonly finderResults: false;
|
|
32
|
+
readonly otherServicesInSuburb: false;
|
|
33
|
+
readonly hiddenContainer: false;
|
|
34
|
+
}>;
|
|
35
|
+
export type LinkPrefetchRole = keyof typeof LINK_PREFETCH_POLICY;
|
|
36
|
+
/** The most links of each prefetching role on one page, and W2's page limits. */
|
|
37
|
+
export declare const LINK_PREFETCH_LIMITS: Readonly<{
|
|
38
|
+
headerTopLevel: 7;
|
|
39
|
+
contentCard: 8;
|
|
40
|
+
landingDestinations: 8;
|
|
41
|
+
scrolledDestinations: 12;
|
|
42
|
+
}>;
|
|
43
|
+
/** The marker `IntentPrefetch` listens for. */
|
|
44
|
+
export declare const INTENT_PREFETCH_SELECTOR = "a[data-prefetch=\"intent\"]";
|
|
45
|
+
/** The `next/link` props for a setting: nothing for `auto`, prefetch off for `false`, and off plus the marker for `intent`. */
|
|
46
|
+
export declare function linkPrefetchProps(prefetch: LinkPrefetch | undefined): {
|
|
47
|
+
prefetch?: false;
|
|
48
|
+
'data-prefetch'?: 'intent';
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=prefetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefetch.d.ts","sourceRoot":"","sources":["../../src/ui-kit/prefetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAEpD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;EAaiB,CAAA;AAElD,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,oBAAoB,CAAA;AAEhE,iFAAiF;AACjF,eAAO,MAAM,oBAAoB;;;;;EAK/B,CAAA;AAEF,+CAA+C;AAC/C,eAAO,MAAM,wBAAwB,gCAA8B,CAAA;AAEnE,+HAA+H;AAC/H,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,eAAe,CAAC,EAAE,QAAQ,CAAA;CAAE,CAItH"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefetch by link role (W2, 3.13.0). Next's default `<Link>` prefetches the whole route when the link comes within
|
|
3
|
+
* about 200 px of the viewport, so a long page of suburb, footer and map links fetched 16 to 45 pages on a full scroll
|
|
4
|
+
* while visitors read 1.0 to 1.3 pages a session. Each link role now has one setting:
|
|
5
|
+
*
|
|
6
|
+
* - `auto` (Next's default): the top-level header links (7 at most, never the current page) and the main quote or
|
|
7
|
+
* contact action;
|
|
8
|
+
* - `intent`: in-content service, project and blog cards (8 at most, matching K11). They render `prefetch={false}`
|
|
9
|
+
* and `data-prefetch="intent"`; `IntentPrefetch` (mounted once in the root layout) prefetches one of them when a
|
|
10
|
+
* visitor points at, focuses or touches it;
|
|
11
|
+
* - `false`: header panels, the drawer, the footer, area and suburb lists, nearby suburbs, area maps, suburb finder
|
|
12
|
+
* results, other services in this suburb and any link inside a hidden container.
|
|
13
|
+
*
|
|
14
|
+
* The ready gate's W2 journey fails a page that prefetches more than 8 destinations on landing or 12 after a full
|
|
15
|
+
* scroll, or prefetches from an off role, a hidden link or the current page. It finds a site's own off-role
|
|
16
|
+
* containers by the shared section ids (`coverage`, `nearby-areas`, `area-map`), the area map's `data-area-map` or
|
|
17
|
+
* `data-bl-links` with one of `area`, `nearby`, `map`, `finder` or `other-services` on the list's container.
|
|
18
|
+
*/
|
|
19
|
+
export const LINK_PREFETCH_POLICY = Object.freeze({
|
|
20
|
+
headerTopLevel: 'auto',
|
|
21
|
+
primaryAction: 'auto',
|
|
22
|
+
contentCard: 'intent',
|
|
23
|
+
headerPanel: false,
|
|
24
|
+
drawer: false,
|
|
25
|
+
footer: false,
|
|
26
|
+
areaList: false,
|
|
27
|
+
nearbySuburbs: false,
|
|
28
|
+
areaMap: false,
|
|
29
|
+
finderResults: false,
|
|
30
|
+
otherServicesInSuburb: false,
|
|
31
|
+
hiddenContainer: false,
|
|
32
|
+
});
|
|
33
|
+
/** The most links of each prefetching role on one page, and W2's page limits. */
|
|
34
|
+
export const LINK_PREFETCH_LIMITS = Object.freeze({
|
|
35
|
+
headerTopLevel: 7,
|
|
36
|
+
contentCard: 8,
|
|
37
|
+
landingDestinations: 8,
|
|
38
|
+
scrolledDestinations: 12,
|
|
39
|
+
});
|
|
40
|
+
/** The marker `IntentPrefetch` listens for. */
|
|
41
|
+
export const INTENT_PREFETCH_SELECTOR = 'a[data-prefetch="intent"]';
|
|
42
|
+
/** The `next/link` props for a setting: nothing for `auto`, prefetch off for `false`, and off plus the marker for `intent`. */
|
|
43
|
+
export function linkPrefetchProps(prefetch) {
|
|
44
|
+
if (prefetch === 'intent')
|
|
45
|
+
return { prefetch: false, 'data-prefetch': 'intent' };
|
|
46
|
+
if (prefetch === false)
|
|
47
|
+
return { prefetch: false };
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=prefetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefetch.js","sourceRoot":"","sources":["../../src/ui-kit/prefetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,cAAc,EAAE,MAAM;IACtB,aAAa,EAAE,MAAM;IACrB,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,KAAK;IAClB,MAAM,EAAE,KAAK;IACb,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,KAAK;IACf,aAAa,EAAE,KAAK;IACpB,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,KAAK;IACpB,qBAAqB,EAAE,KAAK;IAC5B,eAAe,EAAE,KAAK;CACyB,CAAC,CAAA;AAIlD,iFAAiF;AACjF,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,cAAc,EAAE,CAAC;IACjB,WAAW,EAAE,CAAC;IACd,mBAAmB,EAAE,CAAC;IACtB,oBAAoB,EAAE,EAAE;CACzB,CAAC,CAAA;AAEF,+CAA+C;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,2BAA2B,CAAA;AAEnE,+HAA+H;AAC/H,MAAM,UAAU,iBAAiB,CAAC,QAAkC;IAClE,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAA;IAChF,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAClD,OAAO,EAAE,CAAA;AACX,CAAC","sourcesContent":["/**\n * Prefetch by link role (W2, 3.13.0). Next's default `<Link>` prefetches the whole route when the link comes within\n * about 200 px of the viewport, so a long page of suburb, footer and map links fetched 16 to 45 pages on a full scroll\n * while visitors read 1.0 to 1.3 pages a session. Each link role now has one setting:\n *\n * - `auto` (Next's default): the top-level header links (7 at most, never the current page) and the main quote or\n * contact action;\n * - `intent`: in-content service, project and blog cards (8 at most, matching K11). They render `prefetch={false}`\n * and `data-prefetch=\"intent\"`; `IntentPrefetch` (mounted once in the root layout) prefetches one of them when a\n * visitor points at, focuses or touches it;\n * - `false`: header panels, the drawer, the footer, area and suburb lists, nearby suburbs, area maps, suburb finder\n * results, other services in this suburb and any link inside a hidden container.\n *\n * The ready gate's W2 journey fails a page that prefetches more than 8 destinations on landing or 12 after a full\n * scroll, or prefetches from an off role, a hidden link or the current page. It finds a site's own off-role\n * containers by the shared section ids (`coverage`, `nearby-areas`, `area-map`), the area map's `data-area-map` or\n * `data-bl-links` with one of `area`, `nearby`, `map`, `finder` or `other-services` on the list's container.\n */\n\n/** A link's prefetch setting: Next's default, on intent only, or off. */\nexport type LinkPrefetch = 'auto' | 'intent' | false\n\nexport const LINK_PREFETCH_POLICY = Object.freeze({\n headerTopLevel: 'auto',\n primaryAction: 'auto',\n contentCard: 'intent',\n headerPanel: false,\n drawer: false,\n footer: false,\n areaList: false,\n nearbySuburbs: false,\n areaMap: false,\n finderResults: false,\n otherServicesInSuburb: false,\n hiddenContainer: false,\n} as const satisfies Record<string, LinkPrefetch>)\n\nexport type LinkPrefetchRole = keyof typeof LINK_PREFETCH_POLICY\n\n/** The most links of each prefetching role on one page, and W2's page limits. */\nexport const LINK_PREFETCH_LIMITS = Object.freeze({\n headerTopLevel: 7,\n contentCard: 8,\n landingDestinations: 8,\n scrolledDestinations: 12,\n})\n\n/** The marker `IntentPrefetch` listens for. */\nexport const INTENT_PREFETCH_SELECTOR = 'a[data-prefetch=\"intent\"]'\n\n/** The `next/link` props for a setting: nothing for `auto`, prefetch off for `false`, and off plus the marker for `intent`. */\nexport function linkPrefetchProps(prefetch: LinkPrefetch | undefined): { prefetch?: false; 'data-prefetch'?: 'intent' } {\n if (prefetch === 'intent') return { prefetch: false, 'data-prefetch': 'intent' }\n if (prefetch === false) return { prefetch: false }\n return {}\n}\n"]}
|
|
@@ -3,4 +3,5 @@ export { CONTAINER_WIDTHS, Container, RatingStars, SECTION_TONES, Section, Secti
|
|
|
3
3
|
export { QuoteButton, type QuoteButtonProps, type QuoteButtonSkin, type QuoteButtonSlot } from './quote-button.js';
|
|
4
4
|
export { Icon, SocialIcon, UI_ICON_NAMES, type IconProps, type UiIconName } from '../icons.js';
|
|
5
5
|
export { UI_SOCIAL_KINDS, type UiLink, type UiPhone, type UiSocial, type UiSocialKind } from '../types.js';
|
|
6
|
+
export { INTENT_PREFETCH_SELECTOR, LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY, linkPrefetchProps, type LinkPrefetch, type LinkPrefetchRole, } from '../prefetch.js';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,UAAU,EACV,SAAS,EACT,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,aAAa,EACb,OAAO,EACP,cAAc,EACd,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,eAAe,EAAE,KAAK,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,UAAU,EACV,SAAS,EACT,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,aAAa,EACb,OAAO,EACP,cAAc,EACd,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,eAAe,EAAE,KAAK,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1G,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA"}
|
|
@@ -3,4 +3,5 @@ export { CONTAINER_WIDTHS, Container, RatingStars, SECTION_TONES, Section, Secti
|
|
|
3
3
|
export { QuoteButton } from './quote-button.js';
|
|
4
4
|
export { Icon, SocialIcon, UI_ICON_NAMES } from '../icons.js';
|
|
5
5
|
export { UI_SOCIAL_KINDS } from '../types.js';
|
|
6
|
+
export { INTENT_PREFETCH_SELECTOR, LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY, linkPrefetchProps, } from '../prefetch.js';
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,UAAU,EACV,SAAS,EACT,QAAQ,GAWT,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,aAAa,EACb,OAAO,EACP,cAAc,EACd,cAAc,GASf,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,WAAW,EAAqE,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAmC,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,eAAe,EAA+D,MAAM,aAAa,CAAA","sourcesContent":["export {\n BUTTON_VARIANTS,\n ButtonLink,\n PhoneLink,\n TextLink,\n type ButtonLinkProps,\n type ButtonLinkSkin,\n type ButtonLinkSlot,\n type ButtonVariant,\n type PhoneLinkProps,\n type PhoneLinkSkin,\n type PhoneLinkSlot,\n type TextLinkProps,\n type TextLinkSkin,\n type TextLinkSlot,\n} from './links.js'\nexport {\n CONTAINER_WIDTHS,\n Container,\n RatingStars,\n SECTION_TONES,\n Section,\n SectionHeading,\n VisuallyHidden,\n type ContainerSkin,\n type ContainerWidth,\n type SectionHeadingProps,\n type SectionHeadingSkin,\n type SectionHeadingSlot,\n type SectionProps,\n type SectionSlot,\n type SectionTone,\n} from './layout.js'\nexport { QuoteButton, type QuoteButtonProps, type QuoteButtonSkin, type QuoteButtonSlot } from './quote-button.js'\nexport { Icon, SocialIcon, UI_ICON_NAMES, type IconProps, type UiIconName } from '../icons.js'\nexport { UI_SOCIAL_KINDS, type UiLink, type UiPhone, type UiSocial, type UiSocialKind } from '../types.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,UAAU,EACV,SAAS,EACT,QAAQ,GAWT,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,aAAa,EACb,OAAO,EACP,cAAc,EACd,cAAc,GASf,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,WAAW,EAAqE,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAmC,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,eAAe,EAA+D,MAAM,aAAa,CAAA;AAC1G,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,GAGlB,MAAM,gBAAgB,CAAA","sourcesContent":["export {\n BUTTON_VARIANTS,\n ButtonLink,\n PhoneLink,\n TextLink,\n type ButtonLinkProps,\n type ButtonLinkSkin,\n type ButtonLinkSlot,\n type ButtonVariant,\n type PhoneLinkProps,\n type PhoneLinkSkin,\n type PhoneLinkSlot,\n type TextLinkProps,\n type TextLinkSkin,\n type TextLinkSlot,\n} from './links.js'\nexport {\n CONTAINER_WIDTHS,\n Container,\n RatingStars,\n SECTION_TONES,\n Section,\n SectionHeading,\n VisuallyHidden,\n type ContainerSkin,\n type ContainerWidth,\n type SectionHeadingProps,\n type SectionHeadingSkin,\n type SectionHeadingSlot,\n type SectionProps,\n type SectionSlot,\n type SectionTone,\n} from './layout.js'\nexport { QuoteButton, type QuoteButtonProps, type QuoteButtonSkin, type QuoteButtonSlot } from './quote-button.js'\nexport { Icon, SocialIcon, UI_ICON_NAMES, type IconProps, type UiIconName } from '../icons.js'\nexport { UI_SOCIAL_KINDS, type UiLink, type UiPhone, type UiSocial, type UiSocialKind } from '../types.js'\nexport {\n INTENT_PREFETCH_SELECTOR,\n LINK_PREFETCH_LIMITS,\n LINK_PREFETCH_POLICY,\n linkPrefetchProps,\n type LinkPrefetch,\n type LinkPrefetchRole,\n} from '../prefetch.js'\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { PhoneCallPlacement } from '../../analytics.js';
|
|
3
3
|
import { type UiIconName } from '../icons.js';
|
|
4
|
+
import { type LinkPrefetch } from '../prefetch.js';
|
|
4
5
|
import { type SkinEntry } from '../skin.js';
|
|
5
6
|
import type { UiPhone } from '../types.js';
|
|
6
7
|
export type ButtonLinkSlot = 'root' | 'label' | 'icon';
|
|
@@ -17,18 +18,28 @@ export interface ButtonLinkProps extends ButtonLinkSkin {
|
|
|
17
18
|
readonly iconPosition?: 'start' | 'end';
|
|
18
19
|
/** Force external handling; by default `http(s)://` links are external. */
|
|
19
20
|
readonly external?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Prefetch by link role (`LINK_PREFETCH_POLICY`): `auto` (the default, Next's prefetch in view) for a main action,
|
|
23
|
+
* `intent` for an in-content card link, `false` for footer, area, nearby, map and finder links.
|
|
24
|
+
*/
|
|
25
|
+
readonly prefetch?: LinkPrefetch;
|
|
20
26
|
}
|
|
21
27
|
/** A link styled as a button. A primary action is at least 44 px tall (structure.css). */
|
|
22
|
-
export declare function ButtonLink({ href, children, icon, iconPosition, external, variant, layout, classes }: ButtonLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function ButtonLink({ href, children, icon, iconPosition, external, prefetch, variant, layout, classes }: ButtonLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
23
29
|
export type TextLinkSlot = 'root';
|
|
24
30
|
export type TextLinkSkin = SkinEntry<TextLinkSlot, 'inline' | 'standalone'>;
|
|
25
31
|
export interface TextLinkProps extends TextLinkSkin {
|
|
26
32
|
readonly href: string;
|
|
27
33
|
readonly children: ReactNode;
|
|
28
34
|
readonly external?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Prefetch by link role (`LINK_PREFETCH_POLICY`): `auto` (the default, Next's prefetch in view) for a main action,
|
|
37
|
+
* `intent` for an in-content card link, `false` for footer, area, nearby, map and finder links.
|
|
38
|
+
*/
|
|
39
|
+
readonly prefetch?: LinkPrefetch;
|
|
29
40
|
}
|
|
30
41
|
/** An inline or standalone text link. */
|
|
31
|
-
export declare function TextLink({ href, children, external, variant, classes }: TextLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export declare function TextLink({ href, children, external, prefetch, variant, classes }: TextLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
32
43
|
export type PhoneLinkSlot = 'root' | 'icon' | 'label' | 'number';
|
|
33
44
|
export type PhoneLinkSkin = SkinEntry<PhoneLinkSlot, 'text' | 'button'>;
|
|
34
45
|
export interface PhoneLinkProps extends PhoneLinkSkin {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/links.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/links.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAoC1C,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;AACtD,eAAO,MAAM,eAAe,4CAA6C,CAAA;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAC5D,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,EAAE,aAAa,EAAE;IAAE,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,CAAC,CAAA;AAErG,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;CACjC;AAED,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAoB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAmB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,eAAe,2CAcnJ;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAA;AACjC,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,EAAE,QAAQ,GAAG,YAAY,CAAC,CAAA;AAE3E,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;CACjC;AAED,yCAAyC;AACzC,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAkB,EAAE,OAAO,EAAE,EAAE,aAAa,2CAM1G;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAChE,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAA;AAEvE,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,qFAAqF;IACrF,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,4GAA4G;IAC5G,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAgB,EAAE,OAAgB,EAAE,OAAO,EAAE,EAAE,cAAc,2CAgBjH"}
|
|
@@ -6,25 +6,26 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
6
6
|
*/
|
|
7
7
|
import Link from 'next/link';
|
|
8
8
|
import { Icon } from '../icons.js';
|
|
9
|
+
import { linkPrefetchProps } from '../prefetch.js';
|
|
9
10
|
import { slot } from '../skin.js';
|
|
10
11
|
function isExternal(href) {
|
|
11
12
|
return /^https?:\/\//i.test(href);
|
|
12
13
|
}
|
|
13
|
-
function Anchor({ href, external, hooks, children }) {
|
|
14
|
+
function Anchor({ href, external, prefetch, hooks, children, }) {
|
|
14
15
|
if (external ?? isExternal(href)) {
|
|
15
16
|
return (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", ...hooks, children: children }));
|
|
16
17
|
}
|
|
17
|
-
return (_jsx(Link, { href: href, ...hooks, children: children }));
|
|
18
|
+
return (_jsx(Link, { href: href, ...linkPrefetchProps(prefetch), ...hooks, children: children }));
|
|
18
19
|
}
|
|
19
20
|
export const BUTTON_VARIANTS = ['primary', 'secondary', 'quiet'];
|
|
20
21
|
/** A link styled as a button. A primary action is at least 44 px tall (structure.css). */
|
|
21
|
-
export function ButtonLink({ href, children, icon, iconPosition = 'end', external, variant = 'primary', layout, classes }) {
|
|
22
|
+
export function ButtonLink({ href, children, icon, iconPosition = 'end', external, prefetch, variant = 'primary', layout, classes }) {
|
|
22
23
|
const iconNode = icon ? _jsx("span", { ...slot(classes, 'icon'), children: _jsx(Icon, { name: icon }) }) : null;
|
|
23
|
-
return (_jsxs(Anchor, { href: href, external: external, hooks: { 'data-bl': 'button', 'data-variant': variant, 'data-size': layout?.size ?? 'regular', className: classes?.root }, children: [iconPosition === 'start' ? iconNode : null, _jsx("span", { ...slot(classes, 'label'), children: children }), iconPosition === 'end' ? iconNode : null] }));
|
|
24
|
+
return (_jsxs(Anchor, { href: href, external: external, prefetch: prefetch, hooks: { 'data-bl': 'button', 'data-variant': variant, 'data-size': layout?.size ?? 'regular', className: classes?.root }, children: [iconPosition === 'start' ? iconNode : null, _jsx("span", { ...slot(classes, 'label'), children: children }), iconPosition === 'end' ? iconNode : null] }));
|
|
24
25
|
}
|
|
25
26
|
/** An inline or standalone text link. */
|
|
26
|
-
export function TextLink({ href, children, external, variant = 'inline', classes }) {
|
|
27
|
-
return (_jsx(Anchor, { href: href, external: external, hooks: { 'data-bl': 'text-link', 'data-variant': variant, className: classes?.root }, children: children }));
|
|
27
|
+
export function TextLink({ href, children, external, prefetch, variant = 'inline', classes }) {
|
|
28
|
+
return (_jsx(Anchor, { href: href, external: external, prefetch: prefetch, hooks: { 'data-bl': 'text-link', 'data-variant': variant, className: classes?.root }, children: children }));
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
31
|
* A `tel:` link. Its accessible name is its visible text, which always includes the number (WCAG 2.5.3). Use it for
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/links.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AACH,OAAO,IAAI,MAAM,WAAW,CAAA;AAG5B,OAAO,EAAE,IAAI,EAAmB,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAA;AAGjD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACnC,CAAC;AAKD,SAAS,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/links.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Link primitives: `ButtonLink`, `TextLink` and `PhoneLink`. Server components; every link is a real `<a href>`.\n * External links open in a new tab with `rel=\"noopener noreferrer\"`. `PhoneLink` requires a typed placement, which the\n * shared instrumentation reads to send one `phone_link_click` per tap; nothing here emits events itself.\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport type { PhoneCallPlacement } from '../../analytics.js'\nimport { Icon, type UiIconName } from '../icons.js'\nimport { linkPrefetchProps, type LinkPrefetch } from '../prefetch.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { UiPhone } from '../types.js'\n\nfunction isExternal(href: string) {\n return /^https?:\\/\\//i.test(href)\n}\n\n/** Data hooks and the skin class for an anchor's root. */\ntype AnchorHooks = { readonly className: string | undefined } & Readonly<Record<`data-${string}`, string | undefined>>\n\nfunction Anchor({\n href,\n external,\n prefetch,\n hooks,\n children,\n}: {\n href: string\n external?: boolean | undefined\n prefetch?: LinkPrefetch | undefined\n hooks: AnchorHooks\n children: ReactNode\n}) {\n if (external ?? isExternal(href)) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" {...hooks}>\n {children}\n </a>\n )\n }\n return (\n <Link href={href} {...linkPrefetchProps(prefetch)} {...hooks}>\n {children}\n </Link>\n )\n}\n\nexport type ButtonLinkSlot = 'root' | 'label' | 'icon'\nexport const BUTTON_VARIANTS = ['primary', 'secondary', 'quiet'] as const\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number]\nexport type ButtonLinkSkin = SkinEntry<ButtonLinkSlot, ButtonVariant, { size?: 'regular' | 'large' }>\n\nexport interface ButtonLinkProps extends ButtonLinkSkin {\n readonly href: string\n readonly children: ReactNode\n /** An icon after the label (`arrow`) or before it (`phone`). */\n readonly icon?: UiIconName\n readonly iconPosition?: 'start' | 'end'\n /** Force external handling; by default `http(s)://` links are external. */\n readonly external?: boolean\n /**\n * Prefetch by link role (`LINK_PREFETCH_POLICY`): `auto` (the default, Next's prefetch in view) for a main action,\n * `intent` for an in-content card link, `false` for footer, area, nearby, map and finder links.\n */\n readonly prefetch?: LinkPrefetch\n}\n\n/** A link styled as a button. A primary action is at least 44 px tall (structure.css). */\nexport function ButtonLink({ href, children, icon, iconPosition = 'end', external, prefetch, variant = 'primary', layout, classes }: ButtonLinkProps) {\n const iconNode = icon ? <span {...slot(classes, 'icon')}><Icon name={icon} /></span> : null\n return (\n <Anchor\n href={href}\n external={external}\n prefetch={prefetch}\n hooks={{ 'data-bl': 'button', 'data-variant': variant, 'data-size': layout?.size ?? 'regular', className: classes?.root }}\n >\n {iconPosition === 'start' ? iconNode : null}\n <span {...slot(classes, 'label')}>{children}</span>\n {iconPosition === 'end' ? iconNode : null}\n </Anchor>\n )\n}\n\nexport type TextLinkSlot = 'root'\nexport type TextLinkSkin = SkinEntry<TextLinkSlot, 'inline' | 'standalone'>\n\nexport interface TextLinkProps extends TextLinkSkin {\n readonly href: string\n readonly children: ReactNode\n readonly external?: boolean\n /**\n * Prefetch by link role (`LINK_PREFETCH_POLICY`): `auto` (the default, Next's prefetch in view) for a main action,\n * `intent` for an in-content card link, `false` for footer, area, nearby, map and finder links.\n */\n readonly prefetch?: LinkPrefetch\n}\n\n/** An inline or standalone text link. */\nexport function TextLink({ href, children, external, prefetch, variant = 'inline', classes }: TextLinkProps) {\n return (\n <Anchor href={href} external={external} prefetch={prefetch} hooks={{ 'data-bl': 'text-link', 'data-variant': variant, className: classes?.root }}>\n {children}\n </Anchor>\n )\n}\n\nexport type PhoneLinkSlot = 'root' | 'icon' | 'label' | 'number'\nexport type PhoneLinkSkin = SkinEntry<PhoneLinkSlot, 'text' | 'button'>\n\nexport interface PhoneLinkProps extends PhoneLinkSkin {\n readonly phone: UiPhone\n /** Where the link sits, for `phone_link_click`. Required: a section sets its own. */\n readonly placement: PhoneCallPlacement\n /** Optional words before the number, for example \"Call\". The accessible name is always the visible text. */\n readonly label?: string\n /** The phone icon on the left (header and hero). */\n readonly showIcon?: boolean\n}\n\n/**\n * A `tel:` link. Its accessible name is its visible text, which always includes the number (WCAG 2.5.3). Use it for\n * every call link: a literal `tel:` anywhere else fails lint (K1) and C10 checks the href against the evidence file.\n */\nexport function PhoneLink({ phone, placement, label, showIcon = false, variant = 'text', classes }: PhoneLinkProps) {\n return (\n <a href={phone.telHref} data-bl=\"phone-link\" data-variant={variant} data-phone-location={placement} className={classes?.root}>\n {showIcon ? (\n <span {...slot(classes, 'icon')}>\n <Icon name=\"phone\" />\n </span>\n ) : null}\n {label ? (\n <>\n <span {...slot(classes, 'label')}>{label}</span>{' '}\n </>\n ) : null}\n <span {...slot(classes, 'number')}>{phone.display}</span>\n </a>\n )\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LeadCtaPlacement } from '../../analytics.js';
|
|
2
|
+
import { type LinkPrefetch } from '../prefetch.js';
|
|
2
3
|
import { type SkinEntry } from '../skin.js';
|
|
3
4
|
import type { ButtonVariant } from './links.js';
|
|
4
5
|
export type QuoteButtonSlot = 'root' | 'label' | 'icon';
|
|
@@ -15,6 +16,8 @@ export interface QuoteButtonProps extends QuoteButtonSkin {
|
|
|
15
16
|
readonly placement: LeadCtaPlacement;
|
|
16
17
|
/** A stable id for `lead_cta_click`, for example `quote-header` or `quote-cta`. */
|
|
17
18
|
readonly ctaId: string;
|
|
19
|
+
/** `auto` by default: the main quote action keeps Next's prefetch (`LINK_PREFETCH_POLICY.primaryAction`). */
|
|
20
|
+
readonly prefetch?: LinkPrefetch;
|
|
18
21
|
}
|
|
19
|
-
export declare function QuoteButton({ label, href, placement, ctaId, variant, layout, classes }: QuoteButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function QuoteButton({ label, href, placement, ctaId, prefetch, variant, layout, classes }: QuoteButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
20
23
|
//# sourceMappingURL=quote-button.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quote-button.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/quote-button.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE1D,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;AACvD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,eAAe,EAAE,aAAa,EAAE;IAAE,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAEvH,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,yGAAyG;IACzG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IACpC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"quote-button.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/quote-button.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE1D,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;AACvD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,eAAe,EAAE,aAAa,EAAE;IAAE,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAEvH,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,yGAAyG;IACzG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IACpC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,6GAA6G;IAC7G,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;CACjC;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAmB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,gBAAgB,2CAoB9H"}
|