@bison-lab/payload-core 3.11.0 → 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.
@@ -204,6 +204,52 @@ function deleteLibraryColor(doc, key, replacement) {
204
204
  return next;
205
205
  }
206
206
  //#endregion
207
+ //#region src/theme/color-tokens.ts
208
+ /**
209
+ * A string `rewriteColorToken` would change: `coral`, `coral-400`.
210
+ * `#FF5A2D` and `coral-ish` are not tokens.
211
+ */
212
+ function isColorToken(value, key) {
213
+ return rewriteColorToken(value, key, `\0${key}`) !== value;
214
+ }
215
+ function findColorTokens(value, key) {
216
+ const hits = [];
217
+ walk(value, "", (path, token) => {
218
+ if (isColorToken(token, key)) hits.push({
219
+ path,
220
+ token
221
+ });
222
+ });
223
+ return hits;
224
+ }
225
+ function rewriteColorTokens(value, fromKey, toKey) {
226
+ return rewriteValue(value, fromKey, toKey);
227
+ }
228
+ function rewriteValue(value, fromKey, toKey) {
229
+ if (typeof value === "string") return rewriteColorToken(value, fromKey, toKey);
230
+ if (Array.isArray(value)) return value.map((item) => rewriteValue(item, fromKey, toKey));
231
+ if (value && typeof value === "object") {
232
+ const next = {};
233
+ for (const [name, child] of Object.entries(value)) next[name] = rewriteValue(child, fromKey, toKey);
234
+ return next;
235
+ }
236
+ return value;
237
+ }
238
+ function walk(value, path, visit) {
239
+ if (typeof value === "string") {
240
+ visit(path, value);
241
+ return;
242
+ }
243
+ if (Array.isArray(value)) {
244
+ value.forEach((item, index) => walk(item, join(path, String(index)), visit));
245
+ return;
246
+ }
247
+ if (value && typeof value === "object") for (const [name, child] of Object.entries(value)) walk(child, join(path, name), visit);
248
+ }
249
+ function join(path, segment) {
250
+ return path ? `${path}.${segment}` : segment;
251
+ }
252
+ //#endregion
207
253
  //#region src/theme/looks.ts
