@brandlab-digital/site-next 2.1.0 → 3.0.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 +111 -52
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +281 -153
- package/dist/analytics.js.map +1 -1
- package/dist/blog.d.ts +4 -2
- package/dist/blog.d.ts.map +1 -1
- package/dist/blog.js +7 -7
- package/dist/blog.js.map +1 -1
- package/dist/contact/client-entry.d.ts +2 -0
- package/dist/contact/client-entry.d.ts.map +1 -1
- package/dist/contact/client-entry.js +1 -0
- package/dist/contact/client-entry.js.map +1 -1
- package/dist/contact/client.d.ts +12 -22
- package/dist/contact/client.d.ts.map +1 -1
- package/dist/contact/client.js +28 -21
- package/dist/contact/client.js.map +1 -1
- package/dist/contact/form-client.d.ts +148 -0
- package/dist/contact/form-client.d.ts.map +1 -0
- package/dist/contact/form-client.js +234 -0
- package/dist/contact/form-client.js.map +1 -0
- package/dist/contact/index.d.ts +1 -1
- package/dist/contact/index.d.ts.map +1 -1
- package/dist/contact/index.js +1 -1
- package/dist/contact/index.js.map +1 -1
- package/dist/contact/route-handler.d.ts +32 -0
- package/dist/contact/route-handler.d.ts.map +1 -0
- package/dist/contact/route-handler.js +44 -0
- package/dist/contact/route-handler.js.map +1 -0
- package/dist/contact/server-entry.d.ts +1 -0
- package/dist/contact/server-entry.d.ts.map +1 -1
- package/dist/contact/server-entry.js +1 -0
- package/dist/contact/server-entry.js.map +1 -1
- package/dist/contact/server.d.ts +5 -0
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +13 -7
- package/dist/contact/server.js.map +1 -1
- package/dist/contact/shared.d.ts +4 -0
- package/dist/contact/shared.d.ts.map +1 -1
- package/dist/contact/shared.js.map +1 -1
- package/dist/contact/testing.d.ts +29 -0
- package/dist/contact/testing.d.ts.map +1 -1
- package/dist/contact/testing.js +27 -0
- package/dist/contact/testing.js.map +1 -1
- package/dist/google-tag-plan.d.ts +1 -4
- package/dist/google-tag-plan.d.ts.map +1 -1
- package/dist/google-tag-plan.js +1 -14
- package/dist/google-tag-plan.js.map +1 -1
- package/dist/google-tag.d.ts +5 -4
- package/dist/google-tag.d.ts.map +1 -1
- package/dist/google-tag.js +5 -4
- package/dist/google-tag.js.map +1 -1
- package/dist/headers.d.ts +21 -28
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +62 -51
- package/dist/headers.js.map +1 -1
- package/dist/instrumentation-client.d.ts +21 -1
- package/dist/instrumentation-client.d.ts.map +1 -1
- package/dist/instrumentation-client.js +62 -7
- package/dist/instrumentation-client.js.map +1 -1
- package/dist/lead-registry.d.ts +34 -0
- package/dist/lead-registry.d.ts.map +1 -0
- package/dist/lead-registry.js +30 -0
- package/dist/lead-registry.js.map +1 -0
- package/dist/lead-reporting.d.ts +37 -0
- package/dist/lead-reporting.d.ts.map +1 -0
- package/dist/lead-reporting.js +74 -0
- package/dist/lead-reporting.js.map +1 -0
- package/dist/metadata.d.ts +10 -11
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +17 -19
- package/dist/metadata.js.map +1 -1
- package/dist/next-config.d.ts +102 -0
- package/dist/next-config.d.ts.map +1 -0
- package/dist/next-config.js +140 -0
- package/dist/next-config.js.map +1 -0
- package/dist/robots.d.ts +14 -6
- package/dist/robots.d.ts.map +1 -1
- package/dist/robots.js +26 -17
- package/dist/robots.js.map +1 -1
- package/dist/sitemaps.d.ts +34 -0
- package/dist/sitemaps.d.ts.map +1 -1
- package/dist/sitemaps.js +66 -1
- package/dist/sitemaps.js.map +1 -1
- package/dist/testing/server-only.d.ts +8 -0
- package/dist/testing/server-only.d.ts.map +1 -0
- package/dist/testing/server-only.js +8 -0
- package/dist/testing/server-only.js.map +1 -0
- package/dist/ui-client.d.ts +1 -12
- package/dist/ui-client.d.ts.map +1 -1
- package/dist/ui-client.js +0 -17
- package/dist/ui-client.js.map +1 -1
- package/dist/ui.d.ts +22 -2
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +67 -6
- package/dist/ui.js.map +1 -1
- package/package.json +16 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { defineRedirects } from '@brandlab-digital/site-core/redirects';
|
|
2
|
+
import { resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
3
|
+
import { buildBrandlabHeaderRules, } from './headers.js';
|
|
4
|
+
/** Sharp's Linux binaries must travel with the contact route's image sanitiser. */
|
|
5
|
+
export const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING = Object.freeze({
|
|
6
|
+
'/api/contact': Object.freeze([
|
|
7
|
+
'node_modules/sharp/**/*',
|
|
8
|
+
'node_modules/@img/sharp-linux-x64/**/*',
|
|
9
|
+
'node_modules/@img/sharp-libvips-linux-x64/**/*',
|
|
10
|
+
]),
|
|
11
|
+
});
|
|
12
|
+
const MDX_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mdx', 'md'];
|
|
13
|
+
const OPTION_KEYS = new Set([
|
|
14
|
+
'site',
|
|
15
|
+
'redirects',
|
|
16
|
+
'images',
|
|
17
|
+
'csp',
|
|
18
|
+
'hsts',
|
|
19
|
+
'permissionsPolicy',
|
|
20
|
+
'extraHeaders',
|
|
21
|
+
'mdx',
|
|
22
|
+
'environment',
|
|
23
|
+
]);
|
|
24
|
+
const IMAGE_KEYS = new Set(['deviceSizes', 'imageSizes', 'qualities', 'minimumCacheTTL', 'remotePatterns']);
|
|
25
|
+
function assertKnownKeys(value, allowed, field, hint = '') {
|
|
26
|
+
const unsupported = Object.keys(value).filter((key) => !allowed.has(key));
|
|
27
|
+
if (unsupported.length > 0) {
|
|
28
|
+
throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.${hint}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function positiveIntegers(values, field) {
|
|
32
|
+
if (values === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (!Array.isArray(values) ||
|
|
35
|
+
values.length === 0 ||
|
|
36
|
+
values.some((value) => !Number.isSafeInteger(value) || value < 1)) {
|
|
37
|
+
throw new Error(`images.${field} must list positive integers.`);
|
|
38
|
+
}
|
|
39
|
+
return [...values];
|
|
40
|
+
}
|
|
41
|
+
function buildImagesConfig(images) {
|
|
42
|
+
if (!images || typeof images !== 'object' || Array.isArray(images)) {
|
|
43
|
+
throw new Error('images must declare the site image settings ({} for Next defaults).');
|
|
44
|
+
}
|
|
45
|
+
assertKnownKeys(images, IMAGE_KEYS, 'images', ' The output format is always image/webp.');
|
|
46
|
+
const deviceSizes = positiveIntegers(images.deviceSizes, 'deviceSizes');
|
|
47
|
+
const imageSizes = positiveIntegers(images.imageSizes, 'imageSizes');
|
|
48
|
+
const qualities = positiveIntegers(images.qualities, 'qualities');
|
|
49
|
+
if (qualities?.some((quality) => quality > 100)) {
|
|
50
|
+
throw new Error('images.qualities must be between 1 and 100.');
|
|
51
|
+
}
|
|
52
|
+
if (images.minimumCacheTTL !== undefined &&
|
|
53
|
+
(!Number.isSafeInteger(images.minimumCacheTTL) || images.minimumCacheTTL < 0)) {
|
|
54
|
+
throw new Error('images.minimumCacheTTL must be a non-negative integer of seconds.');
|
|
55
|
+
}
|
|
56
|
+
if (images.remotePatterns !== undefined && !Array.isArray(images.remotePatterns)) {
|
|
57
|
+
throw new Error('images.remotePatterns must be an array.');
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
formats: ['image/webp'],
|
|
61
|
+
...(deviceSizes ? { deviceSizes } : {}),
|
|
62
|
+
...(imageSizes ? { imageSizes } : {}),
|
|
63
|
+
...(qualities ? { qualities } : {}),
|
|
64
|
+
...(images.minimumCacheTTL !== undefined ? { minimumCacheTTL: images.minimumCacheTTL } : {}),
|
|
65
|
+
...(images.remotePatterns
|
|
66
|
+
? { remotePatterns: images.remotePatterns.map((pattern) => ({ ...pattern })) }
|
|
67
|
+
: {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Converts the redirect table to Next redirects: each entry keeps its 301 or
|
|
72
|
+
* 308 status (Next forwards the query string), gains a single-hop
|
|
73
|
+
* trailing-slash variant, then one trailing-slash rule and one permanent
|
|
74
|
+
* canonical-host rule per alias host follow. There is no x-forwarded-proto
|
|
75
|
+
* rule: Vercel's edge already enforces HTTPS.
|
|
76
|
+
*/
|
|
77
|
+
export function buildBrandlabRedirects(options) {
|
|
78
|
+
const table = defineRedirects(options.redirects);
|
|
79
|
+
const entries = table.flatMap((redirect) => {
|
|
80
|
+
const status = redirect.status === 301
|
|
81
|
+
? { statusCode: 301 }
|
|
82
|
+
: { permanent: true };
|
|
83
|
+
const has = redirect.host ? { has: [{ type: 'host', value: redirect.host }] } : {};
|
|
84
|
+
const record = (source) => ({
|
|
85
|
+
source,
|
|
86
|
+
destination: redirect.destination,
|
|
87
|
+
...status,
|
|
88
|
+
...has,
|
|
89
|
+
});
|
|
90
|
+
return redirect.source === '/'
|
|
91
|
+
? [record(redirect.source)]
|
|
92
|
+
: [record(redirect.source), record(`${redirect.source}/`)];
|
|
93
|
+
});
|
|
94
|
+
return [
|
|
95
|
+
...entries,
|
|
96
|
+
{ source: '/:path+/', destination: '/:path+', permanent: true },
|
|
97
|
+
...options.site.aliasHosts.map((host) => ({
|
|
98
|
+
source: '/:path*',
|
|
99
|
+
has: [{ type: 'host', value: host }],
|
|
100
|
+
destination: `${options.site.canonicalUrl}/:path*`,
|
|
101
|
+
permanent: true,
|
|
102
|
+
})),
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
export function defineBrandlabNextConfig(options) {
|
|
106
|
+
if (!options || typeof options !== 'object') {
|
|
107
|
+
throw new TypeError('defineBrandlabNextConfig requires the site, redirects and images.');
|
|
108
|
+
}
|
|
109
|
+
assertKnownKeys(options, OPTION_KEYS, 'next.config options');
|
|
110
|
+
if (!options.site?.canonicalUrl || !Array.isArray(options.site.aliasHosts)) {
|
|
111
|
+
throw new Error('site must come from defineBrandlabSite.');
|
|
112
|
+
}
|
|
113
|
+
if (options.mdx !== undefined && typeof options.mdx !== 'function') {
|
|
114
|
+
throw new TypeError('mdx must be the @next/mdx config wrapper.');
|
|
115
|
+
}
|
|
116
|
+
const deployment = resolveBrandlabDeployment(options.environment ?? process.env);
|
|
117
|
+
const headerRules = buildBrandlabHeaderRules({
|
|
118
|
+
deployment,
|
|
119
|
+
...(options.csp ? { contentSecurityPolicyExtensions: options.csp } : {}),
|
|
120
|
+
...(options.hsts ? { hsts: options.hsts } : {}),
|
|
121
|
+
...(options.permissionsPolicy ? { permissionsPolicy: options.permissionsPolicy } : {}),
|
|
122
|
+
...(options.extraHeaders ? { extraHeaders: options.extraHeaders } : {}),
|
|
123
|
+
});
|
|
124
|
+
const redirects = buildBrandlabRedirects({ site: options.site, redirects: options.redirects });
|
|
125
|
+
const config = {
|
|
126
|
+
poweredByHeader: false,
|
|
127
|
+
skipTrailingSlashRedirect: true,
|
|
128
|
+
env: { NEXT_PUBLIC_VERCEL_ENV: deployment.environment },
|
|
129
|
+
outputFileTracingIncludes: Object.fromEntries(Object.entries(BRANDLAB_CONTACT_OUTPUT_FILE_TRACING).map(([route, globs]) => [route, [...globs]])),
|
|
130
|
+
images: buildImagesConfig(options.images),
|
|
131
|
+
...(options.mdx ? { pageExtensions: [...MDX_PAGE_EXTENSIONS] } : {}),
|
|
132
|
+
headers: async () => headerRules.map((rule) => ({ source: rule.source, headers: rule.headers.map((header) => ({ ...header })) })),
|
|
133
|
+
redirects: async () => redirects.map((redirect) => ({
|
|
134
|
+
...redirect,
|
|
135
|
+
...(redirect.has ? { has: redirect.has.map((condition) => ({ ...condition })) } : {}),
|
|
136
|
+
})),
|
|
137
|
+
};
|
|
138
|
+
return options.mdx ? options.mdx(config) : config;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=next-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next-config.js","sourceRoot":"","sources":["../src/next-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA8B,MAAM,uCAAuC,CAAA;AACnG,OAAO,EACL,yBAAyB,GAI1B,MAAM,kCAAkC,CAAA;AACzC,OAAO,EACL,wBAAwB,GAKzB,MAAM,cAAc,CAAA;AA+DrB,mFAAmF;AACnF,MAAM,CAAC,MAAM,oCAAoC,GAAG,MAAM,CAAC,MAAM,CAAC;IAChE,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC;QAC5B,yBAAyB;QACzB,wCAAwC;QACxC,gDAAgD;KACjD,CAAC;CACH,CAAC,CAAA;AAEF,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;AACnE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,MAAM;IACN,WAAW;IACX,QAAQ;IACR,KAAK;IACL,MAAM;IACN,mBAAmB;IACnB,cAAc;IACd,KAAK;IACL,aAAa;CACd,CAAC,CAAA;AACF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,CAAC,CAAC,CAAA;AAE3G,SAAS,eAAe,CAAC,KAAa,EAAE,OAA4B,EAAE,KAAa,EAAE,IAAI,GAAG,EAAE;IAC5F,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IACzE,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,+BAA+B,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;IAC1F,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAqC,EAAE,KAAa;IAC5E,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC1C,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACtB,MAAM,CAAC,MAAM,KAAK,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,EACjE,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,+BAA+B,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,CAAA;AACpB,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAiC;IAC1D,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAA;IACxF,CAAC;IACD,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,0CAA0C,CAAC,CAAA;IACzF,MAAM,WAAW,GAAG,gBAAgB,CAAC,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC,CAAA;IACvE,MAAM,UAAU,GAAG,gBAAgB,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IACpE,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IACjE,IAAI,SAAS,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IACD,IACE,MAAM,CAAC,eAAe,KAAK,SAAS;QACpC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,MAAM,CAAC,eAAe,GAAG,CAAC,CAAC,EAC7E,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAA;IACtF,CAAC;IACD,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;QACjF,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IACD,OAAO;QACL,OAAO,EAAE,CAAC,YAAY,CAAC;QACvB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnC,GAAG,CAAC,MAAM,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5F,GAAG,CAAC,MAAM,CAAC,cAAc;YACvB,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,EAAE;YAC9E,CAAC,CAAC,EAAE,CAAC;KACR,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAGtC;IACC,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,EAA0B,EAAE;QACjE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,KAAK,GAAG;YACpC,CAAC,CAAC,EAAE,UAAU,EAAE,GAAY,EAAE;YAC9B,CAAC,CAAC,EAAE,SAAS,EAAE,IAAa,EAAE,CAAA;QAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAC3F,MAAM,MAAM,GAAG,CAAC,MAAc,EAAwB,EAAE,CAAC,CAAC;YACxD,MAAM;YACN,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,GAAG,MAAM;YACT,GAAG,GAAG;SACP,CAAC,CAAA;QACF,OAAO,QAAQ,CAAC,MAAM,KAAK,GAAG;YAC5B,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;IACF,OAAO;QACL,GAAG,OAAO;QACV,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE;QAC/D,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAwB,EAAE,CAAC,CAAC;YAC9D,MAAM,EAAE,SAAS;YACjB,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACpC,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,SAAS;YAClD,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;KACJ,CAAA;AACH,CAAC;AAqBD,MAAM,UAAU,wBAAwB,CACtC,OAAsF;IAEtF,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,SAAS,CAAC,mEAAmE,CAAC,CAAA;IAC1F,CAAC;IACD,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,qBAAqB,CAAC,CAAA;IAC5D,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QACnE,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAA;IAClE,CAAC;IACD,MAAM,UAAU,GAAG,yBAAyB,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,CAAA;IAChF,MAAM,WAAW,GAAG,wBAAwB,CAAC;QAC3C,UAAU;QACV,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,+BAA+B,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxE,CAAC,CAAA;IACF,MAAM,SAAS,GAAG,sBAAsB,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAA;IAE9F,MAAM,MAAM,GAAuB;QACjC,eAAe,EAAE,KAAK;QACtB,yBAAyB,EAAE,IAAI;QAC/B,GAAG,EAAE,EAAE,sBAAsB,EAAE,UAAU,CAAC,WAAW,EAAE;QACvD,yBAAyB,EAAE,MAAM,CAAC,WAAW,CAC3C,MAAM,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAClG;QACD,MAAM,EAAE,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC;QACzC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,OAAO,EAAE,KAAK,IAAI,EAAE,CAClB,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9G,SAAS,EAAE,KAAK,IAAI,EAAE,CACpB,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAC3B,GAAG,QAAQ;YACX,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtF,CAAC,CAAC;KACN,CAAA;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AACnD,CAAC","sourcesContent":["import { defineRedirects, type BrandlabRedirectTable } from '@brandlab-digital/site-core/redirects'\nimport {\n resolveBrandlabDeployment,\n type BrandlabDeploymentEnvironment,\n type BrandlabEnvironment,\n type BrandlabSite,\n} from '@brandlab-digital/site-core/site'\nimport {\n buildBrandlabHeaderRules,\n type BrandlabContentSecurityPolicyExtensions,\n type BrandlabHeader,\n type BrandlabHeaderRule,\n type BrandlabHstsExpansion,\n} from './headers.js'\n\n/** Next redirect records: 308 stays `permanent: true`, 301 stays `statusCode: 301`. */\nexport type BrandlabNextRedirect = {\n source: string\n destination: string\n has?: { type: 'host'; value: string }[]\n} & ({ permanent: true; statusCode?: never } | { statusCode: 301; permanent?: never })\n\nexport interface BrandlabNextRemotePattern {\n protocol?: 'http' | 'https'\n hostname: string\n port?: string\n pathname?: string\n search?: string\n}\n\n/** The site's own image settings; the output format is always WebP. */\nexport interface BrandlabNextImagesOptions {\n deviceSizes?: readonly number[]\n imageSizes?: readonly number[]\n qualities?: readonly number[]\n minimumCacheTTL?: number\n remotePatterns?: readonly BrandlabNextRemotePattern[]\n}\n\nexport interface BrandlabNextImagesConfig {\n formats: 'image/webp'[]\n deviceSizes?: number[]\n imageSizes?: number[]\n qualities?: number[]\n minimumCacheTTL?: number\n remotePatterns?: BrandlabNextRemotePattern[]\n}\n\nexport interface BrandlabNextConfig {\n poweredByHeader: false\n /** Trailing slashes are handled by single-hop redirect rules instead. */\n skipTrailingSlashRedirect: true\n env: { NEXT_PUBLIC_VERCEL_ENV: BrandlabDeploymentEnvironment }\n outputFileTracingIncludes: Record<string, string[]>\n images: BrandlabNextImagesConfig\n pageExtensions?: string[]\n headers: () => Promise<BrandlabHeaderRule[]>\n redirects: () => Promise<BrandlabNextRedirect[]>\n}\n\nexport interface BrandlabNextConfigOptions {\n site: BrandlabSite\n /** The src/data/redirects.ts table (defineRedirects). */\n redirects: BrandlabRedirectTable\n images: BrandlabNextImagesOptions\n /** Reviewed CSP origins for declared integrations such as Meta Pixel or map embeds. */\n csp?: BrandlabContentSecurityPolicyExtensions\n hsts?: BrandlabHstsExpansion\n /** Reviewed exception to the default Permissions-Policy that is already live. */\n permissionsPolicy?: string\n /** Reviewed response headers that are already live; protected headers cannot be replaced. */\n extraHeaders?: readonly BrandlabHeader[]\n /** Defaults to process.env; tests supply a fixed environment. */\n environment?: BrandlabEnvironment\n}\n\n/** Sharp's Linux binaries must travel with the contact route's image sanitiser. */\nexport const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING = Object.freeze({\n '/api/contact': Object.freeze([\n 'node_modules/sharp/**/*',\n 'node_modules/@img/sharp-linux-x64/**/*',\n 'node_modules/@img/sharp-libvips-linux-x64/**/*',\n ]),\n})\n\nconst MDX_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mdx', 'md']\nconst OPTION_KEYS = new Set([\n 'site',\n 'redirects',\n 'images',\n 'csp',\n 'hsts',\n 'permissionsPolicy',\n 'extraHeaders',\n 'mdx',\n 'environment',\n])\nconst IMAGE_KEYS = new Set(['deviceSizes', 'imageSizes', 'qualities', 'minimumCacheTTL', 'remotePatterns'])\n\nfunction assertKnownKeys(value: object, allowed: ReadonlySet<string>, field: string, hint = '') {\n const unsupported = Object.keys(value).filter((key) => !allowed.has(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.${hint}`)\n }\n}\n\nfunction positiveIntegers(values: readonly number[] | undefined, field: string) {\n if (values === undefined) return undefined\n if (\n !Array.isArray(values) ||\n values.length === 0 ||\n values.some((value) => !Number.isSafeInteger(value) || value < 1)\n ) {\n throw new Error(`images.${field} must list positive integers.`)\n }\n return [...values]\n}\n\nfunction buildImagesConfig(images: BrandlabNextImagesOptions): BrandlabNextImagesConfig {\n if (!images || typeof images !== 'object' || Array.isArray(images)) {\n throw new Error('images must declare the site image settings ({} for Next defaults).')\n }\n assertKnownKeys(images, IMAGE_KEYS, 'images', ' The output format is always image/webp.')\n const deviceSizes = positiveIntegers(images.deviceSizes, 'deviceSizes')\n const imageSizes = positiveIntegers(images.imageSizes, 'imageSizes')\n const qualities = positiveIntegers(images.qualities, 'qualities')\n if (qualities?.some((quality) => quality > 100)) {\n throw new Error('images.qualities must be between 1 and 100.')\n }\n if (\n images.minimumCacheTTL !== undefined &&\n (!Number.isSafeInteger(images.minimumCacheTTL) || images.minimumCacheTTL < 0)\n ) {\n throw new Error('images.minimumCacheTTL must be a non-negative integer of seconds.')\n }\n if (images.remotePatterns !== undefined && !Array.isArray(images.remotePatterns)) {\n throw new Error('images.remotePatterns must be an array.')\n }\n return {\n formats: ['image/webp'],\n ...(deviceSizes ? { deviceSizes } : {}),\n ...(imageSizes ? { imageSizes } : {}),\n ...(qualities ? { qualities } : {}),\n ...(images.minimumCacheTTL !== undefined ? { minimumCacheTTL: images.minimumCacheTTL } : {}),\n ...(images.remotePatterns\n ? { remotePatterns: images.remotePatterns.map((pattern) => ({ ...pattern })) }\n : {}),\n }\n}\n\n/**\n * Converts the redirect table to Next redirects: each entry keeps its 301 or\n * 308 status (Next forwards the query string), gains a single-hop\n * trailing-slash variant, then one trailing-slash rule and one permanent\n * canonical-host rule per alias host follow. There is no x-forwarded-proto\n * rule: Vercel's edge already enforces HTTPS.\n */\nexport function buildBrandlabRedirects(options: {\n site: BrandlabSite\n redirects: BrandlabRedirectTable\n}): BrandlabNextRedirect[] {\n const table = defineRedirects(options.redirects)\n const entries = table.flatMap((redirect): BrandlabNextRedirect[] => {\n const status = redirect.status === 301\n ? { statusCode: 301 as const }\n : { permanent: true as const }\n const has = redirect.host ? { has: [{ type: 'host' as const, value: redirect.host }] } : {}\n const record = (source: string): BrandlabNextRedirect => ({\n source,\n destination: redirect.destination,\n ...status,\n ...has,\n })\n return redirect.source === '/'\n ? [record(redirect.source)]\n : [record(redirect.source), record(`${redirect.source}/`)]\n })\n return [\n ...entries,\n { source: '/:path+/', destination: '/:path+', permanent: true },\n ...options.site.aliasHosts.map((host): BrandlabNextRedirect => ({\n source: '/:path*',\n has: [{ type: 'host', value: host }],\n destination: `${options.site.canonicalUrl}/:path*`,\n permanent: true,\n })),\n ]\n}\n\n/**\n * The whole next.config.ts after its first line\n * `import '@brandlab-digital/site-next/image-runtime'`:\n * `export default defineBrandlabNextConfig({ site, redirects, images })`.\n *\n * Sets poweredByHeader: false, WebP image output, Sharp output tracing for\n * /api/contact, NEXT_PUBLIC_VERCEL_ENV for browser gates, the deployment's\n * header rules and the redirect table. Validation runs when the config loads,\n * so an invalid table or header exception fails the build.\n */\nexport function defineBrandlabNextConfig(\n options: BrandlabNextConfigOptions & { mdx?: undefined },\n): BrandlabNextConfig\nexport function defineBrandlabNextConfig<Wrapped>(\n options: BrandlabNextConfigOptions & {\n /** The @next/mdx wrapper: createMDX({ options: { remarkPlugins: ['remark-gfm'] } }). */\n mdx: (config: BrandlabNextConfig) => Wrapped\n },\n): Wrapped\nexport function defineBrandlabNextConfig(\n options: BrandlabNextConfigOptions & { mdx?: (config: BrandlabNextConfig) => unknown },\n): unknown {\n if (!options || typeof options !== 'object') {\n throw new TypeError('defineBrandlabNextConfig requires the site, redirects and images.')\n }\n assertKnownKeys(options, OPTION_KEYS, 'next.config options')\n if (!options.site?.canonicalUrl || !Array.isArray(options.site.aliasHosts)) {\n throw new Error('site must come from defineBrandlabSite.')\n }\n if (options.mdx !== undefined && typeof options.mdx !== 'function') {\n throw new TypeError('mdx must be the @next/mdx config wrapper.')\n }\n const deployment = resolveBrandlabDeployment(options.environment ?? process.env)\n const headerRules = buildBrandlabHeaderRules({\n deployment,\n ...(options.csp ? { contentSecurityPolicyExtensions: options.csp } : {}),\n ...(options.hsts ? { hsts: options.hsts } : {}),\n ...(options.permissionsPolicy ? { permissionsPolicy: options.permissionsPolicy } : {}),\n ...(options.extraHeaders ? { extraHeaders: options.extraHeaders } : {}),\n })\n const redirects = buildBrandlabRedirects({ site: options.site, redirects: options.redirects })\n\n const config: BrandlabNextConfig = {\n poweredByHeader: false,\n skipTrailingSlashRedirect: true,\n env: { NEXT_PUBLIC_VERCEL_ENV: deployment.environment },\n outputFileTracingIncludes: Object.fromEntries(\n Object.entries(BRANDLAB_CONTACT_OUTPUT_FILE_TRACING).map(([route, globs]) => [route, [...globs]]),\n ),\n images: buildImagesConfig(options.images),\n ...(options.mdx ? { pageExtensions: [...MDX_PAGE_EXTENSIONS] } : {}),\n headers: async () =>\n headerRules.map((rule) => ({ source: rule.source, headers: rule.headers.map((header) => ({ ...header })) })),\n redirects: async () =>\n redirects.map((redirect) => ({\n ...redirect,\n ...(redirect.has ? { has: redirect.has.map((condition) => ({ ...condition })) } : {}),\n })),\n }\n return options.mdx ? options.mdx(config) : config\n}\n"]}
|
package/dist/robots.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type BrandlabDeployment, type BrandlabEnvironment, type BrandlabSite } from '@brandlab-digital/site-core/site';
|
|
1
2
|
export interface BrandlabRobotsMetadata {
|
|
2
3
|
rules: Array<{
|
|
3
4
|
userAgent: string;
|
|
@@ -6,10 +7,17 @@ export interface BrandlabRobotsMetadata {
|
|
|
6
7
|
}>;
|
|
7
8
|
sitemap?: string;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Crawlers may index any production build except a Vercel Preview: the real
|
|
12
|
+
* production deployment and a local `next start`. `next dev` and every
|
|
13
|
+
* Preview are closed. There is no environment override.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isBrandlabIndexableDeployment(deployment: BrandlabDeployment): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* `src/app/robots.ts`: `export default createBrandlabRobots(site)`.
|
|
18
|
+
* The deployment is resolved when Next renders robots.txt.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createBrandlabRobots(site: BrandlabSite, options?: {
|
|
21
|
+
environment?: BrandlabEnvironment;
|
|
22
|
+
}): () => BrandlabRobotsMetadata;
|
|
15
23
|
//# 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":"
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,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,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,kBAAkB,WAG3E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAAO,SAGzB,sBAAsB,CAajD"}
|
package/dist/robots.js
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
2
|
+
/**
|
|
3
|
+
* Crawlers may index any production build except a Vercel Preview: the real
|
|
4
|
+
* production deployment and a local `next start`. `next dev` and every
|
|
5
|
+
* Preview are closed. There is no environment override.
|
|
6
|
+
*/
|
|
7
|
+
export function isBrandlabIndexableDeployment(deployment) {
|
|
8
|
+
return deployment.environment === 'production' ||
|
|
9
|
+
(deployment.environment === 'development' && deployment.productionBuild);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `src/app/robots.ts`: `export default createBrandlabRobots(site)`.
|
|
13
|
+
* The deployment is resolved when Next renders robots.txt.
|
|
14
|
+
*/
|
|
15
|
+
export function createBrandlabRobots(site, options = {}) {
|
|
16
|
+
const sitemap = `${site.canonicalUrl}/sitemap.xml`;
|
|
17
|
+
return function robots() {
|
|
18
|
+
const deployment = resolveBrandlabDeployment(options.environment ?? process.env);
|
|
19
|
+
if (!isBrandlabIndexableDeployment(deployment)) {
|
|
20
|
+
return { rules: [{ userAgent: '*', disallow: '/' }] };
|
|
21
|
+
}
|
|
22
|
+
// No `host` directive: it is a non-standard robots.txt extension ignored
|
|
23
|
+
// by Google and Bing; canonical-host consolidation is owned by permanent
|
|
24
|
+
// redirects and canonical URLs.
|
|
10
25
|
return {
|
|
11
|
-
rules: [{ userAgent: '*',
|
|
26
|
+
rules: [{ userAgent: '*', allow: '/' }],
|
|
27
|
+
sitemap,
|
|
12
28
|
};
|
|
13
|
-
}
|
|
14
|
-
// No `host` directive: it is a non-standard robots.txt extension ignored
|
|
15
|
-
// by Google and Bing; canonical-host consolidation is owned by permanent
|
|
16
|
-
// edge redirects + canonical URLs (Brandlab Project Standard §5).
|
|
17
|
-
return {
|
|
18
|
-
rules: [{ userAgent: '*', allow: '/' }],
|
|
19
|
-
sitemap: getAbsoluteUrl(options.siteUrl, '/sitemap.xml'),
|
|
20
29
|
};
|
|
21
30
|
}
|
|
22
31
|
//# 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,
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,GAI1B,MAAM,kCAAkC,CAAA;AAOzC;;;;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;;;GAGG;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,CAAC;YACvC,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 }>\n sitemap?: string\n}\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.\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: '/' }],\n sitemap,\n }\n }\n}\n"]}
|
package/dist/sitemaps.d.ts
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
|
+
import type { BrandlabRouteCatalog } from '@brandlab-digital/site-core/routes';
|
|
2
|
+
import type { BrandlabSite } from '@brandlab-digital/site-core/site';
|
|
1
3
|
import { type SitemapEntry } from '@brandlab-digital/site-core/sitemaps';
|
|
2
4
|
export declare function sitemapXmlResponse(entries: readonly SitemapEntry[]): Response;
|
|
3
5
|
export declare function sitemapIndexXmlResponse(paths: readonly string[], options: {
|
|
4
6
|
absoluteUrl: (path: string) => string;
|
|
5
7
|
}): Response;
|
|
8
|
+
export type BrandlabSitemapMode = 'single' | 'split';
|
|
9
|
+
export interface BrandlabSitemapRouteOptions {
|
|
10
|
+
/** `split` only when brandlab.config declares `sitemap.mode: 'split'`. */
|
|
11
|
+
mode: BrandlabSitemapMode;
|
|
12
|
+
/** Split mode: URLs per child sitemap (default 1000). */
|
|
13
|
+
maxUrlsPerSitemap?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface BrandlabSitemapChunkRouteContext {
|
|
16
|
+
params: Promise<{
|
|
17
|
+
chunk: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export interface BrandlabSitemapRoute {
|
|
21
|
+
readonly mode: BrandlabSitemapMode;
|
|
22
|
+
/** `src/app/sitemap.xml/route.ts`: the URL set, or the sitemap index in split mode. */
|
|
23
|
+
GET(): Response;
|
|
24
|
+
/** Child sitemap paths listed by the index; empty in single mode. */
|
|
25
|
+
chunkPaths(): string[];
|
|
26
|
+
/** `src/app/sitemaps/[chunk]/route.ts` (split mode, with `dynamicParams = false`). */
|
|
27
|
+
chunk: {
|
|
28
|
+
generateStaticParams(): {
|
|
29
|
+
chunk: string;
|
|
30
|
+
}[];
|
|
31
|
+
GET(request: Request, context: BrandlabSitemapChunkRouteContext): Promise<Response>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The route-catalogue sitemap. `lastmod` comes only from each route's
|
|
36
|
+
* `lastSignificantChange`, never from build time. Entries are resolved once,
|
|
37
|
+
* so an invalid catalogue fails the build.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createBrandlabSitemapRoute(routes: BrandlabRouteCatalog, site: BrandlabSite, options?: BrandlabSitemapRouteOptions): BrandlabSitemapRoute;
|
|
6
40
|
//# sourceMappingURL=sitemaps.d.ts.map
|
package/dist/sitemaps.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemaps.d.ts","sourceRoot":"","sources":["../src/sitemaps.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sitemaps.d.ts","sourceRoot":"","sources":["../src/sitemaps.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAqB,MAAM,oCAAoC,CAAA;AACjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,EAOL,KAAK,YAAY,EAClB,MAAM,sCAAsC,CAAA;AAE7C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,QAAQ,CAI7E;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE;IAAE,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GACjD,QAAQ,CAIV;AAED,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEpD,MAAM,WAAW,2BAA2B;IAC1C,0EAA0E;IAC1E,IAAI,EAAE,mBAAmB,CAAA;IACzB,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;IAClC,uFAAuF;IACvF,GAAG,IAAI,QAAQ,CAAA;IACf,qEAAqE;IACrE,UAAU,IAAI,MAAM,EAAE,CAAA;IACtB,sFAAsF;IACtF,KAAK,EAAE;QACL,oBAAoB,IAAI;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;QAC3C,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KACpF,CAAA;CACF;AAiCD;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,YAAY,EAClB,OAAO,GAAE,2BAAgD,GACxD,oBAAoB,CAsCtB"}
|
package/dist/sitemaps.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAbsoluteUrl } from '@brandlab-digital/site-core/metadata';
|
|
2
|
+
import { buildSitemapEntries, createSitemapChunks, getSitemapXmlHeaders, serializeSitemap, serializeSitemapIndex, } from '@brandlab-digital/site-core/sitemaps';
|
|
2
3
|
export function sitemapXmlResponse(entries) {
|
|
3
4
|
return new Response(serializeSitemap(entries), {
|
|
4
5
|
headers: getSitemapXmlHeaders(),
|
|
@@ -9,4 +10,68 @@ export function sitemapIndexXmlResponse(paths, options) {
|
|
|
9
10
|
headers: getSitemapXmlHeaders(),
|
|
10
11
|
});
|
|
11
12
|
}
|
|
13
|
+
const SPLIT_SITEMAP_FAMILIES = [
|
|
14
|
+
{ name: 'services', kinds: ['service'] },
|
|
15
|
+
{ name: 'locations', kinds: ['location'] },
|
|
16
|
+
{ name: 'service-locations', kinds: ['service-location'] },
|
|
17
|
+
{ name: 'blog', kinds: ['blog-post'] },
|
|
18
|
+
{ name: 'projects', kinds: ['project'] },
|
|
19
|
+
];
|
|
20
|
+
const SPLIT_SITEMAP_PREFIX = '/sitemaps';
|
|
21
|
+
const DEFAULT_SPLIT_MAX_URLS = 1_000;
|
|
22
|
+
function splitSitemapChunks(routes, siteUrl, maxUrlsPerSitemap) {
|
|
23
|
+
const familyKinds = new Set(SPLIT_SITEMAP_FAMILIES.flatMap((family) => family.kinds));
|
|
24
|
+
const families = [
|
|
25
|
+
{ name: 'core', routes: routes.filter((route) => !familyKinds.has(route.kind)) },
|
|
26
|
+
...SPLIT_SITEMAP_FAMILIES.map((family) => ({
|
|
27
|
+
name: family.name,
|
|
28
|
+
routes: routes.filter((route) => family.kinds.includes(route.kind)),
|
|
29
|
+
})),
|
|
30
|
+
];
|
|
31
|
+
return families.flatMap((family) => createSitemapChunks(buildSitemapEntries(family.routes, { siteUrl }), {
|
|
32
|
+
pathPrefix: `${SPLIT_SITEMAP_PREFIX}/${family.name}`,
|
|
33
|
+
maxUrlsPerSitemap,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The route-catalogue sitemap. `lastmod` comes only from each route's
|
|
38
|
+
* `lastSignificantChange`, never from build time. Entries are resolved once,
|
|
39
|
+
* so an invalid catalogue fails the build.
|
|
40
|
+
*/
|
|
41
|
+
export function createBrandlabSitemapRoute(routes, site, options = { mode: 'single' }) {
|
|
42
|
+
if (options.mode !== 'single' && options.mode !== 'split') {
|
|
43
|
+
throw new Error('Sitemap mode must be "single" or "split".');
|
|
44
|
+
}
|
|
45
|
+
if (options.mode === 'single' && options.maxUrlsPerSitemap !== undefined) {
|
|
46
|
+
throw new Error('maxUrlsPerSitemap applies only to split sitemaps.');
|
|
47
|
+
}
|
|
48
|
+
const entries = buildSitemapEntries(routes, { siteUrl: site.canonicalUrl });
|
|
49
|
+
const chunks = options.mode === 'split'
|
|
50
|
+
? splitSitemapChunks(routes, site.canonicalUrl, options.maxUrlsPerSitemap ?? DEFAULT_SPLIT_MAX_URLS)
|
|
51
|
+
: [];
|
|
52
|
+
const chunkByFileName = new Map(chunks.map((chunk) => [chunk.path.slice(SPLIT_SITEMAP_PREFIX.length + 1), chunk]));
|
|
53
|
+
const absoluteUrl = (path) => getAbsoluteUrl(site.canonicalUrl, path);
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
mode: options.mode,
|
|
56
|
+
GET() {
|
|
57
|
+
return options.mode === 'split'
|
|
58
|
+
? sitemapIndexXmlResponse(chunks.map((chunk) => chunk.path), { absoluteUrl })
|
|
59
|
+
: sitemapXmlResponse(entries);
|
|
60
|
+
},
|
|
61
|
+
chunkPaths() {
|
|
62
|
+
return chunks.map((chunk) => chunk.path);
|
|
63
|
+
},
|
|
64
|
+
chunk: Object.freeze({
|
|
65
|
+
generateStaticParams() {
|
|
66
|
+
return [...chunkByFileName.keys()].map((chunk) => ({ chunk }));
|
|
67
|
+
},
|
|
68
|
+
async GET(_request, context) {
|
|
69
|
+
const found = chunkByFileName.get((await context.params).chunk);
|
|
70
|
+
return found
|
|
71
|
+
? sitemapXmlResponse(found.entries)
|
|
72
|
+
: new Response('Not found', { status: 404 });
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
12
77
|
//# sourceMappingURL=sitemaps.js.map
|
package/dist/sitemaps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemaps.js","sourceRoot":"","sources":["../src/sitemaps.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"sitemaps.js","sourceRoot":"","sources":["../src/sitemaps.ts"],"names":[],"mappings":"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","sourcesContent":["import { getAbsoluteUrl } from '@brandlab-digital/site-core/metadata'\nimport type { BrandlabRouteCatalog, BrandlabRouteKind } from '@brandlab-digital/site-core/routes'\nimport type { BrandlabSite } from '@brandlab-digital/site-core/site'\nimport {\n buildSitemapEntries,\n createSitemapChunks,\n getSitemapXmlHeaders,\n serializeSitemap,\n serializeSitemapIndex,\n type SitemapChunk,\n type SitemapEntry,\n} from '@brandlab-digital/site-core/sitemaps'\n\nexport function sitemapXmlResponse(entries: readonly SitemapEntry[]): Response {\n return new Response(serializeSitemap(entries), {\n headers: getSitemapXmlHeaders(),\n })\n}\n\nexport function sitemapIndexXmlResponse(\n paths: readonly string[],\n options: { absoluteUrl: (path: string) => string },\n): Response {\n return new Response(serializeSitemapIndex(paths, options), {\n headers: getSitemapXmlHeaders(),\n })\n}\n\nexport type BrandlabSitemapMode = 'single' | 'split'\n\nexport interface BrandlabSitemapRouteOptions {\n /** `split` only when brandlab.config declares `sitemap.mode: 'split'`. */\n mode: BrandlabSitemapMode\n /** Split mode: URLs per child sitemap (default 1000). */\n maxUrlsPerSitemap?: number\n}\n\nexport interface BrandlabSitemapChunkRouteContext {\n params: Promise<{ chunk: string }>\n}\n\nexport interface BrandlabSitemapRoute {\n readonly mode: BrandlabSitemapMode\n /** `src/app/sitemap.xml/route.ts`: the URL set, or the sitemap index in split mode. */\n GET(): Response\n /** Child sitemap paths listed by the index; empty in single mode. */\n chunkPaths(): string[]\n /** `src/app/sitemaps/[chunk]/route.ts` (split mode, with `dynamicParams = false`). */\n chunk: {\n generateStaticParams(): { chunk: string }[]\n GET(request: Request, context: BrandlabSitemapChunkRouteContext): Promise<Response>\n }\n}\n\nconst SPLIT_SITEMAP_FAMILIES: readonly { name: string; kinds: readonly BrandlabRouteKind[] }[] = [\n { name: 'services', kinds: ['service'] },\n { name: 'locations', kinds: ['location'] },\n { name: 'service-locations', kinds: ['service-location'] },\n { name: 'blog', kinds: ['blog-post'] },\n { name: 'projects', kinds: ['project'] },\n]\nconst SPLIT_SITEMAP_PREFIX = '/sitemaps'\nconst DEFAULT_SPLIT_MAX_URLS = 1_000\n\nfunction splitSitemapChunks(\n routes: BrandlabRouteCatalog,\n siteUrl: string,\n maxUrlsPerSitemap: number,\n): SitemapChunk[] {\n const familyKinds = new Set(SPLIT_SITEMAP_FAMILIES.flatMap((family) => family.kinds))\n const families = [\n { name: 'core', routes: routes.filter((route) => !familyKinds.has(route.kind)) },\n ...SPLIT_SITEMAP_FAMILIES.map((family) => ({\n name: family.name,\n routes: routes.filter((route) => family.kinds.includes(route.kind)),\n })),\n ]\n return families.flatMap((family) =>\n createSitemapChunks(buildSitemapEntries(family.routes, { siteUrl }), {\n pathPrefix: `${SPLIT_SITEMAP_PREFIX}/${family.name}`,\n maxUrlsPerSitemap,\n }),\n )\n}\n\n/**\n * The route-catalogue sitemap. `lastmod` comes only from each route's\n * `lastSignificantChange`, never from build time. Entries are resolved once,\n * so an invalid catalogue fails the build.\n */\nexport function createBrandlabSitemapRoute(\n routes: BrandlabRouteCatalog,\n site: BrandlabSite,\n options: BrandlabSitemapRouteOptions = { mode: 'single' },\n): BrandlabSitemapRoute {\n if (options.mode !== 'single' && options.mode !== 'split') {\n throw new Error('Sitemap mode must be \"single\" or \"split\".')\n }\n if (options.mode === 'single' && options.maxUrlsPerSitemap !== undefined) {\n throw new Error('maxUrlsPerSitemap applies only to split sitemaps.')\n }\n const entries = buildSitemapEntries(routes, { siteUrl: site.canonicalUrl })\n const chunks = options.mode === 'split'\n ? splitSitemapChunks(routes, site.canonicalUrl, options.maxUrlsPerSitemap ?? DEFAULT_SPLIT_MAX_URLS)\n : []\n const chunkByFileName = new Map(\n chunks.map((chunk) => [chunk.path.slice(SPLIT_SITEMAP_PREFIX.length + 1), chunk]),\n )\n const absoluteUrl = (path: string) => getAbsoluteUrl(site.canonicalUrl, path)\n\n return Object.freeze({\n mode: options.mode,\n GET() {\n return options.mode === 'split'\n ? sitemapIndexXmlResponse(chunks.map((chunk) => chunk.path), { absoluteUrl })\n : sitemapXmlResponse(entries)\n },\n chunkPaths() {\n return chunks.map((chunk) => chunk.path)\n },\n chunk: Object.freeze({\n generateStaticParams() {\n return [...chunkByFileName.keys()].map((chunk) => ({ chunk }))\n },\n async GET(_request: Request, context: BrandlabSitemapChunkRouteContext) {\n const found = chunkByFileName.get((await context.params).chunk)\n return found\n ? sitemapXmlResponse(found.entries)\n : new Response('Not found', { status: 404 })\n },\n }),\n })\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty stand-in for the `server-only` guard in Vitest, where server modules
|
|
3
|
+
* run outside Next's react-server condition. Alias it in vitest.config.ts:
|
|
4
|
+
* `resolve: { alias: { 'server-only': '@brandlab-digital/site-next/testing/server-only' } }`.
|
|
5
|
+
* Never import it from application code.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=server-only.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-only.d.ts","sourceRoot":"","sources":["../../src/testing/server-only.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty stand-in for the `server-only` guard in Vitest, where server modules
|
|
3
|
+
* run outside Next's react-server condition. Alias it in vitest.config.ts:
|
|
4
|
+
* `resolve: { alias: { 'server-only': '@brandlab-digital/site-next/testing/server-only' } }`.
|
|
5
|
+
* Never import it from application code.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=server-only.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-only.js","sourceRoot":"","sources":["../../src/testing/server-only.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAA","sourcesContent":["/**\n * Empty stand-in for the `server-only` guard in Vitest, where server modules\n * run outside Next's react-server condition. Alias it in vitest.config.ts:\n * `resolve: { alias: { 'server-only': '@brandlab-digital/site-next/testing/server-only' } }`.\n * Never import it from application code.\n */\nexport {}\n"]}
|
package/dist/ui-client.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type PhoneCallPlacement } from './analytics.js';
|
|
1
|
+
import { type DialogHTMLAttributes, type ReactNode, type RefObject } from 'react';
|
|
3
2
|
export interface HeadlessDialogProps extends Omit<DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClose'> {
|
|
4
3
|
open: boolean;
|
|
5
4
|
onClose: () => void;
|
|
@@ -10,16 +9,6 @@ export interface HeadlessDialogProps extends Omit<DialogHTMLAttributes<HTMLDialo
|
|
|
10
9
|
children: ReactNode;
|
|
11
10
|
}
|
|
12
11
|
export declare function HeadlessDialog({ open, onClose, labelledBy, describedBy, initialFocusRef, returnFocusRef, children, ...props }: HeadlessDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
-
export interface TrackedTelephoneLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'onClick'> {
|
|
14
|
-
phoneE164: string;
|
|
15
|
-
placement: PhoneCallPlacement;
|
|
16
|
-
onTrack: (input: {
|
|
17
|
-
event: 'phone_link_click';
|
|
18
|
-
placement: PhoneCallPlacement;
|
|
19
|
-
}) => void;
|
|
20
|
-
children: ReactNode;
|
|
21
|
-
}
|
|
22
|
-
export declare function TrackedTelephoneLink({ phoneE164, placement, onTrack, children, ...props }: TrackedTelephoneLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
23
12
|
export interface GalleryLightboxItem {
|
|
24
13
|
id: string;
|
|
25
14
|
alt: string;
|
package/dist/ui-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-client.d.ts","sourceRoot":"","sources":["../src/ui-client.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,oBAAoB,
|
|
1
|
+
{"version":3,"file":"ui-client.d.ts","sourceRoot":"","sources":["../src/ui-client.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,oBAAoB,EAEzB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAEd,MAAM,WAAW,mBACf,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzE,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC/C,cAAc,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC9C,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,OAAO,EACP,UAAU,EACV,WAAW,EACX,eAAe,EACf,cAAc,EACd,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,mBAAmB,2CA2CrB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,oBAAoB,CAAC,IAAI,SAAS,mBAAmB;IACpE,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,SAAS,CAAA;IAChB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,CAAA;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,eAAe,CAAC,IAAI,SAAS,mBAAmB,EAAE,EAChE,IAAI,EACJ,KAAK,EACL,YAAgB,EAChB,OAAO,EACP,KAAK,EACL,OAAO,EACP,UAAU,EACV,aAAa,EACb,SAAS,EACT,UAAU,GACX,EAAE,oBAAoB,CAAC,IAAI,CAAC,2CAgD5B;AAED,wBAAgB,uBAAuB,YAUtC;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,uBAAuB,QAuBxF"}
|
package/dist/ui-client.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState, } from 'react';
|
|
4
|
-
import { PHONE_CALL_PLACEMENTS, } from './analytics.js';
|
|
5
4
|
export function HeadlessDialog({ open, onClose, labelledBy, describedBy, initialFocusRef, returnFocusRef, children, ...props }) {
|
|
6
5
|
const dialogRef = useRef(null);
|
|
7
6
|
const previouslyFocused = useRef(null);
|
|
@@ -37,22 +36,6 @@ export function HeadlessDialog({ open, onClose, labelledBy, describedBy, initial
|
|
|
37
36
|
onClose();
|
|
38
37
|
}, onClose: onClose, ref: dialogRef, children: children }));
|
|
39
38
|
}
|
|
40
|
-
export function TrackedTelephoneLink({ phoneE164, placement, onTrack, children, ...props }) {
|
|
41
|
-
if (!/^\+[1-9]\d{7,14}$/.test(phoneE164)) {
|
|
42
|
-
throw new Error('TrackedTelephoneLink requires an E.164 phone number.');
|
|
43
|
-
}
|
|
44
|
-
if (!PHONE_CALL_PLACEMENTS.includes(placement)) {
|
|
45
|
-
throw new Error('TrackedTelephoneLink requires a canonical phone-call placement.');
|
|
46
|
-
}
|
|
47
|
-
return (_jsx("a", { ...props, href: `tel:${phoneE164}`, onClick: () => {
|
|
48
|
-
try {
|
|
49
|
-
onTrack({ event: 'phone_link_click', placement });
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
// Tracking must never block the telephone action.
|
|
53
|
-
}
|
|
54
|
-
}, children: children }));
|
|
55
|
-
}
|
|
56
39
|
export function GalleryLightbox({ open, items, initialIndex = 0, titleId, title, onClose, renderItem, previousLabel, nextLabel, closeLabel, }) {
|
|
57
40
|
const [index, setIndex] = useState(initialIndex);
|
|
58
41
|
const closeRef = useRef(null);
|
package/dist/ui-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-client.js","sourceRoot":"","sources":["../src/ui-client.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport {\n useEffect,\n useRef,\n useState,\n type AnchorHTMLAttributes,\n type DialogHTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n type RefObject,\n} from 'react'\nimport {\n PHONE_CALL_PLACEMENTS,\n type PhoneCallPlacement,\n} from './analytics.js'\n\nexport interface HeadlessDialogProps\n extends Omit<DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClose'> {\n open: boolean\n onClose: () => void\n labelledBy: string\n describedBy?: string\n initialFocusRef?: RefObject<HTMLElement | null>\n returnFocusRef?: RefObject<HTMLElement | null>\n children: ReactNode\n}\n\nexport function HeadlessDialog({\n open,\n onClose,\n labelledBy,\n describedBy,\n initialFocusRef,\n returnFocusRef,\n children,\n ...props\n}: HeadlessDialogProps) {\n const dialogRef = useRef<HTMLDialogElement>(null)\n const previouslyFocused = useRef<HTMLElement | null>(null)\n\n useEffect(() => {\n const dialog = dialogRef.current\n if (!dialog) return\n if (open) {\n previouslyFocused.current = document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null\n if (!dialog.open) {\n if (typeof dialog.showModal === 'function') dialog.showModal()\n else dialog.setAttribute('open', '')\n }\n const focusTarget = initialFocusRef?.current ?? dialog\n queueMicrotask(() => focusTarget.focus())\n return\n }\n if (dialog.open) {\n if (typeof dialog.close === 'function') dialog.close()\n else dialog.removeAttribute('open')\n }\n const returnTarget = returnFocusRef?.current ?? previouslyFocused.current\n queueMicrotask(() => returnTarget?.focus())\n }, [initialFocusRef, open, returnFocusRef])\n\n return (\n <dialog\n {...props}\n aria-describedby={describedBy}\n aria-labelledby={labelledBy}\n aria-modal=\"true\"\n onCancel={(event) => {\n event.preventDefault()\n onClose()\n }}\n onClose={onClose}\n ref={dialogRef}\n >\n {children}\n </dialog>\n )\n}\n\nexport interface TrackedTelephoneLinkProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'onClick'> {\n phoneE164: string\n placement: PhoneCallPlacement\n onTrack: (input: { event: 'phone_link_click'; placement: PhoneCallPlacement }) => void\n children: ReactNode\n}\n\nexport function TrackedTelephoneLink({\n phoneE164,\n placement,\n onTrack,\n children,\n ...props\n}: TrackedTelephoneLinkProps) {\n if (!/^\\+[1-9]\\d{7,14}$/.test(phoneE164)) {\n throw new Error('TrackedTelephoneLink requires an E.164 phone number.')\n }\n if (!PHONE_CALL_PLACEMENTS.includes(placement)) {\n throw new Error('TrackedTelephoneLink requires a canonical phone-call placement.')\n }\n return (\n <a\n {...props}\n href={`tel:${phoneE164}`}\n onClick={() => {\n try {\n onTrack({ event: 'phone_link_click', placement })\n } catch {\n // Tracking must never block the telephone action.\n }\n }}\n >\n {children}\n </a>\n )\n}\n\nexport interface GalleryLightboxItem {\n id: string\n alt: string\n}\n\nexport interface GalleryLightboxProps<Item extends GalleryLightboxItem> {\n open: boolean\n items: readonly Item[]\n initialIndex?: number\n titleId: string\n title: ReactNode\n onClose: () => void\n renderItem: (item: Item) => ReactNode\n previousLabel: string\n nextLabel: string\n closeLabel: string\n}\n\nexport function GalleryLightbox<Item extends GalleryLightboxItem>({\n open,\n items,\n initialIndex = 0,\n titleId,\n title,\n onClose,\n renderItem,\n previousLabel,\n nextLabel,\n closeLabel,\n}: GalleryLightboxProps<Item>) {\n const [index, setIndex] = useState(initialIndex)\n const closeRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (open) setIndex(Math.min(Math.max(initialIndex, 0), Math.max(items.length - 1, 0)))\n }, [initialIndex, items.length, open])\n\n const move = (offset: number) => {\n if (items.length < 2) return\n setIndex((current) => (current + offset + items.length) % items.length)\n }\n const onKeyDown = (event: KeyboardEvent<HTMLDialogElement>) => {\n if (event.key === 'ArrowLeft') {\n event.preventDefault()\n move(-1)\n } else if (event.key === 'ArrowRight') {\n event.preventDefault()\n move(1)\n }\n }\n const item = items[index]\n\n return (\n <HeadlessDialog\n initialFocusRef={closeRef}\n labelledBy={titleId}\n onClose={onClose}\n onKeyDown={onKeyDown}\n open={open}\n >\n <h2 id={titleId}>{title}</h2>\n <button aria-label={closeLabel} onClick={onClose} ref={closeRef} type=\"button\">\n {closeLabel}\n </button>\n {item ? <figure key={item.id}>{renderItem(item)}<figcaption>{item.alt}</figcaption></figure> : null}\n {items.length > 1 ? (\n <nav aria-labelledby={titleId}>\n <button aria-label={previousLabel} onClick={() => move(-1)} type=\"button\">\n {previousLabel}\n </button>\n <button aria-label={nextLabel} onClick={() => move(1)} type=\"button\">\n {nextLabel}\n </button>\n </nav>\n ) : null}\n </HeadlessDialog>\n )\n}\n\nexport function usePrefersReducedMotion() {\n const [reduced, setReduced] = useState(false)\n useEffect(() => {\n const query = window.matchMedia('(prefers-reduced-motion: reduce)')\n const update = () => setReduced(query.matches)\n update()\n query.addEventListener('change', update)\n return () => query.removeEventListener('change', update)\n }, [])\n return reduced\n}\n\nexport interface HeadlessErrorFocusProps {\n active: boolean\n formId: string\n summaryId: string\n}\n\n/** Focuses the error summary, falling back to the first invalid field. */\nexport function HeadlessErrorFocus({ active, formId, summaryId }: HeadlessErrorFocusProps) {\n useEffect(() => {\n if (!active) return\n let cancelled = false\n queueMicrotask(() => {\n if (cancelled) return\n const form = document.getElementById(formId)\n if (!(form instanceof HTMLFormElement)) return\n const summary = document.getElementById(summaryId)\n const target = summary instanceof HTMLElement && form.contains(summary)\n ? summary\n : form.querySelector<HTMLElement>([\n 'input[aria-invalid=\"true\"]:not([disabled])',\n 'select[aria-invalid=\"true\"]:not([disabled])',\n 'textarea[aria-invalid=\"true\"]:not([disabled])',\n 'button[aria-invalid=\"true\"]:not([disabled])',\n '[aria-invalid=\"true\"][tabindex]:not([aria-disabled=\"true\"])',\n ].join(','))\n target?.focus()\n })\n return () => { cancelled = true }\n }, [active, formId, summaryId])\n return null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ui-client.js","sourceRoot":"","sources":["../src/ui-client.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EACL,SAAS,EACT,MAAM,EACN,QAAQ,GAKT,MAAM,OAAO,CAAA;AAad,MAAM,UAAU,cAAc,CAAC,EAC7B,IAAI,EACJ,OAAO,EACP,UAAU,EACV,WAAW,EACX,eAAe,EACf,cAAc,EACd,QAAQ,EACR,GAAG,KAAK,EACY;IACpB,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IACjD,MAAM,iBAAiB,GAAG,MAAM,CAAqB,IAAI,CAAC,CAAA;IAE1D,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,IAAI,EAAE,CAAC;YACT,iBAAiB,CAAC,OAAO,GAAG,QAAQ,CAAC,aAAa,YAAY,WAAW;gBACvE,CAAC,CAAC,QAAQ,CAAC,aAAa;gBACxB,CAAC,CAAC,IAAI,CAAA;YACR,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACjB,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,UAAU;oBAAE,MAAM,CAAC,SAAS,EAAE,CAAA;;oBACzD,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;YACtC,CAAC;YACD,MAAM,WAAW,GAAG,eAAe,EAAE,OAAO,IAAI,MAAM,CAAA;YACtD,cAAc,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAA;YACzC,OAAM;QACR,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU;gBAAE,MAAM,CAAC,KAAK,EAAE,CAAA;;gBACjD,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;QACrC,CAAC;QACD,MAAM,YAAY,GAAG,cAAc,EAAE,OAAO,IAAI,iBAAiB,CAAC,OAAO,CAAA;QACzE,cAAc,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC,CAAA;IAC7C,CAAC,EAAE,CAAC,eAAe,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAA;IAE3C,OAAO,CACL,oBACM,KAAK,sBACS,WAAW,qBACZ,UAAU,gBAChB,MAAM,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YAClB,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,OAAO,EAAE,CAAA;QACX,CAAC,EACD,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,SAAS,YAEb,QAAQ,GACF,CACV,CAAA;AACH,CAAC;AAoBD,MAAM,UAAU,eAAe,CAAmC,EAChE,IAAI,EACJ,KAAK,EACL,YAAY,GAAG,CAAC,EAChB,OAAO,EACP,KAAK,EACL,OAAO,EACP,UAAU,EACV,aAAa,EACb,SAAS,EACT,UAAU,GACiB;IAC3B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAA;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IAEhD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IACxF,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;IAEtC,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,EAAE;QAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAM;QAC5B,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;IACzE,CAAC,CAAA;IACD,MAAM,SAAS,GAAG,CAAC,KAAuC,EAAE,EAAE;QAC5D,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;YAC9B,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QACV,CAAC;aAAM,IAAI,KAAK,CAAC,GAAG,KAAK,YAAY,EAAE,CAAC;YACtC,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,IAAI,CAAC,CAAC,CAAC,CAAA;QACT,CAAC;IACH,CAAC,CAAA;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;IAEzB,OAAO,CACL,MAAC,cAAc,IACb,eAAe,EAAE,QAAQ,EACzB,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,IAAI,aAEV,aAAI,EAAE,EAAE,OAAO,YAAG,KAAK,GAAM,EAC7B,+BAAoB,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAC,QAAQ,YAC3E,UAAU,GACJ,EACR,IAAI,CAAC,CAAC,CAAC,6BAAuB,UAAU,CAAC,IAAI,CAAC,EAAC,+BAAa,IAAI,CAAC,GAAG,GAAc,KAA9D,IAAI,CAAC,EAAE,CAAgE,CAAC,CAAC,CAAC,IAAI,EAClG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAClB,kCAAsB,OAAO,aAC3B,+BAAoB,aAAa,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAC,QAAQ,YACtE,aAAa,GACP,EACT,+BAAoB,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAC,QAAQ,YACjE,SAAS,GACH,IACL,CACP,CAAC,CAAC,CAAC,IAAI,IACO,CAClB,CAAA;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAA;QACnE,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC9C,MAAM,EAAE,CAAA;QACR,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACxC,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC1D,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,OAAO,OAAO,CAAA;AAChB,CAAC;AAQD,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAA2B;IACvF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,cAAc,CAAC,GAAG,EAAE;YAClB,IAAI,SAAS;gBAAE,OAAM;YACrB,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;YAC5C,IAAI,CAAC,CAAC,IAAI,YAAY,eAAe,CAAC;gBAAE,OAAM;YAC9C,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;YAClD,MAAM,MAAM,GAAG,OAAO,YAAY,WAAW,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;gBACrE,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,IAAI,CAAC,aAAa,CAAc;oBAC9B,4CAA4C;oBAC5C,6CAA6C;oBAC7C,+CAA+C;oBAC/C,6CAA6C;oBAC7C,6DAA6D;iBAC9D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;YAChB,MAAM,EAAE,KAAK,EAAE,CAAA;QACjB,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,EAAE,GAAG,SAAS,GAAG,IAAI,CAAA,CAAC,CAAC,CAAA;IACnC,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;IAC/B,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["'use client'\n\nimport {\n useEffect,\n useRef,\n useState,\n type DialogHTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n type RefObject,\n} from 'react'\n\nexport interface HeadlessDialogProps\n extends Omit<DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClose'> {\n open: boolean\n onClose: () => void\n labelledBy: string\n describedBy?: string\n initialFocusRef?: RefObject<HTMLElement | null>\n returnFocusRef?: RefObject<HTMLElement | null>\n children: ReactNode\n}\n\nexport function HeadlessDialog({\n open,\n onClose,\n labelledBy,\n describedBy,\n initialFocusRef,\n returnFocusRef,\n children,\n ...props\n}: HeadlessDialogProps) {\n const dialogRef = useRef<HTMLDialogElement>(null)\n const previouslyFocused = useRef<HTMLElement | null>(null)\n\n useEffect(() => {\n const dialog = dialogRef.current\n if (!dialog) return\n if (open) {\n previouslyFocused.current = document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null\n if (!dialog.open) {\n if (typeof dialog.showModal === 'function') dialog.showModal()\n else dialog.setAttribute('open', '')\n }\n const focusTarget = initialFocusRef?.current ?? dialog\n queueMicrotask(() => focusTarget.focus())\n return\n }\n if (dialog.open) {\n if (typeof dialog.close === 'function') dialog.close()\n else dialog.removeAttribute('open')\n }\n const returnTarget = returnFocusRef?.current ?? previouslyFocused.current\n queueMicrotask(() => returnTarget?.focus())\n }, [initialFocusRef, open, returnFocusRef])\n\n return (\n <dialog\n {...props}\n aria-describedby={describedBy}\n aria-labelledby={labelledBy}\n aria-modal=\"true\"\n onCancel={(event) => {\n event.preventDefault()\n onClose()\n }}\n onClose={onClose}\n ref={dialogRef}\n >\n {children}\n </dialog>\n )\n}\n\nexport interface GalleryLightboxItem {\n id: string\n alt: string\n}\n\nexport interface GalleryLightboxProps<Item extends GalleryLightboxItem> {\n open: boolean\n items: readonly Item[]\n initialIndex?: number\n titleId: string\n title: ReactNode\n onClose: () => void\n renderItem: (item: Item) => ReactNode\n previousLabel: string\n nextLabel: string\n closeLabel: string\n}\n\nexport function GalleryLightbox<Item extends GalleryLightboxItem>({\n open,\n items,\n initialIndex = 0,\n titleId,\n title,\n onClose,\n renderItem,\n previousLabel,\n nextLabel,\n closeLabel,\n}: GalleryLightboxProps<Item>) {\n const [index, setIndex] = useState(initialIndex)\n const closeRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (open) setIndex(Math.min(Math.max(initialIndex, 0), Math.max(items.length - 1, 0)))\n }, [initialIndex, items.length, open])\n\n const move = (offset: number) => {\n if (items.length < 2) return\n setIndex((current) => (current + offset + items.length) % items.length)\n }\n const onKeyDown = (event: KeyboardEvent<HTMLDialogElement>) => {\n if (event.key === 'ArrowLeft') {\n event.preventDefault()\n move(-1)\n } else if (event.key === 'ArrowRight') {\n event.preventDefault()\n move(1)\n }\n }\n const item = items[index]\n\n return (\n <HeadlessDialog\n initialFocusRef={closeRef}\n labelledBy={titleId}\n onClose={onClose}\n onKeyDown={onKeyDown}\n open={open}\n >\n <h2 id={titleId}>{title}</h2>\n <button aria-label={closeLabel} onClick={onClose} ref={closeRef} type=\"button\">\n {closeLabel}\n </button>\n {item ? <figure key={item.id}>{renderItem(item)}<figcaption>{item.alt}</figcaption></figure> : null}\n {items.length > 1 ? (\n <nav aria-labelledby={titleId}>\n <button aria-label={previousLabel} onClick={() => move(-1)} type=\"button\">\n {previousLabel}\n </button>\n <button aria-label={nextLabel} onClick={() => move(1)} type=\"button\">\n {nextLabel}\n </button>\n </nav>\n ) : null}\n </HeadlessDialog>\n )\n}\n\nexport function usePrefersReducedMotion() {\n const [reduced, setReduced] = useState(false)\n useEffect(() => {\n const query = window.matchMedia('(prefers-reduced-motion: reduce)')\n const update = () => setReduced(query.matches)\n update()\n query.addEventListener('change', update)\n return () => query.removeEventListener('change', update)\n }, [])\n return reduced\n}\n\nexport interface HeadlessErrorFocusProps {\n active: boolean\n formId: string\n summaryId: string\n}\n\n/** Focuses the error summary, falling back to the first invalid field. */\nexport function HeadlessErrorFocus({ active, formId, summaryId }: HeadlessErrorFocusProps) {\n useEffect(() => {\n if (!active) return\n let cancelled = false\n queueMicrotask(() => {\n if (cancelled) return\n const form = document.getElementById(formId)\n if (!(form instanceof HTMLFormElement)) return\n const summary = document.getElementById(summaryId)\n const target = summary instanceof HTMLElement && form.contains(summary)\n ? summary\n : form.querySelector<HTMLElement>([\n 'input[aria-invalid=\"true\"]:not([disabled])',\n 'select[aria-invalid=\"true\"]:not([disabled])',\n 'textarea[aria-invalid=\"true\"]:not([disabled])',\n 'button[aria-invalid=\"true\"]:not([disabled])',\n '[aria-invalid=\"true\"][tabindex]:not([aria-disabled=\"true\"])',\n ].join(','))\n target?.focus()\n })\n return () => { cancelled = true }\n }, [active, formId, summaryId])\n return null\n}\n"]}
|
package/dist/ui.d.ts
CHANGED
|
@@ -74,8 +74,28 @@ export type SafeRichTextNode = {
|
|
|
74
74
|
} | {
|
|
75
75
|
type: 'line-break';
|
|
76
76
|
};
|
|
77
|
-
export
|
|
77
|
+
export type SafeRichTextElement = 'div' | 'p' | 'span';
|
|
78
|
+
export interface SafeRichTextProps extends HTMLAttributes<HTMLElement> {
|
|
78
79
|
nodes: readonly SafeRichTextNode[];
|
|
80
|
+
/**
|
|
81
|
+
* The wrapper element. `div` (default) holds block content: paragraphs and
|
|
82
|
+
* lists. `p` and `span` hold inline content only (text, emphasis, links and
|
|
83
|
+
* line breaks), so the output has no block element and can sit where only
|
|
84
|
+
* phrasing content is allowed, such as inside a button; a block node under
|
|
85
|
+
* them throws.
|
|
86
|
+
*/
|
|
87
|
+
as?: SafeRichTextElement;
|
|
88
|
+
/** Class for every link, rendered after `href`. */
|
|
89
|
+
linkClassName?: string;
|
|
79
90
|
}
|
|
80
|
-
export declare function SafeRichText({ nodes, ...props }: SafeRichTextProps): import("react/jsx-runtime").JSX.Element;
|
|
91
|
+
export declare function SafeRichText({ nodes, as: Element, linkClassName, ...props }: SafeRichTextProps): import("react/jsx-runtime").JSX.Element;
|
|
92
|
+
/**
|
|
93
|
+
* Inline rich-text markup as nodes, for content written as short HTML
|
|
94
|
+
* strings: `<strong>` and `<b>` become strong text, `<a href>` becomes a link
|
|
95
|
+
* when its href passes sanitizeRichTextHref (otherwise its text stays as plain
|
|
96
|
+
* text), every other tag is dropped and text is kept exactly as written
|
|
97
|
+
* (React escapes it). Nothing is ever rendered as HTML. Strong text holds text
|
|
98
|
+
* only; markup nested inside it throws.
|
|
99
|
+
*/
|
|
100
|
+
export declare function parseInlineRichText(markup: string): SafeRichTextNode[];
|
|
81
101
|
//# sourceMappingURL=ui.d.ts.map
|