208
254
  /**
209
255
  * System colors Color Settings offers as page-editor fills. Success and
@@ -378,6 +424,6 @@ function resolveThemeIdentity(doc, fallback) {
378
424
  };
379
425
  }
380
426
  //#endregion
381
- export { THEME_IDENTITY_FALLBACK as A, THEME_APPEARANCE_FIELD as C, THEME_DOCUMENT_CONTROLS as D, THEME_CONTRAST_REPORT as E, THEME_SECTION_HEADING as F, headingSelectValue as I, THEME_PAIRING_FIELD as M, THEME_PUBLISH_FIELD as N, THEME_FONT_FIELD as O, THEME_SAVE_BUTTON as P, LOOK_FIELD as S, THEME_COLOR_SCALE_FIELD as T, docFromConfig as _, pageEditorLooks as a, validateSourceIncluded as b, deleteLibraryColor as c, SYSTEM_COLOR_KEYS as d, THEME_APPEARANCE_SLUG as f, THEME_TYPOGRAPHY_SLUG as g, THEME_SLUG as h, PAGE_EDITOR_SYSTEM_KEYS as i, THEME_LIBRARY_FIELD as j, THEME_GREY_SCALE_FIELD as k, rewriteColorToken as l, THEME_IDENTITY_SLUG as m, colorTokenField as n, pageEditorTokens as o, THEME_COLORS_SLUG as p, lookField as r, themeLibraryFromDoc as s, resolveThemeIdentity as t, themeColorKeys as u, stateFromColorDoc as v, THEME_COLOR_FIELD as w, validateThemeHex as x, themeConfigFromDoc as y };
427
+ export { THEME_FONT_FIELD as A, validateThemeHex as C, THEME_COLOR_SCALE_FIELD as D, THEME_COLOR_FIELD as E, THEME_PUBLISH_FIELD as F, THEME_SAVE_BUTTON as I, THEME_SECTION_HEADING as L, THEME_IDENTITY_FALLBACK as M, THEME_LIBRARY_FIELD as N, THEME_CONTRAST_REPORT as O, THEME_PAIRING_FIELD as P, headingSelectValue as R, validateSourceIncluded as S, THEME_APPEARANCE_FIELD as T, THEME_SLUG as _, pageEditorLooks as a, stateFromColorDoc as b, findColorTokens as c, rewriteColorToken as d, themeColorKeys as f, THEME_IDENTITY_SLUG as g, THEME_COLORS_SLUG as h, PAGE_EDITOR_SYSTEM_KEYS as i, THEME_GREY_SCALE_FIELD as j, THEME_DOCUMENT_CONTROLS as k, rewriteColorTokens as l, THEME_APPEARANCE_SLUG as m, colorTokenField as n, pageEditorTokens as o, SYSTEM_COLOR_KEYS as p, lookField as r, themeLibraryFromDoc as s, resolveThemeIdentity as t, deleteLibraryColor as u, THEME_TYPOGRAPHY_SLUG as v, LOOK_FIELD as w, themeConfigFromDoc as x, docFromConfig as y };
382
428
 
383
- //# sourceMappingURL=identity-DZOb3_Gk.mjs.map
429
+ //# sourceMappingURL=identity-LAzSeorC.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-LAzSeorC.mjs","names":[],"sources":["../src/theme/fields.ts","../src/theme/map.ts","../src/theme/types.ts","../src/theme/library.ts","../src/theme/color-tokens.ts","../src/theme/looks.ts","../src/theme/look-field.ts","../src/theme/identity.ts"],"sourcesContent":["import type { ThemeConfig } from \"@bison-lab/tokens\";\n\nimport type { EditableTheme } from \"./types\";\n\nexport const THEME_COLOR_FIELD = \"@bison-lab/payload-core/admin#ColorField\";\nexport const THEME_COLOR_SCALE_FIELD = \"@bison-lab/payload-core/admin#ColorScaleField\";\nexport const THEME_LIBRARY_FIELD = \"@bison-lab/payload-core/admin#LibraryField\";\nexport const THEME_FONT_FIELD = \"@bison-lab/payload-core/admin#FontField\";\nexport const THEME_PAIRING_FIELD = \"@bison-lab/payload-core/admin#PairingField\";\nexport const THEME_APPEARANCE_FIELD = \"@bison-lab/payload-core/admin#AppearanceField\";\nexport const THEME_GREY_SCALE_FIELD = \"@bison-lab/payload-core/admin#GreyScaleField\";\nexport const THEME_CONTRAST_REPORT = \"@bison-lab/payload-core/admin#ContrastReport\";\nexport const THEME_SECTION_HEADING = \"@bison-lab/payload-core/admin#SectionHeading\";\nexport const THEME_PUBLISH_FIELD = \"@bison-lab/payload-core/admin#PublishChild\";\nexport const THEME_SAVE_BUTTON = \"@bison-lab/payload-core/admin#HiddenSaveButton\";\nexport const THEME_DOCUMENT_CONTROLS = \"@bison-lab/payload-core/admin#ThemeDocumentControls\";\nexport const THEME_IDENTITY_FALLBACK = \"@bison-lab/payload-core/admin#IdentityFallback\";\nexport const LOOK_FIELD = \"@bison-lab/payload-core/admin#LookField\";\n\n/**\n * Document path for each editable `ThemeConfig` key. Locked with `satisfies`\n * so a new config field without a Theme field (or the reverse) fails at\n * compile time. `darkSelector` and `fontWeights` are seed-only.\n */\nexport const THEME_FIELD_PATHS = {\n brandPrimary: \"colors.brand.primary.hex\",\n brandSecondary: \"colors.brand.secondary.hex\",\n brandAccent: \"colors.brand.accent.hex\",\n brandHighlight: \"colors.brand.highlight.hex\",\n brandSuccess: \"colors.brand.success.hex\",\n brandDestructive: \"colors.brand.destructive.hex\",\n greyScale: \"colors.greyScale\",\n defaultTheme: \"appearance.defaultTheme\",\n radius: \"appearance.radius\",\n shadow: \"appearance.shadow\",\n motion: \"appearance.motion\",\n density: \"appearance.density\",\n fontBody: \"typography.body\",\n fontHeading: \"typography.heading\",\n} as const satisfies Record<keyof EditableTheme, string>;\n\nexport type ThemeFieldPath = (typeof THEME_FIELD_PATHS)[keyof typeof THEME_FIELD_PATHS];\n\n/** Empty select value for heading: same family as body. */\nexport const SAME_AS_BODY = \"\";\n\nexport function headingSelectValue(heading: ThemeConfig[\"fontHeading\"]): string {\n return heading ?? SAME_AS_BODY;\n}\n","import {\n createColorScale,\n DESTRUCTIVE_SCALE_HEX,\n generateColorScale,\n isThemeHex,\n SHADE_STEPS,\n type ColorScale,\n type ColorScaleInclude,\n type ColorScaleState,\n type ShadeStep,\n type ThemeConfig,\n} from \"@bison-lab/tokens\";\n\nimport { SAME_AS_BODY } from \"./fields\";\nimport type { ThemeColorDoc, ThemeDoc } from \"./types\";\n\nfunction pick<T>(value: T | null | undefined, fallback: T): T {\n return value == null ? fallback : value;\n}\n\nfunction headingFromDoc(\n heading: string | null | undefined,\n seed: ThemeConfig[\"fontHeading\"],\n): string | null {\n if (heading === undefined) return seed;\n if (heading === null || heading === SAME_AS_BODY) return null;\n return heading;\n}\n\nfunction hexFromColor(value: ThemeColorDoc | string | null | undefined, fallback: string): string {\n if (typeof value === \"string\") return value || fallback;\n return pick(value?.hex, fallback);\n}\n\nexport function colorDocFromHex(hex: string, sourceStep: ShadeStep = 500): ThemeColorDoc {\n return colorDocFromState(createColorScale(hex, sourceStep));\n}\n\nexport function colorDocFromState(state: ColorScaleState): ThemeColorDoc {\n return {\n hex: state.hex,\n sourceStep: state.sourceStep,\n scale: state.scale,\n stale: state.stale,\n include: Array.isArray(state.include) ? \"custom\" : state.include,\n includedSteps: Array.isArray(state.include) ? [...state.include] : undefined,\n };\n}\n\nexport function stateFromColorDoc(\n doc: ThemeColorDoc | null | undefined,\n fallbackHex = \"#000000\",\n): ColorScaleState {\n const hex = isThemeHex(doc?.hex) ? doc.hex : fallbackHex;\n const sourceStep = (Number(doc?.sourceStep) || 500) as ShadeStep;\n const include = includeFromDoc(doc, sourceStep);\n const scale =\n doc?.scale && typeof doc.scale === \"object\" ? (doc.scale as ColorScale) : generateColorScale(hex, sourceStep);\n return { hex, sourceStep, scale, stale: Boolean(doc?.stale), include };\n}\n\nfunction includeFromDoc(doc: ThemeColorDoc | null | undefined, sourceStep: ShadeStep): ColorScaleInclude {\n if (doc?.include === \"source\") return \"source\";\n if (doc?.include === \"custom\" && Array.isArray(doc.includedSteps)) {\n const steps = doc.includedSteps.map(Number).filter((step): step is ShadeStep => SHADE_STEPS.includes(step as ShadeStep));\n if (!steps.includes(sourceStep)) steps.push(sourceStep);\n return steps;\n }\n return \"all\";\n}\n\n/**\n * Nested Theme document → flat `ThemeConfig`. Null or missing editor\n * fields take the seed; `darkSelector` and `fontWeights` always come from\n * the seed. Reads the BIS-87 groups and the pre-BIS-87 flat fields.\n */\nexport function themeConfigFromDoc(doc: ThemeDoc | null | undefined, seed: ThemeConfig): ThemeConfig {\n const brand = doc?.colors?.brand ?? doc?.brand;\n const typography = doc?.typography ?? doc?.fonts;\n const appearance = doc?.appearance;\n return {\n brandPrimary: hexFromColor(brand?.primary, seed.brandPrimary),\n brandSecondary: hexFromColor(brand?.secondary, seed.brandSecondary),\n brandAccent: hexFromColor(brand?.accent, seed.brandAccent),\n brandHighlight: hexFromColor(brand?.highlight, seed.brandHighlight),\n brandSuccess: hexFromColor(brand?.success, seed.brandSuccess),\n brandDestructive:\n hexFromColor(brand?.destructive, seed.brandDestructive ?? DESTRUCTIVE_SCALE_HEX) ||\n seed.brandDestructive ||\n DESTRUCTIVE_SCALE_HEX,\n greyScale: pick(doc?.colors?.greyScale ?? doc?.greyScale, seed.greyScale),\n radius: pick(appearance?.radius ?? doc?.radius, seed.radius),\n shadow: pick(appearance?.shadow ?? doc?.shadow, seed.shadow),\n motion: pick(appearance?.motion ?? doc?.motion, seed.motion),\n density: pick(appearance?.density ?? doc?.density, seed.density),\n defaultTheme: pick(appearance?.defaultTheme ?? doc?.defaultTheme, seed.defaultTheme),\n fontBody: pick(typography?.body, seed.fontBody),\n fontHeading: headingFromDoc(typography?.heading, seed.fontHeading),\n darkSelector: seed.darkSelector,\n fontWeights: seed.fontWeights,\n };\n}\n\n/** The document `seedTheme` writes so a fresh Global matches the seed config. */\nexport function docFromConfig(config: ThemeConfig, destructive?: string): ThemeDoc {\n const destructiveHex = destructive ?? config.brandDestructive ?? DESTRUCTIVE_SCALE_HEX;\n return {\n colors: {\n brand: {\n primary: colorDocFromHex(config.brandPrimary),\n secondary: colorDocFromHex(config.brandSecondary),\n accent: colorDocFromHex(config.brandAccent),\n highlight: colorDocFromHex(config.brandHighlight),\n success: colorDocFromHex(config.brandSuccess),\n destructive: colorDocFromHex(destructiveHex),\n },\n library: [],\n greyScale: config.greyScale,\n },\n typography: {\n body: config.fontBody,\n heading: config.fontHeading,\n },\n appearance: {\n defaultTheme: config.defaultTheme,\n radius: config.radius,\n shadow: config.shadow,\n motion: config.motion,\n density: config.density,\n },\n };\n}\n\nexport function validateThemeHex(value: unknown): true | string {\n if (!isThemeHex(value)) return \"Enter a six-digit hex colour like #1e3a5f\";\n return true;\n}\n\nexport function validateSourceIncluded(\n value: unknown,\n { siblingData }: { siblingData?: { sourceStep?: unknown; include?: unknown } },\n): true | string {\n const include = siblingData?.include;\n if (include === \"all\" || include === \"source\") return true;\n const source = Number(siblingData?.sourceStep);\n const steps = Array.isArray(value) ? value.map(Number) : [];\n if (!SHADE_STEPS.includes(source as ShadeStep)) return true;\n if (!steps.includes(source)) return \"The source step cannot be excluded\";\n return true;\n}\n","import type { Access } from \"payload\";\nimport type { FontEntry } from \"@bison-lab/fonts\";\nimport type { ColorScale, ColorScaleInclude, ShadeStep, ThemeConfig } from \"@bison-lab/tokens\";\n\nimport type { ResolvedThemeIdentity, ThemeIdentityFallback } from \"./identity\";\n\n/**\n * The Theme document a site's generated types will describe. Optional and\n * nullable, no index signature: a generated Global is assignable to this,\n * never the reverse.\n *\n * Legacy flat `brand.*` hexes, `fonts`, and preset fields at the root stay\n * readable so `getPublishedTheme` still maps a pre-BIS-87 row.\n */\nexport interface ThemeColorDoc {\n hex?: string | null;\n sourceStep?: ShadeStep | string | null;\n scale?: ColorScale | null;\n stale?: boolean | null;\n include?: ColorScaleInclude | \"custom\" | null;\n includedSteps?: ShadeStep[] | string[] | null;\n}\n\nexport interface ThemeLibraryColorDoc extends ThemeColorDoc {\n key?: string | null;\n label?: string | null;\n}\n\nexport interface ThemeDoc {\n colors?: {\n brand?: {\n primary?: ThemeColorDoc | string | null;\n secondary?: ThemeColorDoc | string | null;\n accent?: ThemeColorDoc | string | null;\n highlight?: ThemeColorDoc | string | null;\n success?: ThemeColorDoc | string | null;\n destructive?: ThemeColorDoc | string | null;\n } | null;\n library?: ThemeLibraryColorDoc[] | null;\n greyScale?: ThemeConfig[\"greyScale\"] | null;\n } | null;\n typography?: {\n body?: string | null;\n heading?: string | null;\n } | null;\n appearance?: {\n defaultTheme?: ThemeConfig[\"defaultTheme\"] | null;\n radius?: ThemeConfig[\"radius\"] | null;\n shadow?: ThemeConfig[\"shadow\"] | null;\n motion?: ThemeConfig[\"motion\"] | null;\n density?: ThemeConfig[\"density\"] | null;\n } | null;\n /** @deprecated Pre-BIS-87 flat brand hexes. */\n brand?: {\n primary?: ThemeColorDoc | string | null;\n secondary?: ThemeColorDoc | string | null;\n accent?: ThemeColorDoc | string | null;\n highlight?: ThemeColorDoc | string | null;\n success?: ThemeColorDoc | string | null;\n destructive?: ThemeColorDoc | string | null;\n } | null;\n /** @deprecated Pre-BIS-87; now `colors.greyScale`. */\n greyScale?: ThemeConfig[\"greyScale\"] | null;\n /** @deprecated Pre-BIS-87; now `appearance.*`. */\n radius?: ThemeConfig[\"radius\"] | null;\n shadow?: ThemeConfig[\"shadow\"] | null;\n motion?: ThemeConfig[\"motion\"] | null;\n density?: ThemeConfig[\"density\"] | null;\n defaultTheme?: ThemeConfig[\"defaultTheme\"] | null;\n /** @deprecated Pre-BIS-87; now `typography`. */\n fonts?: {\n body?: string | null;\n heading?: string | null;\n } | null;\n logo?: number | string | ThemeUploadDoc | null;\n favicon?: number | string | ThemeUploadDoc | null;\n logoMark?: number | string | ThemeUploadDoc | null;\n}\n\nexport interface ThemeUploadDoc {\n id?: number | string | null;\n url?: string | null;\n alt?: string | null;\n}\n\n/**\n * What an editor can change. `darkSelector` and `fontWeights` stay on the\n * seed — they mean nothing in the admin — and `themeConfigFromDoc` puts\n * them back.\n */\nexport type EditableTheme = Omit<ThemeConfig, \"darkSelector\" | \"fontWeights\">;\n\nexport interface CreateThemeOptions {\n /**\n * Required, no default. Pass `canManageBrand` as `update` (and typically\n * `isAuthenticated` as `read`). Theme does not read the Roles Global.\n */\n access: { read: Access; update: Access };\n /** The site's `bison.config.json`. Every field's `defaultValue`, and the fallback `getPublishedTheme` returns. */\n seed: ThemeConfig;\n /**\n * Destructive seed hex. Falls back to `seed.brandDestructive`, then\n * the library's `DESTRUCTIVE_SCALE_HEX` (`#ef4444`).\n */\n destructive?: string;\n /** Default: the whole catalogue. */\n fonts?: readonly FontEntry[];\n /** Where the site's `serveFont` route answers, for the picker's specimens. Default `\"/fonts\"`. */\n fontsBaseUrl?: string;\n /** Fill + automatic label target. Default `7`. Warnings never block save. */\n contrastTarget?: number;\n /**\n * @deprecated Theme has no preview pane (SPI-56 canceled). Kept so a\n * site that still passes it does not fail to boot.\n */\n previewPath?: string;\n /**\n * Adds the Identity page (logo, favicon, mobile-menu mark) when given.\n * Pass `BRAND_ASSETS_SLUG` (or the slug you gave `createBrandAssets`).\n * Save on that page writes only those uploads onto the stored Theme row.\n */\n logo?: { collection: string };\n /**\n * Fallback art when logo, favicon, or mobile-menu mark is empty. The\n * site passes today's files; `--color-primary-mark` is not a Theme field.\n */\n identity?: { fallback?: ThemeIdentityFallback };\n /** Fires from `afterChange` on every save — Theme has no draft mode. */\n onPublish?: (theme: ThemeConfig, doc: ThemeDoc) => void | Promise<void>;\n /**\n * Slugs that store `lookField` / `colorTokenField` values. Colors\n * Delete scans these (drafts included) before dropping a custom row.\n * Omit or pass empty: every additional color is unused and deletes\n * immediately. A new look surface is added here, not copied into a\n * site helper — site adopt names the slugs (SPI-93).\n */\n colorUsages?: {\n collections?: string[];\n globals?: string[];\n };\n}\n\nexport interface ThemeHeadOptions {\n fontsBaseUrl?: string;\n attribute?: \"class\" | \"data-theme\";\n identity?: ResolvedThemeIdentity;\n /**\n * Custom Colors rows. `themeHead` emits their 50–950 variables and\n * `[data-look]` bindings so a picker key paints without a package bump.\n */\n library?: ThemeLibraryColorDoc[] | null;\n}\n\nexport interface ThemeHead {\n css: string;\n preloads: { href: string; type: \"font/woff2\" }[];\n identity?: ResolvedThemeIdentity;\n}\n\nexport const THEME_SLUG = \"theme\";\nexport const THEME_COLORS_SLUG = \"theme-colors\";\nexport const THEME_TYPOGRAPHY_SLUG = \"theme-typography\";\nexport const THEME_APPEARANCE_SLUG = \"theme-appearance\";\nexport const THEME_IDENTITY_SLUG = \"theme-identity\";\n\nexport const SYSTEM_COLOR_KEYS = [\n \"primary\",\n \"secondary\",\n \"accent\",\n \"highlight\",\n \"success\",\n \"destructive\",\n] as const;\n\nexport type SystemColorKey = (typeof SYSTEM_COLOR_KEYS)[number];\n","import { SHADE_STEPS, type ShadeStep } from \"@bison-lab/tokens\";\n\nimport { SYSTEM_COLOR_KEYS, type ThemeDoc, type ThemeLibraryColorDoc } from \"./types\";\n\n/**\n * Payload's array form state stores the row count (`0`, `2`, …), not the\n * rows. A custom Field that treats that number as the list will throw\n * `rows.filter is not a function` on an empty Theme library.\n */\nexport function asLibraryRows(value: unknown): ThemeLibraryColorDoc[] {\n return Array.isArray(value) ? value : [];\n}\n\nconst STEP_SET = new Set<number>(SHADE_STEPS);\n\n/**\n * Page editors pick `coral-400`, never a raw hex. Rewrite every use of\n * `fromKey` onto `toKey` at the same step: `coral-400` → `highlight-400`.\n * A bare key (`coral`) becomes `toKey`. Anything else is left alone.\n */\nexport function rewriteColorToken(token: string, fromKey: string, toKey: string): string {\n if (token === fromKey) return toKey;\n const dash = token.lastIndexOf(\"-\");\n if (dash <= 0) return token;\n const key = token.slice(0, dash);\n const step = Number(token.slice(dash + 1));\n if (key !== fromKey || !STEP_SET.has(step)) return token;\n return `${toKey}-${step as ShadeStep}`;\n}\n\nexport function themeColorKeys(doc: ThemeDoc | null | undefined): string[] {\n const custom = asLibraryRows(doc?.colors?.library)\n .map((row) => row.key?.trim())\n .filter((key): key is string => Boolean(key));\n return [...SYSTEM_COLOR_KEYS, ...custom];\n}\n\n/**\n * Remove a custom color. Unused: omit `replacement` and the row is gone.\n * In use: pass another system or custom key; the caller rewrites page\n * tokens with `rewriteColorToken`. After this, no library row still\n * has `key`.\n */\nexport function deleteLibraryColor(doc: ThemeDoc, key: string, replacement?: string): ThemeDoc {\n const library = [...asLibraryRows(doc.colors?.library)];\n const index = library.findIndex((row) => row.key === key);\n if (index === -1) {\n throw new Error(`No custom color named ${JSON.stringify(key)}`);\n }\n library.splice(index, 1);\n const next: ThemeDoc = {\n ...doc,\n colors: { ...doc.colors, library },\n };\n if (replacement !== undefined) {\n if (replacement === key) {\n throw new Error(\"Replacement must be a different color\");\n }\n if (!themeColorKeys(next).includes(replacement)) {\n throw new Error(`Replacement ${JSON.stringify(replacement)} is not a system or remaining custom color`);\n }\n }\n return next;\n}\n","import { rewriteColorToken } from \"./library\";\n\nexport interface ColorTokenHit {\n path: string;\n token: string;\n}\n\n/**\n * A string `rewriteColorToken` would change: `coral`, `coral-400`.\n * `#FF5A2D` and `coral-ish` are not tokens.\n */\nexport function isColorToken(value: string, key: string): boolean {\n return rewriteColorToken(value, key, `\\0${key}`) !== value;\n}\n\nexport function findColorTokens(value: unknown, key: string): ColorTokenHit[] {\n const hits: ColorTokenHit[] = [];\n walk(value, \"\", (path, token) => {\n if (isColorToken(token, key)) hits.push({ path, token });\n });\n return hits;\n}\n\nexport function rewriteColorTokens<T>(value: T, fromKey: string, toKey: string): T {\n return rewriteValue(value, fromKey, toKey) as T;\n}\n\nfunction rewriteValue(value: unknown, fromKey: string, toKey: string): unknown {\n if (typeof value === \"string\") return rewriteColorToken(value, fromKey, toKey);\n if (Array.isArray(value)) return value.map((item) => rewriteValue(item, fromKey, toKey));\n if (value && typeof value === \"object\") {\n const next: Record<string, unknown> = {};\n for (const [name, child] of Object.entries(value)) {\n next[name] = rewriteValue(child, fromKey, toKey);\n }\n return next;\n }\n return value;\n}\n\nfunction walk(value: unknown, path: string, visit: (path: string, token: string) => void): void {\n if (typeof value === \"string\") {\n visit(path, value);\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((item, index) => walk(item, join(path, String(index)), visit));\n return;\n }\n if (value && typeof value === \"object\") {\n for (const [name, child] of Object.entries(value)) {\n walk(child, join(path, name), visit);\n }\n }\n}\n\nfunction join(path: string, segment: string): string {\n return path ? `${path}.${segment}` : segment;\n}\n","import { includedShadeSteps, type ShadeStep } from \"@bison-lab/tokens\";\n\nimport { stateFromColorDoc } from \"./map\";\nimport { THEME_SLUG, type ThemeColorDoc, type ThemeDoc, type ThemeLibraryColorDoc } from \"./types\";\n\n/**\n * System colors Color Settings offers as page-editor fills. Success and\n * Destructive stay off this list — they are theme chrome only.\n */\nexport const PAGE_EDITOR_SYSTEM_KEYS = [\"primary\", \"secondary\", \"accent\", \"highlight\"] as const;\n\nexport type PageEditorSystemKey = (typeof PAGE_EDITOR_SYSTEM_KEYS)[number];\n\nexport interface LookOption {\n label: string;\n value: string;\n}\n\nconst SYSTEM_LABELS: Record<PageEditorSystemKey, string> = {\n primary: \"Primary\",\n secondary: \"Secondary\",\n accent: \"Accent\",\n highlight: \"Highlight\",\n};\n\nfunction brandColor(doc: ThemeDoc | null | undefined, key: PageEditorSystemKey): ThemeColorDoc | null {\n const brand = doc?.colors?.brand ?? doc?.brand;\n const value = brand?.[key];\n if (typeof value === \"string\" || value == null) return value == null ? null : { hex: value };\n return value;\n}\n\nexport function themeLibraryFromDoc(doc: ThemeDoc | null | undefined): ThemeLibraryColorDoc[] {\n return Array.isArray(doc?.colors?.library) ? doc.colors.library : [];\n}\n\nfunction libraryRows(doc: ThemeDoc | null | undefined): ThemeLibraryColorDoc[] {\n return themeLibraryFromDoc(doc);\n}\n\nfunction titleCase(key: string): string {\n return key.charAt(0).toUpperCase() + key.slice(1);\n}\n\nfunction stepsFor(color: ThemeColorDoc | null | undefined, fallbackHex: string): ShadeStep[] {\n return includedShadeSteps(stateFromColorDoc(color, fallbackHex));\n}\n\n/**\n * Family keys a `lookField()` may offer: the page-editor system scales\n * plus every custom color on Colors. Adding Coral on Theme makes `coral`\n * appear here with no `@bison-lab/*` release.\n */\nexport function pageEditorLooks(doc: ThemeDoc | null | undefined): LookOption[] {\n const system = PAGE_EDITOR_SYSTEM_KEYS.map((value) => ({\n label: SYSTEM_LABELS[value],\n value,\n }));\n const custom = libraryRows(doc)\n .map((row) => {\n const value = row.key?.trim();\n if (!value) return null;\n return { label: row.label?.trim() || titleCase(value), value };\n })\n .filter((option): option is LookOption => option !== null);\n return [...system, ...custom];\n}\n\n/**\n * Included steps as `coral-400`. Excluded steps do not appear. System\n * scales with no stored include list offer the full 50–950 ramp.\n */\nexport function pageEditorTokens(doc: ThemeDoc | null | undefined): LookOption[] {\n const tokens: LookOption[] = [];\n for (const key of PAGE_EDITOR_SYSTEM_KEYS) {\n const label = SYSTEM_LABELS[key];\n for (const step of stepsFor(brandColor(doc, key), \"#000000\")) {\n tokens.push({ label: `${label} ${step}`, value: `${key}-${step}` });\n }\n }\n for (const row of libraryRows(doc)) {\n const key = row.key?.trim();\n if (!key) continue;\n const label = row.label?.trim() || titleCase(key);\n for (const step of stepsFor(row, row.hex ?? \"#000000\")) {\n tokens.push({ label: `${label} ${step}`, value: `${key}-${step}` });\n }\n }\n return tokens;\n}\n\nexport async function themeDocFromRequest(req: {\n payload?: {\n findGlobal?: (args: {\n slug: string;\n depth?: number;\n overrideAccess?: boolean;\n }) => Promise<ThemeDoc | null | undefined>;\n };\n}): Promise<ThemeDoc | null> {\n try {\n const doc = await req.payload?.findGlobal?.({\n slug: THEME_SLUG,\n depth: 0,\n overrideAccess: true,\n });\n return doc ?? null;\n } catch {\n return null;\n }\n}\n","import type { StaticLabel, TextField, TextFieldSingleValidation } from \"payload\";\n\nimport { LOOK_FIELD } from \"./fields\";\nimport { pageEditorLooks, pageEditorTokens, themeDocFromRequest } from \"./looks\";\n\nexport interface LookFieldOptions {\n name?: string;\n label?: StaticLabel;\n required?: boolean;\n admin?: TextField[\"admin\"];\n validate?: TextFieldSingleValidation;\n}\n\nexport type LookFieldMode = \"look\" | \"token\";\n\nexport const validateLook: TextFieldSingleValidation = async (value, { req }) => {\n if (value == null || value === \"\") return true;\n if (typeof value !== \"string\") return \"Pick a look from Theme → Colors\";\n const doc = await themeDocFromRequest(req);\n if (pageEditorLooks(doc).some((look) => look.value === value)) return true;\n return \"Pick a look from Theme → Colors\";\n};\n\nexport const validateColorToken: TextFieldSingleValidation = async (value, { req }) => {\n if (value == null || value === \"\") return true;\n if (typeof value !== \"string\") return \"Pick an included step from Theme → Colors\";\n const doc = await themeDocFromRequest(req);\n if (pageEditorTokens(doc).some((token) => token.value === value)) return true;\n return \"Pick an included step from Theme → Colors\";\n};\n\nfunction pickerField(\n defaults: { name: string; label: string; mode: LookFieldMode; validate: TextFieldSingleValidation },\n overrides: LookFieldOptions,\n): TextField {\n const { name = defaults.name, label = defaults.label, admin, required, validate = defaults.validate } = overrides;\n return {\n name,\n type: \"text\",\n label,\n required,\n validate,\n admin: {\n ...admin,\n components: { Field: LOOK_FIELD, ...admin?.components },\n custom: { mode: defaults.mode, ...admin?.custom },\n },\n };\n}\n\n/**\n * A picker any collection or global can offer. Options come from the\n * published Theme at request time: page-editor system scales plus custom\n * Colors. Payload select options are a static list, so this is a text\n * field with an admin picker — adding Coral does not need a schema change.\n */\nexport function lookField(overrides: LookFieldOptions = {}): TextField {\n return pickerField({ name: \"look\", label: \"Look\", mode: \"look\", validate: validateLook }, overrides);\n}\n\n/**\n * Included steps as `coral-400`. Excluded steps do not appear.\n */\nexport function colorTokenField(overrides: LookFieldOptions = {}): TextField {\n return pickerField(\n { name: \"color\", label: \"Color\", mode: \"token\", validate: validateColorToken },\n overrides,\n );\n}\n","import type { ThemeDoc, ThemeUploadDoc } from \"./types\";\n\nexport interface ThemeIdentityAsset {\n url: string;\n alt: string;\n}\n\nexport interface ThemeIdentityFallback {\n lockup?: ThemeIdentityAsset;\n mark?: ThemeIdentityAsset;\n favicon?: ThemeIdentityAsset;\n}\n\nexport interface ResolvedThemeIdentity {\n lockup: ThemeIdentityAsset | null;\n mark: ThemeIdentityAsset | null;\n favicon: ThemeIdentityAsset | null;\n}\n\nfunction fromUpload(value: ThemeDoc[\"logo\"], fallback?: ThemeIdentityAsset): ThemeIdentityAsset | null {\n if (value && typeof value === \"object\") {\n const upload = value as ThemeUploadDoc;\n if (upload.url) {\n return { url: upload.url, alt: upload.alt ?? fallback?.alt ?? \"\" };\n }\n }\n return fallback ?? null;\n}\n\n/**\n * Uploaded logo, favicon, and mobile-menu mark, or the fallback the site\n * passed into `createTheme`. Empty fields keep the fallback;\n * `--color-primary-mark` is not a Theme field.\n */\nexport function resolveThemeIdentity(\n doc: ThemeDoc | null | undefined,\n fallback?: ThemeIdentityFallback,\n): ResolvedThemeIdentity {\n return {\n lockup: fromUpload(doc?.logo, fallback?.lockup),\n mark: fromUpload(doc?.logoMark, fallback?.mark),\n favicon: fromUpload(doc?.favicon, fallback?.favicon),\n };\n}\n"],"mappings":";;AAIA,MAAa,oBAAoB;AACjC,MAAa,0BAA0B;AACvC,MAAa,sBAAsB;AACnC,MAAa,mBAAmB;AAChC,MAAa,sBAAsB;AACnC,MAAa,yBAAyB;AACtC,MAAa,yBAAyB;AACtC,MAAa,wBAAwB;AACrC,MAAa,wBAAwB;AACrC,MAAa,sBAAsB;AACnC,MAAa,oBAAoB;AACjC,MAAa,0BAA0B;AACvC,MAAa,0BAA0B;AACvC,MAAa,aAAa;AA6B1B,SAAgB,mBAAmB,SAA6C;AAC9E,QAAO,WAAA;;;;AC/BT,SAAS,KAAQ,OAA6B,UAAgB;AAC5D,QAAO,SAAS,OAAO,WAAW;;AAGpC,SAAS,eACP,SACA,MACe;AACf,KAAI,YAAY,KAAA,EAAW,QAAO;AAClC,KAAI,YAAY,QAAQ,YAAA,GAA0B,QAAO;AACzD,QAAO;;AAGT,SAAS,aAAa,OAAkD,UAA0B;AAChG,KAAI,OAAO,UAAU,SAAU,QAAO,SAAS;AAC/C,QAAO,KAAK,OAAO,KAAK,SAAS;;AAGnC,SAAgB,gBAAgB,KAAa,aAAwB,KAAoB;AACvF,QAAO,kBAAkB,iBAAiB,KAAK,WAAW,CAAC;;AAG7D,SAAgB,kBAAkB,OAAuC;AACvE,QAAO;EACL,KAAK,MAAM;EACX,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,OAAO,MAAM;EACb,SAAS,MAAM,QAAQ,MAAM,QAAQ,GAAG,WAAW,MAAM;EACzD,eAAe,MAAM,QAAQ,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,QAAQ,GAAG,KAAA;EACpE;;AAGH,SAAgB,kBACd,KACA,cAAc,WACG;CACjB,MAAM,MAAM,WAAW,KAAK,IAAI,GAAG,IAAI,MAAM;CAC7C,MAAM,aAAc,OAAO,KAAK,WAAW,IAAI;CAC/C,MAAM,UAAU,eAAe,KAAK,WAAW;AAG/C,QAAO;EAAE;EAAK;EAAY,OADxB,KAAK,SAAS,OAAO,IAAI,UAAU,WAAY,IAAI,QAAuB,mBAAmB,KAAK,WAAW;EAC9E,OAAO,QAAQ,KAAK,MAAM;EAAE;EAAS;;AAGxE,SAAS,eAAe,KAAuC,YAA0C;AACvG,KAAI,KAAK,YAAY,SAAU,QAAO;AACtC,KAAI,KAAK,YAAY,YAAY,MAAM,QAAQ,IAAI,cAAc,EAAE;EACjE,MAAM,QAAQ,IAAI,cAAc,IAAI,OAAO,CAAC,QAAQ,SAA4B,YAAY,SAAS,KAAkB,CAAC;AACxH,MAAI,CAAC,MAAM,SAAS,WAAW,CAAE,OAAM,KAAK,WAAW;AACvD,SAAO;;AAET,QAAO;;;;;;;AAQT,SAAgB,mBAAmB,KAAkC,MAAgC;CACnG,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK;CACzC,MAAM,aAAa,KAAK,cAAc,KAAK;CAC3C,MAAM,aAAa,KAAK;AACxB,QAAO;EACL,cAAc,aAAa,OAAO,SAAS,KAAK,aAAa;EAC7D,gBAAgB,aAAa,OAAO,WAAW,KAAK,eAAe;EACnE,aAAa,aAAa,OAAO,QAAQ,KAAK,YAAY;EAC1D,gBAAgB,aAAa,OAAO,WAAW,KAAK,eAAe;EACnE,cAAc,aAAa,OAAO,SAAS,KAAK,aAAa;EAC7D,kBACE,aAAa,OAAO,aAAa,KAAK,oBAAoB,sBAAsB,IAChF,KAAK,oBACL;EACF,WAAW,KAAK,KAAK,QAAQ,aAAa,KAAK,WAAW,KAAK,UAAU;EACzE,QAAQ,KAAK,YAAY,UAAU,KAAK,QAAQ,KAAK,OAAO;EAC5D,QAAQ,KAAK,YAAY,UAAU,KAAK,QAAQ,KAAK,OAAO;EAC5D,QAAQ,KAAK,YAAY,UAAU,KAAK,QAAQ,KAAK,OAAO;EAC5D,SAAS,KAAK,YAAY,WAAW,KAAK,SAAS,KAAK,QAAQ;EAChE,cAAc,KAAK,YAAY,gBAAgB,KAAK,cAAc,KAAK,aAAa;EACpF,UAAU,KAAK,YAAY,MAAM,KAAK,SAAS;EAC/C,aAAa,eAAe,YAAY,SAAS,KAAK,YAAY;EAClE,cAAc,KAAK;EACnB,aAAa,KAAK;EACnB;;;AAIH,SAAgB,cAAc,QAAqB,aAAgC;CACjF,MAAM,iBAAiB,eAAe,OAAO,oBAAoB;AACjE,QAAO;EACL,QAAQ;GACN,OAAO;IACL,SAAS,gBAAgB,OAAO,aAAa;IAC7C,WAAW,gBAAgB,OAAO,eAAe;IACjD,QAAQ,gBAAgB,OAAO,YAAY;IAC3C,WAAW,gBAAgB,OAAO,eAAe;IACjD,SAAS,gBAAgB,OAAO,aAAa;IAC7C,aAAa,gBAAgB,eAAe;IAC7C;GACD,SAAS,EAAE;GACX,WAAW,OAAO;GACnB;EACD,YAAY;GACV,MAAM,OAAO;GACb,SAAS,OAAO;GACjB;EACD,YAAY;GACV,cAAc,OAAO;GACrB,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,SAAS,OAAO;GACjB;EACF;;AAGH,SAAgB,iBAAiB,OAA+B;AAC9D,KAAI,CAAC,WAAW,MAAM,CAAE,QAAO;AAC/B,QAAO;;AAGT,SAAgB,uBACd,OACA,EAAE,eACa;CACf,MAAM,UAAU,aAAa;AAC7B,KAAI,YAAY,SAAS,YAAY,SAAU,QAAO;CACtD,MAAM,SAAS,OAAO,aAAa,WAAW;CAC9C,MAAM,QAAQ,MAAM,QAAQ,MAAM,GAAG,MAAM,IAAI,OAAO,GAAG,EAAE;AAC3D,KAAI,CAAC,YAAY,SAAS,OAAoB,CAAE,QAAO;AACvD,KAAI,CAAC,MAAM,SAAS,OAAO,CAAE,QAAO;AACpC,QAAO;;;;ACWT,MAAa,aAAa;AAC1B,MAAa,oBAAoB;AACjC,MAAa,wBAAwB;AACrC,MAAa,wBAAwB;AACrC,MAAa,sBAAsB;AAEnC,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;ACnKD,SAAgB,cAAc,OAAwC;AACpE,QAAO,MAAM,QAAQ,MAAM,GAAG,QAAQ,EAAE;;AAG1C,MAAM,WAAW,IAAI,IAAY,YAAY;;;;;;AAO7C,SAAgB,kBAAkB,OAAe,SAAiB,OAAuB;AACvF,KAAI,UAAU,QAAS,QAAO;CAC9B,MAAM,OAAO,MAAM,YAAY,IAAI;AACnC,KAAI,QAAQ,EAAG,QAAO;CACtB,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK;CAChC,MAAM,OAAO,OAAO,MAAM,MAAM,OAAO,EAAE,CAAC;AAC1C,KAAI,QAAQ,WAAW,CAAC,SAAS,IAAI,KAAK,CAAE,QAAO;AACnD,QAAO,GAAG,MAAM,GAAG;;AAGrB,SAAgB,eAAe,KAA4C;CACzE,MAAM,SAAS,cAAc,KAAK,QAAQ,QAAQ,CAC/C,KAAK,QAAQ,IAAI,KAAK,MAAM,CAAC,CAC7B,QAAQ,QAAuB,QAAQ,IAAI,CAAC;AAC/C,QAAO,CAAC,GAAG,mBAAmB,GAAG,OAAO;;;;;;;;AAS1C,SAAgB,mBAAmB,KAAe,KAAa,aAAgC;CAC7F,MAAM,UAAU,CAAC,GAAG,cAAc,IAAI,QAAQ,QAAQ,CAAC;CACvD,MAAM,QAAQ,QAAQ,WAAW,QAAQ,IAAI,QAAQ,IAAI;AACzD,KAAI,UAAU,GACZ,OAAM,IAAI,MAAM,yBAAyB,KAAK,UAAU,IAAI,GAAG;AAEjE,SAAQ,OAAO,OAAO,EAAE;CACxB,MAAM,OAAiB;EACrB,GAAG;EACH,QAAQ;GAAE,GAAG,IAAI;GAAQ;GAAS;EACnC;AACD,KAAI,gBAAgB,KAAA,GAAW;AAC7B,MAAI,gBAAgB,IAClB,OAAM,IAAI,MAAM,wCAAwC;AAE1D,MAAI,CAAC,eAAe,KAAK,CAAC,SAAS,YAAY,CAC7C,OAAM,IAAI,MAAM,eAAe,KAAK,UAAU,YAAY,CAAC,4CAA4C;;AAG3G,QAAO;;;;;;;;ACnDT,SAAgB,aAAa,OAAe,KAAsB;AAChE,QAAO,kBAAkB,OAAO,KAAK,KAAK,MAAM,KAAK;;AAGvD,SAAgB,gBAAgB,OAAgB,KAA8B;CAC5E,MAAM,OAAwB,EAAE;AAChC,MAAK,OAAO,KAAK,MAAM,UAAU;AAC/B,MAAI,aAAa,OAAO,IAAI,CAAE,MAAK,KAAK;GAAE;GAAM;GAAO,CAAC;GACxD;AACF,QAAO;;AAGT,SAAgB,mBAAsB,OAAU,SAAiB,OAAkB;AACjF,QAAO,aAAa,OAAO,SAAS,MAAM;;AAG5C,SAAS,aAAa,OAAgB,SAAiB,OAAwB;AAC7E,KAAI,OAAO,UAAU,SAAU,QAAO,kBAAkB,OAAO,SAAS,MAAM;AAC9E,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,KAAK,SAAS,aAAa,MAAM,SAAS,MAAM,CAAC;AACxF,KAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,OAAgC,EAAE;AACxC,OAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,CAC/C,MAAK,QAAQ,aAAa,OAAO,SAAS,MAAM;AAElD,SAAO;;AAET,QAAO;;AAGT,SAAS,KAAK,OAAgB,MAAc,OAAoD;AAC9F,KAAI,OAAO,UAAU,UAAU;AAC7B,QAAM,MAAM,MAAM;AAClB;;AAEF,KAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,QAAM,SAAS,MAAM,UAAU,KAAK,MAAM,KAAK,MAAM,OAAO,MAAM,CAAC,EAAE,MAAM,CAAC;AAC5E;;AAEF,KAAI,SAAS,OAAO,UAAU,SAC5B,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,CAC/C,MAAK,OAAO,KAAK,MAAM,KAAK,EAAE,MAAM;;AAK1C,SAAS,KAAK,MAAc,SAAyB;AACnD,QAAO,OAAO,GAAG,KAAK,GAAG,YAAY;;;;;;;;AChDvC,MAAa,0BAA0B;CAAC;CAAW;CAAa;CAAU;CAAY;AAStF,MAAM,gBAAqD;CACzD,SAAS;CACT,WAAW;CACX,QAAQ;CACR,WAAW;CACZ;AAED,SAAS,WAAW,KAAkC,KAAgD;CAEpG,MAAM,SADQ,KAAK,QAAQ,SAAS,KAAK,SACnB;AACtB,KAAI,OAAO,UAAU,YAAY,SAAS,KAAM,QAAO,SAAS,OAAO,OAAO,EAAE,KAAK,OAAO;AAC5F,QAAO;;AAGT,SAAgB,oBAAoB,KAA0D;AAC5F,QAAO,MAAM,QAAQ,KAAK,QAAQ,QAAQ,GAAG,IAAI,OAAO,UAAU,EAAE;;AAGtE,SAAS,YAAY,KAA0D;AAC7E,QAAO,oBAAoB,IAAI;;AAGjC,SAAS,UAAU,KAAqB;AACtC,QAAO,IAAI,OAAO,EAAE,CAAC,aAAa,GAAG,IAAI,MAAM,EAAE;;AAGnD,SAAS,SAAS,OAAyC,aAAkC;AAC3F,QAAO,mBAAmB,kBAAkB,OAAO,YAAY,CAAC;;;;;;;AAQlE,SAAgB,gBAAgB,KAAgD;CAC9E,MAAM,SAAS,wBAAwB,KAAK,WAAW;EACrD,OAAO,cAAc;EACrB;EACD,EAAE;CACH,MAAM,SAAS,YAAY,IAAI,CAC5B,KAAK,QAAQ;EACZ,MAAM,QAAQ,IAAI,KAAK,MAAM;AAC7B,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO;GAAE,OAAO,IAAI,OAAO,MAAM,IAAI,UAAU,MAAM;GAAE;GAAO;GAC9D,CACD,QAAQ,WAAiC,WAAW,KAAK;AAC5D,QAAO,CAAC,GAAG,QAAQ,GAAG,OAAO;;;;;;AAO/B,SAAgB,iBAAiB,KAAgD;CAC/E,MAAM,SAAuB,EAAE;AAC/B,MAAK,MAAM,OAAO,yBAAyB;EACzC,MAAM,QAAQ,cAAc;AAC5B,OAAK,MAAM,QAAQ,SAAS,WAAW,KAAK,IAAI,EAAE,UAAU,CAC1D,QAAO,KAAK;GAAE,OAAO,GAAG,MAAM,GAAG;GAAQ,OAAO,GAAG,IAAI,GAAG;GAAQ,CAAC;;AAGvE,MAAK,MAAM,OAAO,YAAY,IAAI,EAAE;EAClC,MAAM,MAAM,IAAI,KAAK,MAAM;AAC3B,MAAI,CAAC,IAAK;EACV,MAAM,QAAQ,IAAI,OAAO,MAAM,IAAI,UAAU,IAAI;AACjD,OAAK,MAAM,QAAQ,SAAS,KAAK,IAAI,OAAO,UAAU,CACpD,QAAO,KAAK;GAAE,OAAO,GAAG,MAAM,GAAG;GAAQ,OAAO,GAAG,IAAI,GAAG;GAAQ,CAAC;;AAGvE,QAAO;;AAGT,eAAsB,oBAAoB,KAQb;AAC3B,KAAI;AAMF,SALY,MAAM,IAAI,SAAS,aAAa;GAC1C,MAAA;GACA,OAAO;GACP,gBAAgB;GACjB,CAAC,IACY;SACR;AACN,SAAO;;;;;AC7FX,MAAa,eAA0C,OAAO,OAAO,EAAE,UAAU;AAC/E,KAAI,SAAS,QAAQ,UAAU,GAAI,QAAO;AAC1C,KAAI,OAAO,UAAU,SAAU,QAAO;AAEtC,KAAI,gBADQ,MAAM,oBAAoB,IAAI,CAClB,CAAC,MAAM,SAAS,KAAK,UAAU,MAAM,CAAE,QAAO;AACtE,QAAO;;AAGT,MAAa,qBAAgD,OAAO,OAAO,EAAE,UAAU;AACrF,KAAI,SAAS,QAAQ,UAAU,GAAI,QAAO;AAC1C,KAAI,OAAO,UAAU,SAAU,QAAO;AAEtC,KAAI,iBADQ,MAAM,oBAAoB,IAAI,CACjB,CAAC,MAAM,UAAU,MAAM,UAAU,MAAM,CAAE,QAAO;AACzE,QAAO;;AAGT,SAAS,YACP,UACA,WACW;CACX,MAAM,EAAE,OAAO,SAAS,MAAM,QAAQ,SAAS,OAAO,OAAO,UAAU,WAAW,SAAS,aAAa;AACxG,QAAO;EACL;EACA,MAAM;EACN;EACA;EACA;EACA,OAAO;GACL,GAAG;GACH,YAAY;IAAE,OAAO;IAAY,GAAG,OAAO;IAAY;GACvD,QAAQ;IAAE,MAAM,SAAS;IAAM,GAAG,OAAO;IAAQ;GAClD;EACF;;;;;;;;AASH,SAAgB,UAAU,YAA8B,EAAE,EAAa;AACrE,QAAO,YAAY;EAAE,MAAM;EAAQ,OAAO;EAAQ,MAAM;EAAQ,UAAU;EAAc,EAAE,UAAU;;;;;AAMtG,SAAgB,gBAAgB,YAA8B,EAAE,EAAa;AAC3E,QAAO,YACL;EAAE,MAAM;EAAS,OAAO;EAAS,MAAM;EAAS,UAAU;EAAoB,EAC9E,UACD;;;;AChDH,SAAS,WAAW,OAAyB,UAA0D;AACrG,KAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,SAAS;AACf,MAAI,OAAO,IACT,QAAO;GAAE,KAAK,OAAO;GAAK,KAAK,OAAO,OAAO,UAAU,OAAO;GAAI;;AAGtE,QAAO,YAAY;;;;;;;AAQrB,SAAgB,qBACd,KACA,UACuB;AACvB,QAAO;EACL,QAAQ,WAAW,KAAK,MAAM,UAAU,OAAO;EAC/C,MAAM,WAAW,KAAK,UAAU,UAAU,KAAK;EAC/C,SAAS,WAAW,KAAK,SAAS,UAAU,QAAQ;EACrD"}
package/dist/index.d.mts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { a as SeoImageSize, i as SeoImageDoc, n as titleTemplate, o as SeoImageValue, r as MediaId, s as SeoMeta, t as documentTitle } from "./title-B2-gWQZd.mjs";
2
- import { A as ThemeIdentityFallback, C as ThemeColorDoc, D as ThemeLibraryColorDoc, O as ThemeUploadDoc, S as THEME_TYPOGRAPHY_SLUG, _ as SystemColorKey, a as pageEditorTokens, b as THEME_IDENTITY_SLUG, c as LookFieldOptions, d as deleteLibraryColor, f as rewriteColorToken, g as SYSTEM_COLOR_KEYS, h as EditableTheme, i as pageEditorLooks, j as resolveThemeIdentity, k as ResolvedThemeIdentity, l as colorTokenField, m as CreateThemeOptions, n as PAGE_EDITOR_SYSTEM_KEYS, o as themeLibraryFromDoc, p as themeColorKeys, r as PageEditorSystemKey, s as LookFieldMode, t as LookOption, u as lookField, v as THEME_APPEARANCE_SLUG, w as ThemeDoc, x as THEME_SLUG, y as THEME_COLORS_SLUG } from "./looks-DsizRfFV.mjs";
2
+ import { A as ThemeLibraryColorDoc, C as THEME_IDENTITY_SLUG, D as ThemeDoc, E as ThemeColorDoc, M as ResolvedThemeIdentity, N as ThemeIdentityFallback, P as resolveThemeIdentity, S as THEME_COLORS_SLUG, T as THEME_TYPOGRAPHY_SLUG, _ as CreateThemeOptions, a as pageEditorTokens, b as SystemColorKey, c as LookFieldOptions, d as ColorTokenHit, f as findColorTokens, g as themeColorKeys, h as rewriteColorToken, i as pageEditorLooks, j as ThemeUploadDoc, l as colorTokenField, m as deleteLibraryColor, n as PAGE_EDITOR_SYSTEM_KEYS, o as themeLibraryFromDoc, p as rewriteColorTokens, r as PageEditorSystemKey, s as LookFieldMode, t as LookOption, u as lookField, v as EditableTheme, w as THEME_SLUG, x as THEME_APPEARANCE_SLUG, y as SYSTEM_COLOR_KEYS } from "./looks-C6kTjvk3.mjs";
3
3
  import { ThemeConfig } from "@bison-lab/tokens";
4
- import { Access, CheckboxField, CollectionConfig, CollectionSlug, GlobalConfig, Plugin, UploadCollectionSlug } from "payload";
4
+ import * as payload from "payload";
5
+ import { Access, Block, CheckboxField, CollectionConfig, CollectionSlug, GlobalConfig, ImageSize, PayloadRequest, Plugin, TextField, UploadCollectionSlug } from "payload";
5
6
 
6
7
  //#region src/seo/plugin.d.ts
7
8
  /**
@@ -123,8 +124,8 @@ interface CreateBrandAssetsOptions {
123
124
  /**
124
125
  * Required, no default — same shape as `createTheme`. `read` is typically
125
126
  * public so the live site can load the lockup; `update` is `canManageBrand`
126
- * and covers create, update, delete, and version history. Predicates live
127
- * in each site's `src/platform` until BIS-43.
127
+ * and covers create, update, delete, and version history. This collection
128
+ * does not read the Roles Global.
128
129
  */
129
130
  access: {
130
131
  read: Access;
@@ -163,6 +164,59 @@ interface SeedThemePayload {
163
164
  */
164
165
  declare function seedTheme(payload: SeedThemePayload, seed: ThemeConfig): Promise<unknown>;
165
166
  //#endregion
167
+ //#region src/theme/color-usages.d.ts
168
+ interface ColorUsagesOption {
169
+ collections?: string[];
170
+ globals?: string[];
171
+ }
172
+ interface ColorUsage {
173
+ collection?: string;
174
+ global?: string;
175
+ id?: string | number;
176
+ path: string;
177
+ token: string;
178
+ }
179
+ type FindResult = {
180
+ docs: Record<string, unknown>[];
181
+ totalPages?: number;
182
+ page?: number;
183
+ };
184
+ interface ColorUsagePayload {
185
+ find: (args: {
186
+ collection: string;
187
+ draft?: boolean;
188
+ depth?: number;
189
+ limit?: number;
190
+ page?: number;
191
+ overrideAccess?: boolean;
192
+ }) => Promise<FindResult>;
193
+ findGlobal: (args: {
194
+ slug: string;
195
+ draft?: boolean;
196
+ depth?: number;
197
+ overrideAccess?: boolean;
198
+ }) => Promise<Record<string, unknown> | ThemeDoc | null | undefined>;
199
+ update: (args: {
200
+ collection: string;
201
+ id: string | number;
202
+ data: Record<string, unknown>;
203
+ draft?: boolean;
204
+ overrideAccess?: boolean;
205
+ }) => Promise<unknown>;
206
+ updateGlobal: (args: {
207
+ slug: string;
208
+ data: Record<string, unknown>;
209
+ draft?: boolean;
210
+ overrideAccess?: boolean;
211
+ }) => Promise<unknown>;
212
+ }
213
+ /**
214
+ * Walk every current document in the named collections and globals
215
+ * (drafts included). A color only on an unpublished page is still in use.
216
+ */
217
+ declare function findColorUsages(payload: ColorUsagePayload, scopes: ColorUsagesOption | undefined, key: string): Promise<ColorUsage[]>;
218
+ declare function rewriteColorUsages(payload: ColorUsagePayload, scopes: ColorUsagesOption | undefined, fromKey: string, toKey: string): Promise<void>;
219
+ //#endregion
166
220
  //#region src/theme/preview.d.ts
167
221
  /**
168
222
  * Device sizes the Theme (and, later, Pages) live-preview toolbar offers.
@@ -231,5 +285,415 @@ declare const THEME_DOCUMENT_CONTROLS = "@bison-lab/payload-core/admin#ThemeDocu
231
285
  declare const THEME_IDENTITY_FALLBACK = "@bison-lab/payload-core/admin#IdentityFallback";
232
286
  declare const LOOK_FIELD = "@bison-lab/payload-core/admin#LookField";
233
287
  //#endregion
234
- export { BRAND_ASSETS_MIME_TYPES, BRAND_ASSETS_SLUG, type CreateBrandAssetsOptions, type CreateThemeOptions, DESCRIPTION_LENGTH, type EditableTheme, LOOK_FIELD, type LookFieldMode, type LookFieldOptions, type LookOption, type MediaId, PAGE_EDITOR_SYSTEM_KEYS, type PageEditorSystemKey, type ResolvedThemeIdentity, SHARE_IMAGE_SIZE, SYSTEM_COLOR_KEYS, type SeoDoc, type SeoImageDoc, type SeoImageSize, type SeoImageValue, type SeoMeta, type SeoPluginOptions, type SystemColorKey, THEME_APPEARANCE_FIELD, THEME_APPEARANCE_SLUG, THEME_COLORS_SLUG, THEME_COLOR_FIELD, THEME_COLOR_SCALE_FIELD, THEME_CONTRAST_REPORT, THEME_DOCUMENT_CONTROLS, THEME_FONT_FIELD, THEME_GREY_SCALE_FIELD, THEME_IDENTITY_FALLBACK, THEME_IDENTITY_SLUG, THEME_LIBRARY_FIELD, THEME_PAIRING_FIELD, THEME_PREVIEW_BREAKPOINTS, THEME_PUBLISH_FIELD, THEME_SAVE_BUTTON, THEME_SECTION_HEADING, THEME_SLUG, THEME_TYPOGRAPHY_SLUG, type ThemeChild, type ThemeColorDoc, type ThemeDoc, type ThemeIdentityFallback, type ThemeLibraryColorDoc, type ThemeUploadDoc, colorTokenField, createBrandAssets, createTheme, deleteLibraryColor, documentTitle, firstImageIn, lookField, noIndexField, pageEditorLooks, pageEditorTokens, persistThemeChild, publishThemeChild, resolveThemeIdentity, rewriteColorToken, sanitizeSvg, seedTheme, seoPlugin, themeColorKeys, themeLibraryFromDoc, titleTemplate, truncateAtWord };
288
+ //#region src/roles/types.d.ts
289
+ /**
290
+ * The Roles Global a site's generated types will describe. Optional and
291
+ * nullable, no index signature: a generated Global is assignable to this,
292
+ * never the reverse.
293
+ */
294
+ declare const ROLES: readonly ["developer", "admin", "designer", "author"];
295
+ type Role = (typeof ROLES)[number];
296
+ declare const ROLE_LABELS: Record<Role, string>;
297
+ declare const CAPABILITIES: readonly ["content", "brand", "publish", "users"];
298
+ type Capability = (typeof CAPABILITIES)[number];
299
+ /**
300
+ * A site-defined row passed into `createRoles({ extras })`. The slug is the
301
+ * stored key; the label is what Settings → Roles and Users show.
302
+ */
303
+ interface RoleExtra {
304
+ role: string;
305
+ label: string;
306
+ content: boolean;
307
+ brand: boolean;
308
+ publish: boolean;
309
+ users: boolean;
310
+ }
311
+ interface RoleRow {
312
+ id?: string | null;
313
+ role: string;
314
+ label?: string | null;
315
+ content: boolean;
316
+ brand: boolean;
317
+ publish: boolean;
318
+ users: boolean;
319
+ }
320
+ interface RolesDoc {
321
+ roles?: RoleRow[] | null;
322
+ }
323
+ /**
324
+ * `req.user` as Payload hands it over. Structural so this module never
325
+ * imports a site's generated `User`. `roles` is optional because a row
326
+ * that predates the field must come back powerless.
327
+ */
328
+ type MaybeUser = {
329
+ id?: string | number;
330
+ roles?: readonly string[] | null;
331
+ } | null | undefined;
332
+ declare function isRole(value: unknown): value is Role;
333
+ /** Lowercase slug: `editor`, `site-editor`. Empty and punctuation are out. */
334
+ declare function isRoleSlug(value: unknown): value is string;
335
+ declare function roleLabel(role: string, label?: string | null): string;
336
+ //#endregion
337
+ //#region src/roles/global.d.ts
338
+ interface CreateRolesOptions {
339
+ /**
340
+ * Extra catalogue rows after the seed four. Each is a slug, a display
341
+ * label, and default ticks. A site that passes nothing still gets
342
+ * Developer / Admin / Designer / Author.
343
+ */
344
+ extras?: readonly RoleExtra[];
345
+ }
346
+ /**
347
+ * Settings → Roles. Rank is the array order (Payload's drag handle). Ticks
348
+ * are Content, Brand, Publish, Users. The API tab is not a column.
349
+ */
350
+ declare function createRoles({
351
+ extras
352
+ }?: CreateRolesOptions): GlobalConfig;
353
+ //#endregion
354
+ //#region src/roles/seed.d.ts
355
+ interface SeedRolesPayload {
356
+ updateGlobal: (args: {
357
+ slug: string;
358
+ data: RolesDoc;
359
+ }) => Promise<unknown>;
360
+ }
361
+ /**
362
+ * Writes the default matrix (seed four plus any site extras). For a site's
363
+ * migration `up()`. Never writes a user row — seeding `developer` on a
364
+ * person stays a site concern.
365
+ */
366
+ declare function seedRoles(payload: SeedRolesPayload, extras?: readonly RoleExtra[]): Promise<unknown>;
367
+ //#endregion
368
+ //#region src/roles/fields.d.ts
369
+ declare const ROLES_MATRIX_FIELD = "@bison-lab/payload-core/admin#RolesMatrixField";
370
+ declare const ROLES_ROW_LABEL = "@bison-lab/payload-core/admin#RolesRowLabel";
371
+ declare const ROLE_SLUG_FIELD = "@bison-lab/payload-core/admin#RoleSlugField";
372
+ declare const ROLES_FIELD = "@bison-lab/payload-core/admin#RolesField";
373
+ //#endregion
374
+ //#region src/features/types.d.ts
375
+ declare const FEATURES_SLUG = "features";
376
+ declare const PACKAGE_FEATURE_SLUGS: readonly ["pages", "media", "theme", "brand-assets", "users", "roles", "features"];
377
+ type PackageFeatureSlug = (typeof PACKAGE_FEATURE_SLUGS)[number];
378
+ interface PackageFeature {
379
+ slug: PackageFeatureSlug;
380
+ label: string;
381
+ fallback: Capability;
382
+ locked: boolean;
383
+ }
384
+ /**
385
+ * Package catalogue. Locked rows cannot be turned off. Empty Global falls
386
+ * back to the named capability (Content → Pages/Media, Brand → Theme /
387
+ * Brand assets, Users → Users/Roles/Features).
388
+ */
389
+ declare const PACKAGE_FEATURES: readonly PackageFeature[];
390
+ /** A site-only row. Other sites never see this slug. */
391
+ interface FeatureExtra {
392
+ slug: string;
393
+ label: string;
394
+ }
395
+ interface FeatureRow {
396
+ id?: string | null;
397
+ slug: string;
398
+ label?: string | null;
399
+ locked?: boolean | null;
400
+ roles?: string[] | null;
401
+ }
402
+ interface FeaturesDoc {
403
+ features?: FeatureRow[] | null;
404
+ }
405
+ declare function isPackageFeatureSlug(value: unknown): value is PackageFeatureSlug;
406
+ declare function isFeatureSlug(value: unknown): value is string;
407
+ declare function isLockedFeature(slug: string, locked?: boolean | null): boolean;
408
+ //#endregion
409
+ //#region src/features/global.d.ts
410
+ interface CreateFeaturesOptions {
411
+ /**
412
+ * Site-only rows after the package catalogue. A second `createFeatures()`
413
+ * without these extras never includes them.
414
+ */
415
+ extras?: readonly FeatureExtra[];
416
+ }
417
+ /**
418
+ * Settings → Features. Rows are the catalogue (code). Columns are the
419
+ * Roles Global. Locked rows and Developer cannot be turned off.
420
+ */
421
+ declare function createFeatures({
422
+ extras
423
+ }?: CreateFeaturesOptions): GlobalConfig;
424
+ //#endregion
425
+ //#region src/features/seed.d.ts
426
+ interface SeedFeaturesPayload {
427
+ updateGlobal: (args: {
428
+ slug: string;
429
+ data: FeaturesDoc;
430
+ }) => Promise<unknown>;
431
+ }
432
+ /**
433
+ * Writes the default switchboard (package rows plus any site extras).
434
+ * For a site's migration `up()`.
435
+ */
436
+ declare function seedFeatures(payload: SeedFeaturesPayload, extras?: readonly FeatureExtra[]): Promise<unknown>;
437
+ //#endregion
438
+ //#region src/features/fields.d.ts
439
+ declare const FEATURES_MATRIX_FIELD = "@bison-lab/payload-core/admin#FeaturesMatrixField";
440
+ //#endregion
441
+ //#region src/roles/access.d.ts
442
+ type AccessArgs$1 = {
443
+ req: {
444
+ user?: MaybeUser;
445
+ payload?: Pick<NonNullable<PayloadRequest["payload"]>, "findGlobal">;
446
+ };
447
+ };
448
+ type CapabilityPredicate = {
449
+ (user: MaybeUser, matrix?: readonly RoleRow[]): boolean;
450
+ (args: AccessArgs$1): boolean | Promise<boolean>;
451
+ };
452
+ declare function storedRoles(user: MaybeUser): readonly string[];
453
+ declare function hasRole(user: MaybeUser, role: string): boolean;
454
+ /** Not a tick. True only when `developer` is stored on the row. */
455
+ declare function isDeveloper(user: MaybeUser): boolean;
456
+ declare function hasCapability(user: MaybeUser, capability: Capability, matrix?: readonly RoleRow[]): boolean;
457
+ /**
458
+ * Reads the saved Roles Global, falling back to the seed when the row is
459
+ * empty, missing, or unreadable. Always override-access so a Designer
460
+ * evaluating Theme does not have to read Settings → Roles.
461
+ */
462
+ declare function getRolesMatrix(req?: AccessArgs$1["req"], extras?: readonly RoleExtra[]): Promise<RoleRow[]>;
463
+ declare const canManageContent: CapabilityPredicate;
464
+ declare const canManageBrand: CapabilityPredicate;
465
+ declare const canPublish: CapabilityPredicate;
466
+ /** Users tick on any held role. */
467
+ declare const isAdmin: CapabilityPredicate;
468
+ /** This role id currently has the Users tick. */
469
+ declare function isPrivilegedRole(role: unknown, matrix?: readonly RoleRow[]): boolean;
470
+ declare function isAuthenticated(user: MaybeUser): boolean;
471
+ declare function isAuthenticated(args: AccessArgs$1): boolean;
472
+ declare const isAdminOrSelf: Access;
473
+ declare const authenticatedOrPublished: Access;
474
+ /** API tab condition: Developer only, not the Users tick. */
475
+ declare function isDeveloperTab({
476
+ req
477
+ }: AccessArgs$1): boolean;
478
+ //#endregion
479
+ //#region src/features/access.d.ts
480
+ type AccessArgs = {
481
+ req: {
482
+ user?: MaybeUser;
483
+ payload?: Pick<NonNullable<PayloadRequest["payload"]>, "findGlobal">;
484
+ };
485
+ };
486
+ type FeaturePredicate = {
487
+ (user: MaybeUser, features?: readonly FeatureRow[] | null, roles?: readonly RoleRow[]): boolean;
488
+ (args: AccessArgs): boolean | Promise<boolean>;
489
+ };
490
+ /**
491
+ * True when any stored role is on the feature row. An empty grid falls
492
+ * back to the capability map. Developer is always allowed.
493
+ */
494
+ declare function hasFeature(user: MaybeUser, slug: string, features?: readonly FeatureRow[] | null, matrix?: readonly RoleRow[]): boolean;
495
+ /**
496
+ * Reads the saved Features Global. `null` means empty or unreadable — callers
497
+ * fall back to the capability map. Always override-access.
498
+ */
499
+ declare function getFeaturesMatrix(req?: AccessArgs["req"]): Promise<FeatureRow[] | null>;
500
+ /**
501
+ * Access / nav helper for one catalogue slug. Sync against a passed grid;
502
+ * async when given `req` so it can read both Globals.
503
+ */
504
+ declare function canUseFeature(slug: string): FeaturePredicate;
505
+ /** `admin.hidden`: hide when the login cannot use the feature. */
506
+ declare function hideUnlessFeature(slug: string): (args: {
507
+ user?: MaybeUser;
508
+ req?: AccessArgs["req"];
509
+ }) => boolean | Promise<boolean>;
510
+ //#endregion
511
+ //#region src/features/matrix.d.ts
512
+ declare const MISSING_PACKAGE_FEATURES_MESSAGE = "Features must include Pages, Media, Theme, Brand assets, Users, Roles, and Features.";
513
+ declare const LOCKED_FEATURE_MESSAGE = "Users, Roles, and Features cannot be turned off. Developer stays allowed on every row.";
514
+ interface FeatureCatalogueEntry {
515
+ slug: string;
516
+ label: string;
517
+ fallback: PackageFeature["fallback"] | null;
518
+ locked: boolean;
519
+ }
520
+ declare function featureCatalogue(extras?: readonly FeatureExtra[]): FeatureCatalogueEntry[];
521
+ declare function defaultFeaturesFieldValue(extras?: readonly FeatureExtra[], matrix?: readonly RoleRow[]): FeatureRow[];
522
+ declare function parseFeaturesMatrix(value: unknown): {
523
+ ok: true;
524
+ rows: FeatureRow[];
525
+ } | {
526
+ ok: false;
527
+ message: string;
528
+ };
529
+ declare function validateFeaturesMatrix(value: unknown, extras?: readonly FeatureExtra[], matrix?: readonly RoleRow[]): true | string;
530
+ //#endregion
531
+ //#region src/collections/pages.d.ts
532
+ interface PagesOptions {
533
+ /**
534
+ * What a page may open with. One row, so this is the site's hero variants,
535
+ * not a list a page picks several from.
536
+ */
537
+ heroBlocks: Block[];
538
+ /** The sections a page body can be built out of. */
539
+ layoutBlocks: Block[];
540
+ /**
541
+ * True when a slug is not the CMS's to hand out — a path the app already
542
+ * serves from code, or one the framework reserves.
543
+ */
544
+ isReservedSlug: (slug: string) => boolean;
545
+ /**
546
+ * Public path of a page from its stored slug — the site's own route helper.
547
+ * Drives the page preview overlay's iframe.
548
+ */
549
+ previewPath: (slug: string) => string;
550
+ /**
551
+ * Component path for Payload's native Preview button, as a key into the
552
+ * site's committed `importMap.js`. Omit it for Payload's own button.
553
+ */
554
+ previewButton?: string;
555
+ }
556
+ /**
557
+ * CMS-managed pages: one required hero, then a reorderable body of sections.
558
+ * The CMS owns copy and the order of sections; what a section looks like is
559
+ * code-owned, which is why the blocks are an argument.
560
+ */
561
+ declare function createPages({
562
+ heroBlocks,
563
+ isReservedSlug,
564
+ layoutBlocks,
565
+ previewPath,
566
+ previewButton
567
+ }: PagesOptions): CollectionConfig;
568
+ //#endregion
569
+ //#region src/collections/users.d.ts
570
+ /** Refuses the save that would remove Users from the only privileged user. */
571
+ declare const LAST_ADMIN_DEMOTE_MESSAGE = "Make another user an admin before removing it from this one.";
572
+ /** Refuses the delete that would remove the only privileged user. */
573
+ declare const LAST_ADMIN_DELETE_MESSAGE = "Make another user an admin before deleting this one.";
574
+ interface UsersOptions {
575
+ /**
576
+ * Whether the auth cookie is marked Secure. Passed in rather than read from
577
+ * the environment here: which env var means "served over HTTPS" is the
578
+ * host's business, not the CMS's.
579
+ */
580
+ secureCookies: boolean;
581
+ /**
582
+ * Component path for the roles field, as a key into the site's committed
583
+ * `importMap.js`. Defaults to the package checklist. A value nothing
584
+ * resolves renders the field as nothing.
585
+ */
586
+ rolesField?: string;
587
+ /**
588
+ * Same extras passed to `createRoles`. Offered on Users when the Global
589
+ * is empty; a saved Global (including Admin-added rows) wins.
590
+ */
591
+ extras?: readonly RoleExtra[];
592
+ }
593
+ declare function createUsers({
594
+ secureCookies,
595
+ rolesField,
596
+ extras
597
+ }: UsersOptions): CollectionConfig;
598
+ //#endregion
599
+ //#region src/collections/media.d.ts
600
+ interface MediaOptions {
601
+ /** Where uploads land, relative to the app root. */
602
+ staticDir?: string;
603
+ mimeTypes?: string[];
604
+ /**
605
+ * Renditions sharp cuts on upload, each served at its own URL under
606
+ * `sizes.<name>` on the document. Which sizes a site needs is the site's
607
+ * call. A file uploaded before a size was added has no rendition for it.
608
+ */
609
+ imageSizes?: ImageSize[];
610
+ }
611
+ /**
612
+ * Uploads, readable by anyone: the public site serves these files. Writing is
613
+ * an editorial action, deleting is not — removing an asset a live page
614
+ * references breaks that page.
615
+ */
616
+ declare function createMedia({
617
+ staticDir,
618
+ mimeTypes,
619
+ imageSizes
620
+ }?: MediaOptions): CollectionConfig;
621
+ //#endregion
622
+ //#region src/fields/slug.d.ts
623
+ interface SlugFieldOptions {
624
+ /** The collection the field sits on — what the duplicate check searches. */
625
+ collection: CollectionSlug;
626
+ /**
627
+ * True when a slug belongs to something the CMS does not own: a path the app
628
+ * already serves from code, or one reserved for the framework. Which paths
629
+ * those are is knowable only to the site, so it arrives as an option.
630
+ */
631
+ isReserved: (slug: string) => boolean;
632
+ }
633
+ /**
634
+ * A stored slug from whatever was typed: lowercased, with any run of
635
+ * whitespace and slashes stripped from either end in one pass, so `/ about-us /`
636
+ * does not keep the inner spaces a trim-then-strip would leave.
637
+ */
638
+ declare function normalizeSlug(value: string): string;
639
+ /**
640
+ * The path a document is published at, normalised on the way in. Unique
641
+ * across the collection. `validate` is the message under the field;
642
+ * `beforeChange` is the enforcement on a draft save, where Payload skips
643
+ * field validation.
644
+ */
645
+ declare function slugField({
646
+ collection,
647
+ isReserved
648
+ }: SlugFieldOptions): TextField;
649
+ //#endregion
650
+ //#region src/plugins/admin-only-api-tab.d.ts
651
+ /**
652
+ * Gates the document API tab to Developer, config-wide. After plugins that
653
+ * use `definePlugin` order (MCP is 10), so a collection those plugins
654
+ * register is still covered.
655
+ */
656
+ declare const adminOnlyApiTab: () => payload.Plugin;
657
+ //#endregion
658
+ //#region src/roles/matrix.d.ts
659
+ declare const ROLES_SLUG = "roles";
660
+ declare const ROLES_GLOBAL_DESCRIPTION = "Who may do what. Drag to change rank.";
661
+ declare const ROLES_FIELD_DESCRIPTION = "Drag to change rank. Ticks are Content, Brand, Publish, and Users.";
662
+ /**
663
+ * Default rank and ticks. Brand is Designer only. The API tab is not a
664
+ * column — it is locked to Developer in `isDeveloper`.
665
+ */
666
+ declare const DEFAULT_ROLE_MATRIX: readonly RoleRow[];
667
+ declare const DEVELOPER_DESCRIPTION = "Everything Admin can do, plus the document API tab.";
668
+ declare const LAST_USERS_TICK_MESSAGE = "Keep Users ticked on at least one of Admin or Developer.";
669
+ declare const MISSING_SEED_ROLES_MESSAGE = "Roles must include Developer, Admin, Designer, and Author.";
670
+ declare const DUPLICATE_ROLE_SLUG_MESSAGE = "Each role needs a unique slug.";
671
+ declare function seedRoleRows(extras?: readonly RoleExtra[]): RoleRow[];
672
+ declare function defaultRolesFieldValue(extras?: readonly RoleExtra[]): RoleRow[];
673
+ declare function parseRolesMatrix(value: unknown): {
674
+ ok: true;
675
+ rows: RoleRow[];
676
+ } | {
677
+ ok: false;
678
+ message: string;
679
+ };
680
+ declare function validateRolesMatrix(value: unknown): true | string;
681
+ /**
682
+ * Developer is exclusive. Admin does not swallow Designer: both store.
683
+ * Unknown slugs (dropped catalogue keys such as `approver`) fall away unless
684
+ * they appear on the matrix.
685
+ */
686
+ declare function normalizeStoredRoles(value: unknown, matrix?: readonly RoleRow[]): string[];
687
+ declare function roleSelectOptions(matrix?: readonly RoleRow[]): {
688
+ label: string;
689
+ value: string;
690
+ }[];
691
+ /** Capability copy only. Developer names the API tab; nothing about MCP or seed. */
692
+ declare function roleDescription(role: string, matrix?: readonly RoleRow[]): string;
693
+ /**
694
+ * Seed ticks and package labels come back; extra rows stay as they are.
695
+ */
696
+ declare function resetRolesMatrix(current: unknown): RoleRow[];
697
+ //#endregion
698
+ export { BRAND_ASSETS_MIME_TYPES, BRAND_ASSETS_SLUG, CAPABILITIES, type Capability, type ColorTokenHit, type ColorUsage, type ColorUsagesOption, type CreateBrandAssetsOptions, type CreateFeaturesOptions, type CreateRolesOptions, type CreateThemeOptions, DEFAULT_ROLE_MATRIX, DESCRIPTION_LENGTH, DEVELOPER_DESCRIPTION, DUPLICATE_ROLE_SLUG_MESSAGE, type EditableTheme, FEATURES_MATRIX_FIELD, FEATURES_SLUG, type FeatureExtra, type FeatureRow, type FeaturesDoc, LAST_ADMIN_DELETE_MESSAGE, LAST_ADMIN_DEMOTE_MESSAGE, LAST_USERS_TICK_MESSAGE, LOCKED_FEATURE_MESSAGE, LOOK_FIELD, type LookFieldMode, type LookFieldOptions, type LookOption, MISSING_PACKAGE_FEATURES_MESSAGE, MISSING_SEED_ROLES_MESSAGE, type MaybeUser, type MediaId, type MediaOptions, PACKAGE_FEATURES, PACKAGE_FEATURE_SLUGS, PAGE_EDITOR_SYSTEM_KEYS, type PackageFeature, type PackageFeatureSlug, type PageEditorSystemKey, type PagesOptions, ROLES, ROLES_FIELD, ROLES_FIELD_DESCRIPTION, ROLES_GLOBAL_DESCRIPTION, ROLES_MATRIX_FIELD, ROLES_ROW_LABEL, ROLES_SLUG, ROLE_LABELS, ROLE_SLUG_FIELD, type ResolvedThemeIdentity, type Role, type RoleExtra, type RoleRow, type RolesDoc, SHARE_IMAGE_SIZE, SYSTEM_COLOR_KEYS, type SeoDoc, type SeoImageDoc, type SeoImageSize, type SeoImageValue, type SeoMeta, type SeoPluginOptions, type SlugFieldOptions, type SystemColorKey, THEME_APPEARANCE_FIELD, THEME_APPEARANCE_SLUG, THEME_COLORS_SLUG, THEME_COLOR_FIELD, THEME_COLOR_SCALE_FIELD, THEME_CONTRAST_REPORT, THEME_DOCUMENT_CONTROLS, THEME_FONT_FIELD, THEME_GREY_SCALE_FIELD, THEME_IDENTITY_FALLBACK, THEME_IDENTITY_SLUG, THEME_LIBRARY_FIELD, THEME_PAIRING_FIELD, THEME_PREVIEW_BREAKPOINTS, THEME_PUBLISH_FIELD, THEME_SAVE_BUTTON, THEME_SECTION_HEADING, THEME_SLUG, THEME_TYPOGRAPHY_SLUG, type ThemeChild, type ThemeColorDoc, type ThemeDoc, type ThemeIdentityFallback, type ThemeLibraryColorDoc, type ThemeUploadDoc, type UsersOptions, adminOnlyApiTab, authenticatedOrPublished, canManageBrand, canManageContent, canPublish, canUseFeature, colorTokenField, createBrandAssets, createFeatures, createMedia, createPages, createRoles, createTheme, createUsers, defaultFeaturesFieldValue, defaultRolesFieldValue, deleteLibraryColor, documentTitle, featureCatalogue, findColorTokens, findColorUsages, firstImageIn, getFeaturesMatrix, getRolesMatrix, hasCapability, hasFeature, hasRole, hideUnlessFeature, isAdmin, isAdminOrSelf, isAuthenticated, isDeveloper, isDeveloperTab, isFeatureSlug, isLockedFeature, isPackageFeatureSlug, isPrivilegedRole, isRole, isRoleSlug, lookField, noIndexField, normalizeSlug, normalizeStoredRoles, pageEditorLooks, pageEditorTokens, parseFeaturesMatrix, parseRolesMatrix, persistThemeChild, publishThemeChild, resetRolesMatrix, resolveThemeIdentity, rewriteColorToken, rewriteColorTokens, rewriteColorUsages, roleDescription, roleLabel, roleSelectOptions, sanitizeSvg, seedFeatures, seedRoleRows, seedRoles, seedTheme, seoPlugin, slugField, storedRoles, themeColorKeys, themeLibraryFromDoc, titleTemplate, truncateAtWord, validateFeaturesMatrix, validateRolesMatrix };
235
699
  //# sourceMappingURL=index.d.mts.map