@bison-lab/payload-core 3.11.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -16
- package/dist/admin.d.mts +16 -1
- package/dist/admin.d.mts.map +1 -1
- package/dist/admin.mjs +236 -3
- package/dist/admin.mjs.map +1 -1
- package/dist/identity-DZOb3_Gk.mjs.map +1 -1
- package/dist/index.d.mts +251 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +658 -3
- package/dist/index.mjs.map +1 -1
- package/dist/looks-BbL309kO.mjs.map +1 -1
- package/dist/{looks-DsizRfFV.d.mts → looks-CLxXwASa.d.mts} +3 -4
- package/dist/{looks-DsizRfFV.d.mts.map → looks-CLxXwASa.d.mts.map} +1 -1
- package/dist/theme.d.mts +1 -1
- package/package.json +4 -4
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["payloadSeoPlugin","requireAccess"],"sources":["../src/seo/fields.ts","../src/seo/text.ts","../src/seo/plugin.ts","../src/theme/appearance-labels.ts","../src/theme/publish.ts","../src/theme/global.ts","../src/brand-assets/sanitize.ts","../src/brand-assets/collection.ts","../src/theme/seed.ts","../src/theme/preview.ts"],"sourcesContent":["import type { CheckboxField } from \"payload\";\n\n/**\n * The index switch, last in the SEO tab. Off by default: a page is public\n * unless an editor says otherwise. `pageMetadata` turns it into\n * `noindex, nofollow`; a site's sitemap should filter on it too.\n */\nexport const noIndexField: CheckboxField = {\n name: \"noIndex\",\n type: \"checkbox\",\n label: \"Hide this page from search engines\",\n defaultValue: false,\n admin: {\n description:\n \"Search engines will not list this page and the sitemap will leave it out. Anyone with the link can still open it.\",\n },\n};\n","/** The length a search result shows of a description before it is cut. */\nexport const DESCRIPTION_LENGTH = 155;\n\n/**\n * Prose cut to `max` characters at a word, with an ellipsis, so a generated\n * description does not end mid-syllable. Text that fits is returned trimmed\n * and whole. A first word longer than `max` is cut mid-word, since there is\n * no boundary to cut at.\n */\nexport function truncateAtWord(text: string, max = DESCRIPTION_LENGTH): string {\n const trimmed = text.trim();\n if (trimmed.length <= max) return trimmed;\n const cut = trimmed.slice(0, max);\n const lastSpace = cut.lastIndexOf(\" \");\n return `${lastSpace > 0 ? cut.slice(0, lastSpace) : cut}…`;\n}\n","import { seoPlugin as payloadSeoPlugin } from \"@payloadcms/plugin-seo\";\nimport type {\n GenerateDescription,\n GenerateImage,\n GenerateTitle,\n GenerateURL,\n} from \"@payloadcms/plugin-seo/types\";\nimport type { CollectionSlug, Plugin, UploadCollectionSlug } from \"payload\";\n\nimport { noIndexField } from \"./fields\";\nimport { truncateAtWord } from \"./text\";\nimport { documentTitle } from \"./title\";\nimport type { MediaId } from \"./types\";\n\n/**\n * What the plugin reads off a document: its title, for the generated meta\n * title. The site's generated `Page` is assignable to this; the `urlFor`,\n * `describeFrom` and `imageFor` callbacks read the rest, typed as the site\n * chooses through `TDoc`.\n */\nexport interface SeoDoc {\n title?: string | null;\n}\n\nexport interface SeoPluginOptions<TDoc extends SeoDoc = SeoDoc> {\n /** Appended to every generated title: `<page title> | <siteName>`. */\n siteName: string;\n /**\n * The absolute public URL of a document from the form's data, for the\n * search-result preview and the Generate button beside it. `undefined`\n * when the document has no address yet (a slug not typed), which leaves\n * the preview empty rather than pointing at the home page.\n */\n urlFor: (doc: TDoc) => string | undefined;\n /**\n * Prose to cut a generated description from, the first plain-text field a\n * page always has near the top (a hero's body, say). Cut at a word near\n * 155 characters. Without it the Generate button fills in nothing.\n */\n describeFrom?: (doc: TDoc) => string | null | undefined;\n /**\n * An image already on the page, for the Meta Image Generate button, so an\n * editor need not upload a second copy of the hero picture. `firstImageIn`\n * walks block rows for one. The upload chooser stays, so any other media\n * row can still be picked.\n */\n imageFor?: (doc: TDoc) => MediaId | null | undefined;\n /** Collections that get the SEO tab. Default `['pages']`. */\n collections?: CollectionSlug[];\n /** The upload collection the meta image comes from. Default `'media'`. */\n uploadsCollection?: UploadCollectionSlug;\n}\n\n/**\n * `@payloadcms/plugin-seo` configured the Bison Lab way: an SEO tab beside a\n * Content tab (never below the block editor), holding the overview with its\n * character counts, meta title, description and image with Generate buttons,\n * the search-result preview, and the `noIndex` switch.\n *\n * Every string the tab generates comes from the options, so a site spells\n * its name and its URL scheme once. Pin `@payloadcms/plugin-seo` to the same\n * version as `payload` in the site; the plugin's admin components are\n * resolved from the site's import map, so run `payload generate:importmap`\n * after adding it.\n */\nexport function seoPlugin<TDoc extends SeoDoc = SeoDoc>({\n siteName,\n urlFor,\n describeFrom,\n imageFor,\n collections = [\"pages\"],\n uploadsCollection = \"media\",\n}: SeoPluginOptions<TDoc>): Plugin {\n const generateTitle: GenerateTitle<TDoc> = ({ doc }) =>\n doc?.title ? documentTitle(siteName, doc.title) : \"\";\n const generateDescription: GenerateDescription<TDoc> = ({ doc }) =>\n truncateAtWord(describeFrom?.(doc) ?? \"\");\n const generateURL: GenerateURL<TDoc> = ({ doc }) => urlFor(doc) ?? \"\";\n // Only when the site can name one: the button appears with the function.\n const generateImage: GenerateImage<TDoc> | undefined = imageFor\n ? ({ doc }) => imageFor(doc) ?? \"\"\n : undefined;\n\n return payloadSeoPlugin({\n collections,\n uploadsCollection,\n tabbedUI: true,\n generateTitle,\n generateDescription,\n generateURL,\n ...(generateImage ? { generateImage } : {}),\n fields: ({ defaultFields }) => [...defaultFields, noIndexField],\n });\n}\n\n/**\n * The first image among some block rows, as a media id, for `imageFor`. Each\n * row is checked for `field` holding either a bare id or a populated upload\n * document; rows without one are skipped. Pass the page's hero and layout\n * together (`[...doc.hero, ...doc.layout]`) to search in reading order.\n */\nexport function firstImageIn(\n blocks: unknown,\n field = \"image\",\n): MediaId | undefined {\n if (!Array.isArray(blocks)) return undefined;\n for (const block of blocks) {\n if (typeof block !== \"object\" || block === null) continue;\n const value = (block as Record<string, unknown>)[field];\n const id =\n typeof value === \"object\" && value !== null && \"id\" in value\n ? (value as { id: unknown }).id\n : value;\n if (typeof id === \"number\" || (typeof id === \"string\" && id !== \"\"))\n return id;\n }\n return undefined;\n}\n","/**\n * Named choices the Appearance child shows. Values stay the token presets;\n * labels match the mock workflow (Soft → pill, Gentle → minimal, Balanced\n * → default, Follow device → system). No rem, ms, or CSS variable tables.\n */\nexport const APPEARANCE_CHOICES = {\n defaultTheme: {\n light: { label: \"Light\", hint: \"Always opens in light mode\" },\n dark: { label: \"Dark\", hint: \"Always opens in dark mode\" },\n system: { label: \"Follow device\", hint: \"Matches the visitor’s system setting\" },\n },\n radius: {\n sharp: { label: \"Sharp\", hint: \"Crisp and structured\" },\n subtle: { label: \"Subtle\", hint: \"Clean with a little softness\" },\n rounded: { label: \"Rounded\", hint: \"Friendly and contemporary\" },\n pill: { label: \"Soft\", hint: \"Very rounded and expressive\" },\n },\n shadow: {\n flat: { label: \"Flat\", hint: \"Minimal visual depth\" },\n subtle: { label: \"Subtle\", hint: \"Light separation between surfaces\" },\n elevated: { label: \"Elevated\", hint: \"More noticeable layering and depth\" },\n },\n motion: {\n snappy: { label: \"Quick\", hint: \"Fast and responsive\" },\n smooth: { label: \"Smooth\", hint: \"Balanced and natural\" },\n minimal: { label: \"Gentle\", hint: \"Slower and more relaxed\" },\n },\n density: {\n compact: { label: \"Compact\", hint: \"Fits more content on screen\" },\n default: { label: \"Balanced\", hint: \"Comfortable for most sites\" },\n spacious: { label: \"Spacious\", hint: \"More breathing room and larger controls\" },\n },\n} as const;\n\nexport const APPEARANCE_FAMILY_COPY: Record<\n keyof typeof APPEARANCE_CHOICES,\n { title: string; hint: string }\n> = {\n defaultTheme: {\n title: \"Default theme\",\n hint: \"Choose how the public site opens for visitors.\",\n },\n radius: {\n title: \"Corner style\",\n hint: \"Controls the shape of buttons, inputs, cards, and panels across the site.\",\n },\n shadow: {\n title: \"Depth\",\n hint: \"Choose how much cards and floating elements stand out from the page.\",\n },\n motion: {\n title: \"Motion\",\n hint: \"Controls how quickly menus, cards, and interface transitions move.\",\n },\n density: {\n title: \"Spacing\",\n hint: \"Choose how compact or roomy forms, buttons, cards, and page sections feel.\",\n },\n};\n\nexport const FONT_ROLE_COPY = {\n heading: {\n label: \"Heading font\",\n hint: \"Used for page titles, section headings, and cards\",\n },\n body: {\n label: \"Body font\",\n hint: \"Used for paragraphs, navigation, forms, and buttons\",\n },\n} as const;\n\nexport const SYSTEM_COLOR_COPY: Record<\n \"primary\" | \"secondary\" | \"accent\" | \"highlight\" | \"success\" | \"destructive\",\n { label: string; hint: string; group: \"brand\" | \"status\" }\n> = {\n primary: { label: \"Primary\", hint: \"Primary actions and key links\", group: \"brand\" },\n secondary: { label: \"Secondary\", hint: \"Supporting accents and eyebrows\", group: \"brand\" },\n accent: { label: \"Accent\", hint: \"Dark brand surfaces\", group: \"brand\" },\n highlight: { label: \"Highlight\", hint: \"Promotional and emphasis fills\", group: \"brand\" },\n success: {\n label: \"Success\",\n hint: \"Success messages and confirmations\",\n group: \"status\",\n },\n destructive: {\n label: \"Destructive\",\n hint: \"Errors, dangerous actions, and destructive confirmations\",\n group: \"status\",\n },\n};\n","import { THEME_SLUG, type ThemeColorDoc, type ThemeDoc } from \"./types\";\n\nexport const THEME_CHILDREN = [\"colors\", \"typography\", \"appearance\", \"identity\"] as const;\n\nexport type ThemeChild = (typeof THEME_CHILDREN)[number];\n\n/**\n * Publishing a child writes that slice only. Other responsibilities stay\n * as they already are on the stored document. Unsaved form state on a\n * different child never goes live. A first save (no stored row) still\n * writes only the named child; `getPublishedTheme` fills the rest from\n * the seed.\n */\nexport function publishThemeChild(stored: ThemeDoc, incoming: ThemeDoc, child: ThemeChild): ThemeDoc {\n if (child === \"colors\") return { ...stored, colors: incoming.colors };\n if (child === \"typography\") return { ...stored, typography: incoming.typography };\n if (child === \"appearance\") return { ...stored, appearance: incoming.appearance };\n return { ...stored, logo: incoming.logo, favicon: incoming.favicon, logoMark: incoming.logoMark };\n}\n\nexport function isThemeChild(value: unknown): value is ThemeChild {\n return typeof value === \"string\" && (THEME_CHILDREN as readonly string[]).includes(value);\n}\n\nexport function sliceFromTheme(theme: ThemeDoc, child: ThemeChild): ThemeDoc {\n if (child === \"colors\") return { colors: theme.colors };\n if (child === \"typography\") return { typography: theme.typography };\n if (child === \"appearance\") return { appearance: theme.appearance };\n return { logo: theme.logo, favicon: theme.favicon, logoMark: theme.logoMark };\n}\n\nfunction colorHex(color: ThemeColorDoc | string | null | undefined): string | undefined {\n if (typeof color === \"string\") return color;\n return color?.hex ?? undefined;\n}\n\n/**\n * A Theme page is a real Global. Hydrate from the store only when this\n * page has never been saved — otherwise afterRead would overwrite the form.\n */\nexport function hasThemeSlice(doc: ThemeDoc | null | undefined, child: ThemeChild): boolean {\n if (!doc) return false;\n if (child === \"colors\") return Boolean(colorHex(doc.colors?.brand?.primary));\n if (child === \"typography\") return Boolean(doc.typography?.body);\n if (child === \"appearance\") return Boolean(doc.appearance?.radius);\n return Boolean(doc.logo || doc.favicon || doc.logoMark);\n}\n\nexport interface ThemeStorePayload {\n findGlobal: (args: {\n slug: string;\n draft?: boolean;\n depth?: number;\n overrideAccess?: boolean;\n }) => Promise<ThemeDoc | null | undefined>;\n updateGlobal: (args: { slug: string; data: ThemeDoc; draft?: boolean }) => Promise<unknown>;\n}\n\n/**\n * Save on a Theme page writes that slice onto the hidden `theme` row.\n * Other children stay as stored.\n */\nexport async function persistThemeChild(\n payload: ThemeStorePayload,\n incoming: ThemeDoc,\n child: ThemeChild,\n): Promise<ThemeDoc> {\n const stored =\n (await payload.findGlobal({\n slug: THEME_SLUG,\n draft: false,\n overrideAccess: true,\n })) ?? {};\n const next = publishThemeChild(stored, incoming, child);\n await payload.updateGlobal({ slug: THEME_SLUG, data: next, draft: false });\n return next;\n}\n","import { catalog, type FontEntry } from \"@bison-lab/fonts\";\nimport { DESTRUCTIVE_SCALE_HEX, presetHints, SHADE_STEPS } from \"@bison-lab/tokens\";\n\n/** Same value as tokens `SUCCESS_SCALE_HEX`. Inlined so an older tokens peer still boots. */\nconst LIBRARY_SUCCESS_HEX = \"#22c55e\";\nimport type { Field, GlobalConfig, SelectField } from \"payload\";\n\nimport {\n SAME_AS_BODY,\n THEME_APPEARANCE_FIELD,\n THEME_COLOR_SCALE_FIELD,\n THEME_FONT_FIELD,\n THEME_GREY_SCALE_FIELD,\n THEME_LIBRARY_FIELD,\n THEME_PAIRING_FIELD,\n THEME_DOCUMENT_CONTROLS,\n THEME_SECTION_HEADING,\n headingSelectValue,\n} from \"./fields\";\nimport { APPEARANCE_CHOICES } from \"./appearance-labels\";\nimport { themeConfigFromDoc, validateSourceIncluded, validateThemeHex } from \"./map\";\nimport { hasThemeSlice, persistThemeChild, sliceFromTheme, type ThemeChild } from \"./publish\";\nimport { READABILITY_TARGET } from \"./readability\";\nimport {\n THEME_APPEARANCE_SLUG,\n THEME_COLORS_SLUG,\n THEME_IDENTITY_SLUG,\n THEME_SLUG,\n THEME_TYPOGRAPHY_SLUG,\n type CreateThemeOptions,\n type ThemeDoc,\n} from \"./types\";\n\nfunction requireAccess(options: CreateThemeOptions): CreateThemeOptions[\"access\"] {\n const read = options.access?.read;\n const update = options.access?.update;\n if (!read || !update) {\n throw new Error(\n \"createTheme requires access.read and access.update. Pass the site's predicates; canManageBrand is the intended update predicate until BIS-43 moves src/platform here.\",\n );\n }\n return { read, update };\n}\n\nfunction requireDestructive(options: CreateThemeOptions): string {\n return options.destructive ?? options.seed.brandDestructive ?? DESTRUCTIVE_SCALE_HEX;\n}\n\nfunction namedSelect<K extends string>(\n name: string,\n label: string,\n choices: Record<K, { label: string; hint: string }>,\n defaultValue: K,\n field?: string,\n): SelectField {\n return {\n name,\n type: \"select\",\n label,\n required: true,\n defaultValue,\n options: (Object.entries(choices) as [K, { label: string; hint: string }][]).map(\n ([value, { label: optionLabel, hint }]) => ({\n label: `${optionLabel} — ${hint}`,\n value,\n }),\n ),\n ...(field ? { admin: { components: { Field: field } } } : {}),\n };\n}\n\nconst STEP_OPTIONS = SHADE_STEPS.map((step) => ({ label: String(step), value: String(step) }));\n\nfunction colorScaleFields(hexDefault?: string): Field[] {\n const hidden = { hidden: true } as const;\n return [\n {\n name: \"hex\",\n type: \"text\",\n label: \"Hex\",\n required: true,\n ...(hexDefault ? { defaultValue: hexDefault } : {}),\n validate: validateThemeHex,\n admin: hidden,\n },\n {\n name: \"sourceStep\",\n type: \"select\",\n label: \"Source step\",\n required: true,\n defaultValue: \"500\",\n options: STEP_OPTIONS,\n admin: hidden,\n },\n {\n name: \"scale\",\n type: \"json\",\n label: \"Scale\",\n admin: hidden,\n },\n {\n name: \"stale\",\n type: \"checkbox\",\n label: \"Stale\",\n defaultValue: false,\n admin: hidden,\n },\n {\n name: \"include\",\n type: \"select\",\n label: \"Include\",\n required: true,\n defaultValue: \"all\",\n options: [\n { label: \"Include all\", value: \"all\" },\n { label: \"Source only\", value: \"source\" },\n { label: \"Custom\", value: \"custom\" },\n ],\n admin: hidden,\n },\n {\n name: \"includedSteps\",\n type: \"select\",\n label: \"Included steps\",\n hasMany: true,\n options: STEP_OPTIONS,\n validate: validateSourceIncluded,\n admin: hidden,\n },\n ];\n}\n\nfunction systemColorGroup(name: string, label: string, hexDefault: string): Field {\n return {\n name,\n type: \"group\",\n label,\n fields: colorScaleFields(hexDefault),\n admin: { components: { Field: THEME_COLOR_SCALE_FIELD }, hideGutter: true },\n };\n}\n\nfunction fontField(\n name: string,\n label: string,\n defaultValue: string,\n fonts: readonly FontEntry[],\n fontsBaseUrl: string,\n sameAsBody: boolean,\n): SelectField {\n const options = fonts.map((font) => ({ label: font.family, value: font.id }));\n return {\n name,\n type: \"select\",\n label,\n required: !sameAsBody,\n defaultValue,\n options: sameAsBody ? [{ label: \"Same as body\", value: SAME_AS_BODY }, ...options] : options,\n admin: {\n components: { Field: THEME_FONT_FIELD },\n custom: { fontsBaseUrl, ids: fonts.map((font) => font.id), sameAsBody },\n },\n };\n}\n\nfunction colorsFields(seed: CreateThemeOptions[\"seed\"], destructive: string): Field[] {\n return [\n {\n name: \"colors\",\n type: \"group\",\n label: false,\n admin: { hideGutter: true, width: \"100%\" },\n fields: [\n {\n name: \"brand\",\n type: \"group\",\n label: false,\n admin: { hideGutter: true, width: \"100%\" },\n fields: [\n {\n name: \"themeColorsHeading\",\n type: \"ui\",\n admin: {\n components: { Field: THEME_SECTION_HEADING },\n custom: {\n title: \"Brand colors\",\n hint: \"These colors define the visual identity of the site. Adjust them freely to match the brand.\",\n },\n },\n },\n systemColorGroup(\"primary\", \"Primary\", seed.brandPrimary),\n systemColorGroup(\"secondary\", \"Secondary\", seed.brandSecondary),\n systemColorGroup(\"accent\", \"Accent\", seed.brandAccent),\n systemColorGroup(\"highlight\", \"Highlight\", seed.brandHighlight),\n {\n name: \"libraryPlacement\",\n type: \"ui\",\n admin: {\n components: { Field: THEME_LIBRARY_FIELD },\n custom: { dataPath: \"colors.library\" },\n },\n },\n {\n name: \"greyPlacement\",\n type: \"ui\",\n admin: {\n components: { Field: THEME_GREY_SCALE_FIELD },\n custom: { dataPath: \"colors.greyScale\" },\n },\n },\n {\n name: \"statusColorsHeading\",\n type: \"ui\",\n admin: {\n components: { Field: THEME_SECTION_HEADING },\n custom: {\n title: \"Status colors\",\n hint: \"These colors communicate meaning across the site. You can adjust them to fit your brand, but we'll help keep them recognizable as success and error states.\",\n },\n },\n },\n systemColorGroup(\"success\", \"Success\", seed.brandSuccess || LIBRARY_SUCCESS_HEX),\n systemColorGroup(\"destructive\", \"Destructive\", destructive),\n ],\n },\n {\n ...namedSelect(\"greyScale\", \"Gray family\", presetHints.greyScale, seed.greyScale, THEME_GREY_SCALE_FIELD),\n admin: { hidden: true, components: { Field: THEME_GREY_SCALE_FIELD } },\n },\n {\n name: \"library\",\n type: \"array\",\n label: \"Additional colors\",\n labels: { singular: \"Color\", plural: \"Colors\" },\n fields: [\n { name: \"key\", type: \"text\", label: \"Key\", required: true, admin: { hidden: true } },\n { name: \"label\", type: \"text\", label: \"Name\", required: true, admin: { hidden: true } },\n ...colorScaleFields(),\n ],\n admin: { hidden: true, components: { Field: THEME_LIBRARY_FIELD }, width: \"100%\" },\n },\n ],\n },\n ];\n}\n\nfunction typographyFields(\n seed: CreateThemeOptions[\"seed\"],\n fonts: readonly FontEntry[],\n fontsBaseUrl: string,\n): Field[] {\n return [\n {\n name: \"typography\",\n type: \"group\",\n label: \"Typography\",\n fields: [\n fontField(\n \"heading\",\n \"Heading font\",\n headingSelectValue(seed.fontHeading),\n fonts,\n fontsBaseUrl,\n true,\n ),\n fontField(\"body\", \"Body font\", seed.fontBody, fonts, fontsBaseUrl, false),\n {\n name: \"pairing\",\n type: \"ui\",\n admin: { components: { Field: THEME_PAIRING_FIELD }, custom: { fontsBaseUrl } },\n },\n ],\n },\n ];\n}\n\nfunction appearanceFields(seed: CreateThemeOptions[\"seed\"]): Field[] {\n return [\n {\n name: \"appearance\",\n type: \"group\",\n label: \"Appearance\",\n fields: [\n namedSelect(\"defaultTheme\", \"Default theme\", APPEARANCE_CHOICES.defaultTheme, seed.defaultTheme),\n namedSelect(\"radius\", \"Corner style\", APPEARANCE_CHOICES.radius, seed.radius),\n namedSelect(\"shadow\", \"Depth\", APPEARANCE_CHOICES.shadow, seed.shadow),\n namedSelect(\"motion\", \"Motion\", APPEARANCE_CHOICES.motion, seed.motion),\n namedSelect(\"density\", \"Spacing\", APPEARANCE_CHOICES.density, seed.density),\n ],\n admin: { components: { Field: THEME_APPEARANCE_FIELD } },\n },\n ];\n}\n\nfunction identityFields(collection: string): Field[] {\n return [\n {\n name: \"logo\",\n type: \"upload\",\n relationTo: collection,\n label: \"Logo\",\n admin: {\n description: \"The full wordmark used in the header and footer.\",\n },\n },\n {\n name: \"favicon\",\n type: \"upload\",\n relationTo: collection,\n label: \"Favicon\",\n admin: {\n description: \"The small icon in the browser tab. PNG or SVG.\",\n },\n },\n {\n name: \"logoMark\",\n type: \"upload\",\n relationTo: collection,\n label: \"Mobile menu logo\",\n admin: {\n description:\n \"Optional. Used when the full logo is too wide for the mobile menu — typically the mark.\",\n },\n },\n ];\n}\n\nfunction pageHooks(child: ThemeChild) {\n return {\n beforeChange: [\n async ({ data, req }: { data: ThemeDoc; req?: { payload?: Parameters<typeof persistThemeChild>[0] } }) => {\n if (req?.payload) await persistThemeChild(req.payload, data, child);\n return data;\n },\n ],\n afterRead: [\n async ({\n doc,\n req,\n }: {\n doc: ThemeDoc;\n req?: {\n payload?: {\n findGlobal: (args: {\n slug: string;\n draft?: boolean;\n depth?: number;\n overrideAccess?: boolean;\n }) => Promise<ThemeDoc | null | undefined>;\n };\n };\n }) => {\n if (hasThemeSlice(doc, child)) return doc;\n const theme = await req?.payload?.findGlobal({\n slug: THEME_SLUG,\n draft: false,\n depth: child === \"identity\" ? 1 : 0,\n overrideAccess: true,\n });\n if (!theme) return doc;\n return { ...doc, ...sliceFromTheme(theme, child) };\n },\n ],\n };\n}\n\n/**\n * Standard Payload Globals in an `admin.group: \"Theme\"` nav section —\n * the same pattern as Content and Settings. Each page is its own Global\n * (Save, fields, access). A hidden `theme` store is the published read\n * model `getPublishedTheme` already knows. Locking is off: these are\n * settings forms, not collaborative documents. No draft mode, no preview\n * pane.\n */\nexport function createTheme(options: CreateThemeOptions): GlobalConfig[] {\n const access = requireAccess(options);\n const destructive = requireDestructive(options);\n const {\n seed,\n fonts = catalog,\n fontsBaseUrl = \"/fonts\",\n contrastTarget = READABILITY_TARGET,\n logo,\n identity,\n onPublish,\n } = options;\n\n const adminCustom = { contrastTarget, fontsBaseUrl, identityFallback: identity?.fallback };\n const colorFields = colorsFields(seed, destructive);\n const typeFields = typographyFields(seed, fonts, fontsBaseUrl);\n const lookFields = appearanceFields(seed);\n const markFields = logo ? identityFields(logo.collection) : [];\n\n const store: GlobalConfig = {\n slug: THEME_SLUG,\n label: \"Theme\",\n lockDocuments: false,\n admin: {\n hidden: true,\n hideAPIURL: true,\n custom: adminCustom,\n },\n access: {\n read: access.read,\n update: access.update,\n },\n fields: [...colorFields, ...typeFields, ...lookFields, ...markFields],\n hooks: {\n afterChange: [\n async ({ doc }) => {\n if (!onPublish) return;\n const themeDoc = doc as ThemeDoc;\n await onPublish(themeConfigFromDoc(themeDoc, seed), themeDoc);\n },\n ],\n },\n };\n\n function page(\n slug: string,\n label: string,\n child: ThemeChild,\n fields: Field[],\n preview: boolean,\n ): GlobalConfig {\n return {\n slug,\n label,\n lockDocuments: false,\n admin: {\n group: \"Theme\",\n hideAPIURL: true,\n custom: adminCustom,\n components: preview\n ? { elements: { beforeDocumentControls: [THEME_DOCUMENT_CONTROLS] } }\n : undefined,\n },\n access: {\n read: access.read,\n update: access.update,\n },\n fields,\n hooks: pageHooks(child),\n };\n }\n\n return [\n store,\n page(THEME_COLORS_SLUG, \"Colors\", \"colors\", colorFields, true),\n page(THEME_TYPOGRAPHY_SLUG, \"Typography\", \"typography\", typeFields, true),\n page(THEME_APPEARANCE_SLUG, \"Appearance\", \"appearance\", lookFields, true),\n ...(logo ? [page(THEME_IDENTITY_SLUG, \"Identity\", \"identity\", markFields, false)] : []),\n ];\n}\n","const DANGEROUS_TAGS = [\"script\", \"foreignObject\", \"iframe\", \"embed\", \"object\"] as const;\n\n/**\n * Strips the SVG features a lockup does not need and an attacker would\n * use: script, foreignObject, iframe/embed/object, event handlers,\n * javascript: / data:text/html URLs, and xml-stylesheet. Internal `#`\n * refs and ordinary drawing elements stay.\n */\nexport function sanitizeSvg(source: string): string {\n if (!/<svg[\\s>]/i.test(source)) {\n throw new Error(\"Not an SVG\");\n }\n\n let out = source.replace(/<\\?xml-stylesheet[\\s\\S]*?\\?>/gi, \"\");\n\n for (const tag of DANGEROUS_TAGS) {\n out = out.replace(new RegExp(`<${tag}\\\\b[^>]*>[\\\\s\\\\S]*?<\\\\/${tag}\\\\s*>`, \"gi\"), \"\");\n out = out.replace(new RegExp(`<${tag}\\\\b[^>]*\\\\/>`, \"gi\"), \"\");\n }\n\n out = out.replace(/\\s+on[a-z][a-z0-9-]*\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, \"\");\n out = out.replace(\n /((?:href|src|xlink:href)\\s*=\\s*)([\"'])\\s*(?:javascript:|data:text\\/html)[^\"']*\\2/gi,\n \"$1$2$2\",\n );\n\n return out;\n}\n\nexport function isSvgUpload(file: { mimetype?: string; name?: string; data?: Buffer }): boolean {\n if (file.mimetype === \"image/svg+xml\") return true;\n if (typeof file.name === \"string\" && file.name.toLowerCase().endsWith(\".svg\")) return true;\n return false;\n}\n","import type { Access, CollectionConfig } from \"payload\";\n\nimport { isSvgUpload, sanitizeSvg } from \"./sanitize\";\n\nexport const BRAND_ASSETS_SLUG = \"brand-assets\";\n\n/** SVG preferred; PNG allowed. ICO is for the favicon slot. */\nexport const BRAND_ASSETS_MIME_TYPES = [\n \"image/svg+xml\",\n \"image/png\",\n \"image/x-icon\",\n \"image/vnd.microsoft.icon\",\n] as const;\n\nexport interface CreateBrandAssetsOptions {\n /**\n * Required, no default — same shape as `createTheme`. `read` is typically\n * public so the live site can load the lockup; `update` is `canManageBrand`\n * and covers create, update, delete, and version history. Predicates live\n * in each site's `src/platform` until BIS-43.\n */\n access: { read: Access; update: Access };\n /** Default `brand-assets`. Pass the same slug to `createTheme({ logo })`. */\n slug?: string;\n}\n\nfunction requireAccess(options: CreateBrandAssetsOptions): CreateBrandAssetsOptions[\"access\"] {\n const read = options.access?.read;\n const update = options.access?.update;\n if (!read || !update) {\n throw new Error(\n \"createBrandAssets requires access.read and access.update. Pass the site's predicates; canManageBrand is the intended update predicate until BIS-43 moves src/platform here.\",\n );\n }\n return { read, update };\n}\n\n/**\n * Locked cupboard for lockup and mark. Not ordinary Media — only a brand\n * manager writes it. Theme's `logo` / `logoMark` point here when the site\n * passes this slug as `logo.collection`.\n */\nexport function createBrandAssets(options: CreateBrandAssetsOptions): CollectionConfig {\n const access = requireAccess(options);\n const slug = options.slug ?? BRAND_ASSETS_SLUG;\n\n return {\n slug,\n labels: { singular: \"Brand asset\", plural: \"Brand assets\" },\n admin: {\n group: \"Settings\",\n useAsTitle: \"label\",\n description: \"Logo, favicon, and mobile-menu mark. SVG preferred; PNG and ICO allowed.\",\n },\n upload: {\n mimeTypes: [...BRAND_ASSETS_MIME_TYPES],\n crop: false,\n focalPoint: false,\n },\n versions: true,\n access: {\n read: access.read,\n create: access.update,\n update: access.update,\n delete: access.update,\n readVersions: access.update,\n },\n fields: [\n { name: \"label\", type: \"text\", label: \"Label\" },\n { name: \"notes\", type: \"textarea\", label: \"Usage notes\" },\n { name: \"alt\", type: \"text\", label: \"Alt text\", required: true },\n ],\n hooks: {\n beforeOperation: [\n ({ req, operation }) => {\n if (operation !== \"create\" && operation !== \"update\") return;\n const file = req.file;\n if (!file || !isSvgUpload(file)) return;\n file.data = Buffer.from(sanitizeSvg(file.data.toString(\"utf8\")), \"utf8\");\n },\n ],\n },\n };\n}\n","import type { ThemeConfig } from \"@bison-lab/tokens\";\n\nimport { docFromConfig } from \"./map\";\nimport { THEME_SLUG } from \"./types\";\nimport type { ThemeDoc } from \"./types\";\n\nexport interface SeedThemePayload {\n updateGlobal: (args: {\n slug: string;\n data: ThemeDoc;\n draft?: boolean;\n }) => Promise<unknown>;\n}\n\n/**\n * Writes a published Theme from the seed. For a site's migration `up()`,\n * so the row exists on deploy and renders what `bison-theme.css` rendered.\n */\nexport async function seedTheme(payload: SeedThemePayload, seed: ThemeConfig): Promise<unknown> {\n return payload.updateGlobal({\n slug: THEME_SLUG,\n data: docFromConfig(seed),\n draft: false,\n });\n}\n","/**\n * Device sizes the Theme (and, later, Pages) live-preview toolbar offers.\n * One list so the panes match.\n */\nexport const THEME_PREVIEW_BREAKPOINTS = [\n { label: \"Mobile\", name: \"mobile\", width: 375, height: 667 },\n { label: \"Tablet\", name: \"tablet\", width: 768, height: 1024 },\n { label: \"Desktop\", name: \"desktop\", width: 1440, height: 900 },\n] as const;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["payloadSeoPlugin","requireAccess"],"sources":["../src/seo/fields.ts","../src/seo/text.ts","../src/seo/plugin.ts","../src/theme/appearance-labels.ts","../src/theme/publish.ts","../src/theme/global.ts","../src/brand-assets/sanitize.ts","../src/brand-assets/collection.ts","../src/theme/seed.ts","../src/theme/preview.ts","../src/roles/types.ts","../src/roles/matrix.ts","../src/roles/access.ts","../src/roles/fields.ts","../src/roles/global.ts","../src/roles/seed.ts","../src/fields/slug.ts","../src/collections/pages.ts","../src/collections/users.ts","../src/collections/media.ts","../src/plugins/admin-only-api-tab.ts"],"sourcesContent":["import type { CheckboxField } from \"payload\";\n\n/**\n * The index switch, last in the SEO tab. Off by default: a page is public\n * unless an editor says otherwise. `pageMetadata` turns it into\n * `noindex, nofollow`; a site's sitemap should filter on it too.\n */\nexport const noIndexField: CheckboxField = {\n name: \"noIndex\",\n type: \"checkbox\",\n label: \"Hide this page from search engines\",\n defaultValue: false,\n admin: {\n description:\n \"Search engines will not list this page and the sitemap will leave it out. Anyone with the link can still open it.\",\n },\n};\n","/** The length a search result shows of a description before it is cut. */\nexport const DESCRIPTION_LENGTH = 155;\n\n/**\n * Prose cut to `max` characters at a word, with an ellipsis, so a generated\n * description does not end mid-syllable. Text that fits is returned trimmed\n * and whole. A first word longer than `max` is cut mid-word, since there is\n * no boundary to cut at.\n */\nexport function truncateAtWord(text: string, max = DESCRIPTION_LENGTH): string {\n const trimmed = text.trim();\n if (trimmed.length <= max) return trimmed;\n const cut = trimmed.slice(0, max);\n const lastSpace = cut.lastIndexOf(\" \");\n return `${lastSpace > 0 ? cut.slice(0, lastSpace) : cut}…`;\n}\n","import { seoPlugin as payloadSeoPlugin } from \"@payloadcms/plugin-seo\";\nimport type {\n GenerateDescription,\n GenerateImage,\n GenerateTitle,\n GenerateURL,\n} from \"@payloadcms/plugin-seo/types\";\nimport type { CollectionSlug, Plugin, UploadCollectionSlug } from \"payload\";\n\nimport { noIndexField } from \"./fields\";\nimport { truncateAtWord } from \"./text\";\nimport { documentTitle } from \"./title\";\nimport type { MediaId } from \"./types\";\n\n/**\n * What the plugin reads off a document: its title, for the generated meta\n * title. The site's generated `Page` is assignable to this; the `urlFor`,\n * `describeFrom` and `imageFor` callbacks read the rest, typed as the site\n * chooses through `TDoc`.\n */\nexport interface SeoDoc {\n title?: string | null;\n}\n\nexport interface SeoPluginOptions<TDoc extends SeoDoc = SeoDoc> {\n /** Appended to every generated title: `<page title> | <siteName>`. */\n siteName: string;\n /**\n * The absolute public URL of a document from the form's data, for the\n * search-result preview and the Generate button beside it. `undefined`\n * when the document has no address yet (a slug not typed), which leaves\n * the preview empty rather than pointing at the home page.\n */\n urlFor: (doc: TDoc) => string | undefined;\n /**\n * Prose to cut a generated description from, the first plain-text field a\n * page always has near the top (a hero's body, say). Cut at a word near\n * 155 characters. Without it the Generate button fills in nothing.\n */\n describeFrom?: (doc: TDoc) => string | null | undefined;\n /**\n * An image already on the page, for the Meta Image Generate button, so an\n * editor need not upload a second copy of the hero picture. `firstImageIn`\n * walks block rows for one. The upload chooser stays, so any other media\n * row can still be picked.\n */\n imageFor?: (doc: TDoc) => MediaId | null | undefined;\n /** Collections that get the SEO tab. Default `['pages']`. */\n collections?: CollectionSlug[];\n /** The upload collection the meta image comes from. Default `'media'`. */\n uploadsCollection?: UploadCollectionSlug;\n}\n\n/**\n * `@payloadcms/plugin-seo` configured the Bison Lab way: an SEO tab beside a\n * Content tab (never below the block editor), holding the overview with its\n * character counts, meta title, description and image with Generate buttons,\n * the search-result preview, and the `noIndex` switch.\n *\n * Every string the tab generates comes from the options, so a site spells\n * its name and its URL scheme once. Pin `@payloadcms/plugin-seo` to the same\n * version as `payload` in the site; the plugin's admin components are\n * resolved from the site's import map, so run `payload generate:importmap`\n * after adding it.\n */\nexport function seoPlugin<TDoc extends SeoDoc = SeoDoc>({\n siteName,\n urlFor,\n describeFrom,\n imageFor,\n collections = [\"pages\"],\n uploadsCollection = \"media\",\n}: SeoPluginOptions<TDoc>): Plugin {\n const generateTitle: GenerateTitle<TDoc> = ({ doc }) =>\n doc?.title ? documentTitle(siteName, doc.title) : \"\";\n const generateDescription: GenerateDescription<TDoc> = ({ doc }) =>\n truncateAtWord(describeFrom?.(doc) ?? \"\");\n const generateURL: GenerateURL<TDoc> = ({ doc }) => urlFor(doc) ?? \"\";\n // Only when the site can name one: the button appears with the function.\n const generateImage: GenerateImage<TDoc> | undefined = imageFor\n ? ({ doc }) => imageFor(doc) ?? \"\"\n : undefined;\n\n return payloadSeoPlugin({\n collections,\n uploadsCollection,\n tabbedUI: true,\n generateTitle,\n generateDescription,\n generateURL,\n ...(generateImage ? { generateImage } : {}),\n fields: ({ defaultFields }) => [...defaultFields, noIndexField],\n });\n}\n\n/**\n * The first image among some block rows, as a media id, for `imageFor`. Each\n * row is checked for `field` holding either a bare id or a populated upload\n * document; rows without one are skipped. Pass the page's hero and layout\n * together (`[...doc.hero, ...doc.layout]`) to search in reading order.\n */\nexport function firstImageIn(\n blocks: unknown,\n field = \"image\",\n): MediaId | undefined {\n if (!Array.isArray(blocks)) return undefined;\n for (const block of blocks) {\n if (typeof block !== \"object\" || block === null) continue;\n const value = (block as Record<string, unknown>)[field];\n const id =\n typeof value === \"object\" && value !== null && \"id\" in value\n ? (value as { id: unknown }).id\n : value;\n if (typeof id === \"number\" || (typeof id === \"string\" && id !== \"\"))\n return id;\n }\n return undefined;\n}\n","/**\n * Named choices the Appearance child shows. Values stay the token presets;\n * labels match the mock workflow (Soft → pill, Gentle → minimal, Balanced\n * → default, Follow device → system). No rem, ms, or CSS variable tables.\n */\nexport const APPEARANCE_CHOICES = {\n defaultTheme: {\n light: { label: \"Light\", hint: \"Always opens in light mode\" },\n dark: { label: \"Dark\", hint: \"Always opens in dark mode\" },\n system: { label: \"Follow device\", hint: \"Matches the visitor’s system setting\" },\n },\n radius: {\n sharp: { label: \"Sharp\", hint: \"Crisp and structured\" },\n subtle: { label: \"Subtle\", hint: \"Clean with a little softness\" },\n rounded: { label: \"Rounded\", hint: \"Friendly and contemporary\" },\n pill: { label: \"Soft\", hint: \"Very rounded and expressive\" },\n },\n shadow: {\n flat: { label: \"Flat\", hint: \"Minimal visual depth\" },\n subtle: { label: \"Subtle\", hint: \"Light separation between surfaces\" },\n elevated: { label: \"Elevated\", hint: \"More noticeable layering and depth\" },\n },\n motion: {\n snappy: { label: \"Quick\", hint: \"Fast and responsive\" },\n smooth: { label: \"Smooth\", hint: \"Balanced and natural\" },\n minimal: { label: \"Gentle\", hint: \"Slower and more relaxed\" },\n },\n density: {\n compact: { label: \"Compact\", hint: \"Fits more content on screen\" },\n default: { label: \"Balanced\", hint: \"Comfortable for most sites\" },\n spacious: { label: \"Spacious\", hint: \"More breathing room and larger controls\" },\n },\n} as const;\n\nexport const APPEARANCE_FAMILY_COPY: Record<\n keyof typeof APPEARANCE_CHOICES,\n { title: string; hint: string }\n> = {\n defaultTheme: {\n title: \"Default theme\",\n hint: \"Choose how the public site opens for visitors.\",\n },\n radius: {\n title: \"Corner style\",\n hint: \"Controls the shape of buttons, inputs, cards, and panels across the site.\",\n },\n shadow: {\n title: \"Depth\",\n hint: \"Choose how much cards and floating elements stand out from the page.\",\n },\n motion: {\n title: \"Motion\",\n hint: \"Controls how quickly menus, cards, and interface transitions move.\",\n },\n density: {\n title: \"Spacing\",\n hint: \"Choose how compact or roomy forms, buttons, cards, and page sections feel.\",\n },\n};\n\nexport const FONT_ROLE_COPY = {\n heading: {\n label: \"Heading font\",\n hint: \"Used for page titles, section headings, and cards\",\n },\n body: {\n label: \"Body font\",\n hint: \"Used for paragraphs, navigation, forms, and buttons\",\n },\n} as const;\n\nexport const SYSTEM_COLOR_COPY: Record<\n \"primary\" | \"secondary\" | \"accent\" | \"highlight\" | \"success\" | \"destructive\",\n { label: string; hint: string; group: \"brand\" | \"status\" }\n> = {\n primary: { label: \"Primary\", hint: \"Primary actions and key links\", group: \"brand\" },\n secondary: { label: \"Secondary\", hint: \"Supporting accents and eyebrows\", group: \"brand\" },\n accent: { label: \"Accent\", hint: \"Dark brand surfaces\", group: \"brand\" },\n highlight: { label: \"Highlight\", hint: \"Promotional and emphasis fills\", group: \"brand\" },\n success: {\n label: \"Success\",\n hint: \"Success messages and confirmations\",\n group: \"status\",\n },\n destructive: {\n label: \"Destructive\",\n hint: \"Errors, dangerous actions, and destructive confirmations\",\n group: \"status\",\n },\n};\n","import { THEME_SLUG, type ThemeColorDoc, type ThemeDoc } from \"./types\";\n\nexport const THEME_CHILDREN = [\"colors\", \"typography\", \"appearance\", \"identity\"] as const;\n\nexport type ThemeChild = (typeof THEME_CHILDREN)[number];\n\n/**\n * Publishing a child writes that slice only. Other responsibilities stay\n * as they already are on the stored document. Unsaved form state on a\n * different child never goes live. A first save (no stored row) still\n * writes only the named child; `getPublishedTheme` fills the rest from\n * the seed.\n */\nexport function publishThemeChild(stored: ThemeDoc, incoming: ThemeDoc, child: ThemeChild): ThemeDoc {\n if (child === \"colors\") return { ...stored, colors: incoming.colors };\n if (child === \"typography\") return { ...stored, typography: incoming.typography };\n if (child === \"appearance\") return { ...stored, appearance: incoming.appearance };\n return { ...stored, logo: incoming.logo, favicon: incoming.favicon, logoMark: incoming.logoMark };\n}\n\nexport function isThemeChild(value: unknown): value is ThemeChild {\n return typeof value === \"string\" && (THEME_CHILDREN as readonly string[]).includes(value);\n}\n\nexport function sliceFromTheme(theme: ThemeDoc, child: ThemeChild): ThemeDoc {\n if (child === \"colors\") return { colors: theme.colors };\n if (child === \"typography\") return { typography: theme.typography };\n if (child === \"appearance\") return { appearance: theme.appearance };\n return { logo: theme.logo, favicon: theme.favicon, logoMark: theme.logoMark };\n}\n\nfunction colorHex(color: ThemeColorDoc | string | null | undefined): string | undefined {\n if (typeof color === \"string\") return color;\n return color?.hex ?? undefined;\n}\n\n/**\n * A Theme page is a real Global. Hydrate from the store only when this\n * page has never been saved — otherwise afterRead would overwrite the form.\n */\nexport function hasThemeSlice(doc: ThemeDoc | null | undefined, child: ThemeChild): boolean {\n if (!doc) return false;\n if (child === \"colors\") return Boolean(colorHex(doc.colors?.brand?.primary));\n if (child === \"typography\") return Boolean(doc.typography?.body);\n if (child === \"appearance\") return Boolean(doc.appearance?.radius);\n return Boolean(doc.logo || doc.favicon || doc.logoMark);\n}\n\nexport interface ThemeStorePayload {\n findGlobal: (args: {\n slug: string;\n draft?: boolean;\n depth?: number;\n overrideAccess?: boolean;\n }) => Promise<ThemeDoc | null | undefined>;\n updateGlobal: (args: { slug: string; data: ThemeDoc; draft?: boolean }) => Promise<unknown>;\n}\n\n/**\n * Save on a Theme page writes that slice onto the hidden `theme` row.\n * Other children stay as stored.\n */\nexport async function persistThemeChild(\n payload: ThemeStorePayload,\n incoming: ThemeDoc,\n child: ThemeChild,\n): Promise<ThemeDoc> {\n const stored =\n (await payload.findGlobal({\n slug: THEME_SLUG,\n draft: false,\n overrideAccess: true,\n })) ?? {};\n const next = publishThemeChild(stored, incoming, child);\n await payload.updateGlobal({ slug: THEME_SLUG, data: next, draft: false });\n return next;\n}\n","import { catalog, type FontEntry } from \"@bison-lab/fonts\";\nimport { DESTRUCTIVE_SCALE_HEX, presetHints, SHADE_STEPS } from \"@bison-lab/tokens\";\n\n/** Same value as tokens `SUCCESS_SCALE_HEX`. Inlined so an older tokens peer still boots. */\nconst LIBRARY_SUCCESS_HEX = \"#22c55e\";\nimport type { Field, GlobalConfig, SelectField } from \"payload\";\n\nimport {\n SAME_AS_BODY,\n THEME_APPEARANCE_FIELD,\n THEME_COLOR_SCALE_FIELD,\n THEME_FONT_FIELD,\n THEME_GREY_SCALE_FIELD,\n THEME_LIBRARY_FIELD,\n THEME_PAIRING_FIELD,\n THEME_DOCUMENT_CONTROLS,\n THEME_SECTION_HEADING,\n headingSelectValue,\n} from \"./fields\";\nimport { APPEARANCE_CHOICES } from \"./appearance-labels\";\nimport { themeConfigFromDoc, validateSourceIncluded, validateThemeHex } from \"./map\";\nimport { hasThemeSlice, persistThemeChild, sliceFromTheme, type ThemeChild } from \"./publish\";\nimport { READABILITY_TARGET } from \"./readability\";\nimport {\n THEME_APPEARANCE_SLUG,\n THEME_COLORS_SLUG,\n THEME_IDENTITY_SLUG,\n THEME_SLUG,\n THEME_TYPOGRAPHY_SLUG,\n type CreateThemeOptions,\n type ThemeDoc,\n} from \"./types\";\n\nfunction requireAccess(options: CreateThemeOptions): CreateThemeOptions[\"access\"] {\n const read = options.access?.read;\n const update = options.access?.update;\n if (!read || !update) {\n throw new Error(\n \"createTheme requires access.read and access.update. Pass canManageBrand from this package as update.\",\n );\n }\n return { read, update };\n}\n\nfunction requireDestructive(options: CreateThemeOptions): string {\n return options.destructive ?? options.seed.brandDestructive ?? DESTRUCTIVE_SCALE_HEX;\n}\n\nfunction namedSelect<K extends string>(\n name: string,\n label: string,\n choices: Record<K, { label: string; hint: string }>,\n defaultValue: K,\n field?: string,\n): SelectField {\n return {\n name,\n type: \"select\",\n label,\n required: true,\n defaultValue,\n options: (Object.entries(choices) as [K, { label: string; hint: string }][]).map(\n ([value, { label: optionLabel, hint }]) => ({\n label: `${optionLabel} — ${hint}`,\n value,\n }),\n ),\n ...(field ? { admin: { components: { Field: field } } } : {}),\n };\n}\n\nconst STEP_OPTIONS = SHADE_STEPS.map((step) => ({ label: String(step), value: String(step) }));\n\nfunction colorScaleFields(hexDefault?: string): Field[] {\n const hidden = { hidden: true } as const;\n return [\n {\n name: \"hex\",\n type: \"text\",\n label: \"Hex\",\n required: true,\n ...(hexDefault ? { defaultValue: hexDefault } : {}),\n validate: validateThemeHex,\n admin: hidden,\n },\n {\n name: \"sourceStep\",\n type: \"select\",\n label: \"Source step\",\n required: true,\n defaultValue: \"500\",\n options: STEP_OPTIONS,\n admin: hidden,\n },\n {\n name: \"scale\",\n type: \"json\",\n label: \"Scale\",\n admin: hidden,\n },\n {\n name: \"stale\",\n type: \"checkbox\",\n label: \"Stale\",\n defaultValue: false,\n admin: hidden,\n },\n {\n name: \"include\",\n type: \"select\",\n label: \"Include\",\n required: true,\n defaultValue: \"all\",\n options: [\n { label: \"Include all\", value: \"all\" },\n { label: \"Source only\", value: \"source\" },\n { label: \"Custom\", value: \"custom\" },\n ],\n admin: hidden,\n },\n {\n name: \"includedSteps\",\n type: \"select\",\n label: \"Included steps\",\n hasMany: true,\n options: STEP_OPTIONS,\n validate: validateSourceIncluded,\n admin: hidden,\n },\n ];\n}\n\nfunction systemColorGroup(name: string, label: string, hexDefault: string): Field {\n return {\n name,\n type: \"group\",\n label,\n fields: colorScaleFields(hexDefault),\n admin: { components: { Field: THEME_COLOR_SCALE_FIELD }, hideGutter: true },\n };\n}\n\nfunction fontField(\n name: string,\n label: string,\n defaultValue: string,\n fonts: readonly FontEntry[],\n fontsBaseUrl: string,\n sameAsBody: boolean,\n): SelectField {\n const options = fonts.map((font) => ({ label: font.family, value: font.id }));\n return {\n name,\n type: \"select\",\n label,\n required: !sameAsBody,\n defaultValue,\n options: sameAsBody ? [{ label: \"Same as body\", value: SAME_AS_BODY }, ...options] : options,\n admin: {\n components: { Field: THEME_FONT_FIELD },\n custom: { fontsBaseUrl, ids: fonts.map((font) => font.id), sameAsBody },\n },\n };\n}\n\nfunction colorsFields(seed: CreateThemeOptions[\"seed\"], destructive: string): Field[] {\n return [\n {\n name: \"colors\",\n type: \"group\",\n label: false,\n admin: { hideGutter: true, width: \"100%\" },\n fields: [\n {\n name: \"brand\",\n type: \"group\",\n label: false,\n admin: { hideGutter: true, width: \"100%\" },\n fields: [\n {\n name: \"themeColorsHeading\",\n type: \"ui\",\n admin: {\n components: { Field: THEME_SECTION_HEADING },\n custom: {\n title: \"Brand colors\",\n hint: \"These colors define the visual identity of the site. Adjust them freely to match the brand.\",\n },\n },\n },\n systemColorGroup(\"primary\", \"Primary\", seed.brandPrimary),\n systemColorGroup(\"secondary\", \"Secondary\", seed.brandSecondary),\n systemColorGroup(\"accent\", \"Accent\", seed.brandAccent),\n systemColorGroup(\"highlight\", \"Highlight\", seed.brandHighlight),\n {\n name: \"libraryPlacement\",\n type: \"ui\",\n admin: {\n components: { Field: THEME_LIBRARY_FIELD },\n custom: { dataPath: \"colors.library\" },\n },\n },\n {\n name: \"greyPlacement\",\n type: \"ui\",\n admin: {\n components: { Field: THEME_GREY_SCALE_FIELD },\n custom: { dataPath: \"colors.greyScale\" },\n },\n },\n {\n name: \"statusColorsHeading\",\n type: \"ui\",\n admin: {\n components: { Field: THEME_SECTION_HEADING },\n custom: {\n title: \"Status colors\",\n hint: \"These colors communicate meaning across the site. You can adjust them to fit your brand, but we'll help keep them recognizable as success and error states.\",\n },\n },\n },\n systemColorGroup(\"success\", \"Success\", seed.brandSuccess || LIBRARY_SUCCESS_HEX),\n systemColorGroup(\"destructive\", \"Destructive\", destructive),\n ],\n },\n {\n ...namedSelect(\"greyScale\", \"Gray family\", presetHints.greyScale, seed.greyScale, THEME_GREY_SCALE_FIELD),\n admin: { hidden: true, components: { Field: THEME_GREY_SCALE_FIELD } },\n },\n {\n name: \"library\",\n type: \"array\",\n label: \"Additional colors\",\n labels: { singular: \"Color\", plural: \"Colors\" },\n fields: [\n { name: \"key\", type: \"text\", label: \"Key\", required: true, admin: { hidden: true } },\n { name: \"label\", type: \"text\", label: \"Name\", required: true, admin: { hidden: true } },\n ...colorScaleFields(),\n ],\n admin: { hidden: true, components: { Field: THEME_LIBRARY_FIELD }, width: \"100%\" },\n },\n ],\n },\n ];\n}\n\nfunction typographyFields(\n seed: CreateThemeOptions[\"seed\"],\n fonts: readonly FontEntry[],\n fontsBaseUrl: string,\n): Field[] {\n return [\n {\n name: \"typography\",\n type: \"group\",\n label: \"Typography\",\n fields: [\n fontField(\n \"heading\",\n \"Heading font\",\n headingSelectValue(seed.fontHeading),\n fonts,\n fontsBaseUrl,\n true,\n ),\n fontField(\"body\", \"Body font\", seed.fontBody, fonts, fontsBaseUrl, false),\n {\n name: \"pairing\",\n type: \"ui\",\n admin: { components: { Field: THEME_PAIRING_FIELD }, custom: { fontsBaseUrl } },\n },\n ],\n },\n ];\n}\n\nfunction appearanceFields(seed: CreateThemeOptions[\"seed\"]): Field[] {\n return [\n {\n name: \"appearance\",\n type: \"group\",\n label: \"Appearance\",\n fields: [\n namedSelect(\"defaultTheme\", \"Default theme\", APPEARANCE_CHOICES.defaultTheme, seed.defaultTheme),\n namedSelect(\"radius\", \"Corner style\", APPEARANCE_CHOICES.radius, seed.radius),\n namedSelect(\"shadow\", \"Depth\", APPEARANCE_CHOICES.shadow, seed.shadow),\n namedSelect(\"motion\", \"Motion\", APPEARANCE_CHOICES.motion, seed.motion),\n namedSelect(\"density\", \"Spacing\", APPEARANCE_CHOICES.density, seed.density),\n ],\n admin: { components: { Field: THEME_APPEARANCE_FIELD } },\n },\n ];\n}\n\nfunction identityFields(collection: string): Field[] {\n return [\n {\n name: \"logo\",\n type: \"upload\",\n relationTo: collection,\n label: \"Logo\",\n admin: {\n description: \"The full wordmark used in the header and footer.\",\n },\n },\n {\n name: \"favicon\",\n type: \"upload\",\n relationTo: collection,\n label: \"Favicon\",\n admin: {\n description: \"The small icon in the browser tab. PNG or SVG.\",\n },\n },\n {\n name: \"logoMark\",\n type: \"upload\",\n relationTo: collection,\n label: \"Mobile menu logo\",\n admin: {\n description:\n \"Optional. Used when the full logo is too wide for the mobile menu — typically the mark.\",\n },\n },\n ];\n}\n\nfunction pageHooks(child: ThemeChild) {\n return {\n beforeChange: [\n async ({ data, req }: { data: ThemeDoc; req?: { payload?: Parameters<typeof persistThemeChild>[0] } }) => {\n if (req?.payload) await persistThemeChild(req.payload, data, child);\n return data;\n },\n ],\n afterRead: [\n async ({\n doc,\n req,\n }: {\n doc: ThemeDoc;\n req?: {\n payload?: {\n findGlobal: (args: {\n slug: string;\n draft?: boolean;\n depth?: number;\n overrideAccess?: boolean;\n }) => Promise<ThemeDoc | null | undefined>;\n };\n };\n }) => {\n if (hasThemeSlice(doc, child)) return doc;\n const theme = await req?.payload?.findGlobal({\n slug: THEME_SLUG,\n draft: false,\n depth: child === \"identity\" ? 1 : 0,\n overrideAccess: true,\n });\n if (!theme) return doc;\n return { ...doc, ...sliceFromTheme(theme, child) };\n },\n ],\n };\n}\n\n/**\n * Standard Payload Globals in an `admin.group: \"Theme\"` nav section —\n * the same pattern as Content and Settings. Each page is its own Global\n * (Save, fields, access). A hidden `theme` store is the published read\n * model `getPublishedTheme` already knows. Locking is off: these are\n * settings forms, not collaborative documents. No draft mode, no preview\n * pane.\n */\nexport function createTheme(options: CreateThemeOptions): GlobalConfig[] {\n const access = requireAccess(options);\n const destructive = requireDestructive(options);\n const {\n seed,\n fonts = catalog,\n fontsBaseUrl = \"/fonts\",\n contrastTarget = READABILITY_TARGET,\n logo,\n identity,\n onPublish,\n } = options;\n\n const adminCustom = { contrastTarget, fontsBaseUrl, identityFallback: identity?.fallback };\n const colorFields = colorsFields(seed, destructive);\n const typeFields = typographyFields(seed, fonts, fontsBaseUrl);\n const lookFields = appearanceFields(seed);\n const markFields = logo ? identityFields(logo.collection) : [];\n\n const store: GlobalConfig = {\n slug: THEME_SLUG,\n label: \"Theme\",\n lockDocuments: false,\n admin: {\n hidden: true,\n hideAPIURL: true,\n custom: adminCustom,\n },\n access: {\n read: access.read,\n update: access.update,\n },\n fields: [...colorFields, ...typeFields, ...lookFields, ...markFields],\n hooks: {\n afterChange: [\n async ({ doc }) => {\n if (!onPublish) return;\n const themeDoc = doc as ThemeDoc;\n await onPublish(themeConfigFromDoc(themeDoc, seed), themeDoc);\n },\n ],\n },\n };\n\n function page(\n slug: string,\n label: string,\n child: ThemeChild,\n fields: Field[],\n preview: boolean,\n ): GlobalConfig {\n return {\n slug,\n label,\n lockDocuments: false,\n admin: {\n group: \"Theme\",\n hideAPIURL: true,\n custom: adminCustom,\n components: preview\n ? { elements: { beforeDocumentControls: [THEME_DOCUMENT_CONTROLS] } }\n : undefined,\n },\n access: {\n read: access.read,\n update: access.update,\n },\n fields,\n hooks: pageHooks(child),\n };\n }\n\n return [\n store,\n page(THEME_COLORS_SLUG, \"Colors\", \"colors\", colorFields, true),\n page(THEME_TYPOGRAPHY_SLUG, \"Typography\", \"typography\", typeFields, true),\n page(THEME_APPEARANCE_SLUG, \"Appearance\", \"appearance\", lookFields, true),\n ...(logo ? [page(THEME_IDENTITY_SLUG, \"Identity\", \"identity\", markFields, false)] : []),\n ];\n}\n","const DANGEROUS_TAGS = [\"script\", \"foreignObject\", \"iframe\", \"embed\", \"object\"] as const;\n\n/**\n * Strips the SVG features a lockup does not need and an attacker would\n * use: script, foreignObject, iframe/embed/object, event handlers,\n * javascript: / data:text/html URLs, and xml-stylesheet. Internal `#`\n * refs and ordinary drawing elements stay.\n */\nexport function sanitizeSvg(source: string): string {\n if (!/<svg[\\s>]/i.test(source)) {\n throw new Error(\"Not an SVG\");\n }\n\n let out = source.replace(/<\\?xml-stylesheet[\\s\\S]*?\\?>/gi, \"\");\n\n for (const tag of DANGEROUS_TAGS) {\n out = out.replace(new RegExp(`<${tag}\\\\b[^>]*>[\\\\s\\\\S]*?<\\\\/${tag}\\\\s*>`, \"gi\"), \"\");\n out = out.replace(new RegExp(`<${tag}\\\\b[^>]*\\\\/>`, \"gi\"), \"\");\n }\n\n out = out.replace(/\\s+on[a-z][a-z0-9-]*\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, \"\");\n out = out.replace(\n /((?:href|src|xlink:href)\\s*=\\s*)([\"'])\\s*(?:javascript:|data:text\\/html)[^\"']*\\2/gi,\n \"$1$2$2\",\n );\n\n return out;\n}\n\nexport function isSvgUpload(file: { mimetype?: string; name?: string; data?: Buffer }): boolean {\n if (file.mimetype === \"image/svg+xml\") return true;\n if (typeof file.name === \"string\" && file.name.toLowerCase().endsWith(\".svg\")) return true;\n return false;\n}\n","import type { Access, CollectionConfig } from \"payload\";\n\nimport { isSvgUpload, sanitizeSvg } from \"./sanitize\";\n\nexport const BRAND_ASSETS_SLUG = \"brand-assets\";\n\n/** SVG preferred; PNG allowed. ICO is for the favicon slot. */\nexport const BRAND_ASSETS_MIME_TYPES = [\n \"image/svg+xml\",\n \"image/png\",\n \"image/x-icon\",\n \"image/vnd.microsoft.icon\",\n] as const;\n\nexport interface CreateBrandAssetsOptions {\n /**\n * Required, no default — same shape as `createTheme`. `read` is typically\n * public so the live site can load the lockup; `update` is `canManageBrand`\n * and covers create, update, delete, and version history. This collection\n * does not read the Roles Global.\n */\n access: { read: Access; update: Access };\n /** Default `brand-assets`. Pass the same slug to `createTheme({ logo })`. */\n slug?: string;\n}\n\nfunction requireAccess(options: CreateBrandAssetsOptions): CreateBrandAssetsOptions[\"access\"] {\n const read = options.access?.read;\n const update = options.access?.update;\n if (!read || !update) {\n throw new Error(\n \"createBrandAssets requires access.read and access.update. Pass canManageBrand from this package as update.\",\n );\n }\n return { read, update };\n}\n\n/**\n * Locked cupboard for lockup and mark. Not ordinary Media — only a brand\n * manager writes it. Theme's `logo` / `logoMark` point here when the site\n * passes this slug as `logo.collection`.\n */\nexport function createBrandAssets(options: CreateBrandAssetsOptions): CollectionConfig {\n const access = requireAccess(options);\n const slug = options.slug ?? BRAND_ASSETS_SLUG;\n\n return {\n slug,\n labels: { singular: \"Brand asset\", plural: \"Brand assets\" },\n admin: {\n group: \"Settings\",\n useAsTitle: \"label\",\n description: \"Logo, favicon, and mobile-menu mark. SVG preferred; PNG and ICO allowed.\",\n },\n upload: {\n mimeTypes: [...BRAND_ASSETS_MIME_TYPES],\n crop: false,\n focalPoint: false,\n },\n versions: true,\n access: {\n read: access.read,\n create: access.update,\n update: access.update,\n delete: access.update,\n readVersions: access.update,\n },\n fields: [\n { name: \"label\", type: \"text\", label: \"Label\" },\n { name: \"notes\", type: \"textarea\", label: \"Usage notes\" },\n { name: \"alt\", type: \"text\", label: \"Alt text\", required: true },\n ],\n hooks: {\n beforeOperation: [\n ({ req, operation }) => {\n if (operation !== \"create\" && operation !== \"update\") return;\n const file = req.file;\n if (!file || !isSvgUpload(file)) return;\n file.data = Buffer.from(sanitizeSvg(file.data.toString(\"utf8\")), \"utf8\");\n },\n ],\n },\n };\n}\n","import type { ThemeConfig } from \"@bison-lab/tokens\";\n\nimport { docFromConfig } from \"./map\";\nimport { THEME_SLUG } from \"./types\";\nimport type { ThemeDoc } from \"./types\";\n\nexport interface SeedThemePayload {\n updateGlobal: (args: {\n slug: string;\n data: ThemeDoc;\n draft?: boolean;\n }) => Promise<unknown>;\n}\n\n/**\n * Writes a published Theme from the seed. For a site's migration `up()`,\n * so the row exists on deploy and renders what `bison-theme.css` rendered.\n */\nexport async function seedTheme(payload: SeedThemePayload, seed: ThemeConfig): Promise<unknown> {\n return payload.updateGlobal({\n slug: THEME_SLUG,\n data: docFromConfig(seed),\n draft: false,\n });\n}\n","/**\n * Device sizes the Theme (and, later, Pages) live-preview toolbar offers.\n * One list so the panes match.\n */\nexport const THEME_PREVIEW_BREAKPOINTS = [\n { label: \"Mobile\", name: \"mobile\", width: 375, height: 667 },\n { label: \"Tablet\", name: \"tablet\", width: 768, height: 1024 },\n { label: \"Desktop\", name: \"desktop\", width: 1440, height: 900 },\n] as const;\n","/**\n * The Roles Global 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\nexport const ROLES = [\"developer\", \"admin\", \"designer\", \"author\"] as const;\n\nexport type Role = (typeof ROLES)[number];\n\nexport const ROLE_LABELS: Record<Role, string> = {\n developer: \"Developer\",\n admin: \"Admin\",\n designer: \"Designer\",\n author: \"Author\",\n};\n\nexport const CAPABILITIES = [\"content\", \"brand\", \"publish\", \"users\"] as const;\n\nexport type Capability = (typeof CAPABILITIES)[number];\n\nexport interface RoleRow {\n id?: string | null;\n role: Role;\n content: boolean;\n brand: boolean;\n publish: boolean;\n users: boolean;\n}\n\nexport interface RolesDoc {\n roles?: RoleRow[] | null;\n}\n\n/**\n * `req.user` as Payload hands it over. Structural so this module never\n * imports a site's generated `User`. `roles` is optional because a row\n * that predates the field must come back powerless.\n */\nexport type MaybeUser = { id?: string | number; roles?: readonly string[] | null } | null | undefined;\n\nexport function isRole(value: unknown): value is Role {\n return typeof value === \"string\" && (ROLES as readonly string[]).includes(value);\n}\n","import { CAPABILITIES, isRole, ROLE_LABELS, ROLES, type Capability, type Role, type RoleRow } from \"./types\";\n\nexport const ROLES_SLUG = \"roles\";\n\n/**\n * Default rank and ticks. Brand is Designer only. The API tab is not a\n * column — it is locked to Developer in `isDeveloper`.\n */\nexport const DEFAULT_ROLE_MATRIX: readonly RoleRow[] = [\n { role: \"developer\", content: true, brand: true, publish: true, users: true },\n { role: \"admin\", content: true, brand: false, publish: true, users: true },\n { role: \"designer\", content: false, brand: true, publish: false, users: false },\n { role: \"author\", content: true, brand: false, publish: false, users: false },\n];\n\nexport const DEVELOPER_DESCRIPTION = \"Everything Admin can do, plus the document API tab.\";\n\nexport const LAST_USERS_TICK_MESSAGE =\n \"Keep Users ticked on at least one of Admin or Developer.\";\n\nconst CAPABILITY_LABELS: Record<Capability, string> = {\n content: \"Content\",\n brand: \"Brand\",\n publish: \"Publish\",\n users: \"Users\",\n};\n\nexport function defaultRolesFieldValue(): RoleRow[] {\n return DEFAULT_ROLE_MATRIX.map((row) => ({ id: row.role, ...row }));\n}\n\nexport function parseRolesMatrix(\n value: unknown,\n): { ok: true; rows: RoleRow[] } | { ok: false; message: string } {\n if (!Array.isArray(value)) {\n return { ok: false, message: \"Roles must be Developer, Admin, Designer, and Author.\" };\n }\n\n const rows: RoleRow[] = [];\n const seen = new Set<Role>();\n for (const item of value) {\n if (!item || typeof item !== \"object\") continue;\n const role = \"role\" in item ? item.role : undefined;\n if (!isRole(role) || seen.has(role)) continue;\n seen.add(role);\n rows.push({\n id: role,\n role,\n content: Boolean(\"content\" in item && item.content),\n brand: Boolean(\"brand\" in item && item.brand),\n publish: Boolean(\"publish\" in item && item.publish),\n users: Boolean(\"users\" in item && item.users),\n });\n }\n\n if (rows.length !== ROLES.length || ROLES.some((role) => !seen.has(role))) {\n return { ok: false, message: \"Roles must be Developer, Admin, Designer, and Author.\" };\n }\n return { ok: true, rows };\n}\n\nexport function hasPrivilegedUsersTick(rows: readonly RoleRow[]): boolean {\n return rows.some((row) => (row.role === \"admin\" || row.role === \"developer\") && row.users);\n}\n\nexport function validateRolesMatrix(value: unknown): true | string {\n const parsed = parseRolesMatrix(value);\n if (!parsed.ok) return parsed.message;\n if (!hasPrivilegedUsersTick(parsed.rows)) return LAST_USERS_TICK_MESSAGE;\n return true;\n}\n\n/**\n * Developer is exclusive. Admin does not swallow Designer: both store.\n */\nexport function normalizeStoredRoles(value: unknown): Role[] {\n if (!Array.isArray(value)) return [];\n const roles = [...new Set(value.filter(isRole))];\n if (roles.includes(\"developer\")) return [\"developer\"];\n return roles;\n}\n\nexport function roleSelectOptions(matrix: readonly RoleRow[] = DEFAULT_ROLE_MATRIX) {\n return matrix.map((row) => ({ label: ROLE_LABELS[row.role], value: row.role }));\n}\n\n/** Capability copy only. Developer names the API tab; nothing about MCP or seed. */\nexport function roleDescription(role: Role, matrix: readonly RoleRow[] = DEFAULT_ROLE_MATRIX): string {\n if (role === \"developer\") return DEVELOPER_DESCRIPTION;\n const row = matrix.find((entry) => entry.role === role);\n if (!row) return \"No capabilities\";\n const ticks = CAPABILITIES.filter((capability) => row[capability]).map(\n (capability) => CAPABILITY_LABELS[capability],\n );\n return ticks.join(\", \") || \"No capabilities\";\n}\n","import type { Access, PayloadRequest } from \"payload\";\n\nimport { DEFAULT_ROLE_MATRIX, parseRolesMatrix, ROLES_SLUG } from \"./matrix\";\nimport { isRole, type Capability, type MaybeUser, type Role, type RoleRow } from \"./types\";\n\nexport type { MaybeUser } from \"./types\";\n\ntype AccessArgs = {\n req: {\n user?: MaybeUser;\n payload?: Pick<NonNullable<PayloadRequest[\"payload\"]>, \"findGlobal\">;\n };\n};\n\ntype CapabilityPredicate = {\n (user: MaybeUser, matrix?: readonly RoleRow[]): boolean;\n (args: AccessArgs): boolean | Promise<boolean>;\n};\n\nfunction isAccessArgs(value: unknown): value is AccessArgs {\n return typeof value === \"object\" && value !== null && \"req\" in value;\n}\n\nexport function storedRoles(user: MaybeUser): readonly string[] {\n return Array.isArray(user?.roles) ? user.roles : [];\n}\n\nexport function hasRole(user: MaybeUser, role: Role): boolean {\n return storedRoles(user).includes(role);\n}\n\n/** Not a tick. True only when `developer` is stored on the row. */\nexport function isDeveloper(user: MaybeUser): boolean {\n return hasRole(user, \"developer\");\n}\n\nexport function hasCapability(\n user: MaybeUser,\n capability: Capability,\n matrix: readonly RoleRow[] = DEFAULT_ROLE_MATRIX,\n): boolean {\n return storedRoles(user).some((role) => {\n const row = matrix.find((entry) => entry.role === role);\n return row?.[capability] === true;\n });\n}\n\n/**\n * Reads the saved Roles Global, falling back to the seed when the row is\n * empty, missing, or unreadable. Always override-access so a Designer\n * evaluating Theme does not have to read Settings → Roles.\n */\nexport async function getRolesMatrix(req: AccessArgs[\"req\"] = {}): Promise<RoleRow[]> {\n const findGlobal = req.payload?.findGlobal;\n if (typeof findGlobal !== \"function\") return [...DEFAULT_ROLE_MATRIX];\n try {\n const doc = await findGlobal({\n slug: ROLES_SLUG,\n overrideAccess: true,\n req: req as PayloadRequest,\n });\n const parsed = parseRolesMatrix(doc && typeof doc === \"object\" && \"roles\" in doc ? doc.roles : undefined);\n return parsed.ok ? parsed.rows : [...DEFAULT_ROLE_MATRIX];\n } catch {\n return [...DEFAULT_ROLE_MATRIX];\n }\n}\n\nfunction capabilityPredicate(capability: Capability): CapabilityPredicate {\n function predicate(\n userOrArgs: MaybeUser | AccessArgs,\n matrix?: readonly RoleRow[],\n ): boolean | Promise<boolean> {\n if (isAccessArgs(userOrArgs)) {\n const user = userOrArgs.req.user as MaybeUser;\n if (matrix) return hasCapability(user, capability, matrix);\n return getRolesMatrix(userOrArgs.req).then((rows) => hasCapability(user, capability, rows));\n }\n return hasCapability(userOrArgs, capability, matrix ?? DEFAULT_ROLE_MATRIX);\n }\n return predicate as CapabilityPredicate;\n}\n\nexport const canManageContent = capabilityPredicate(\"content\");\nexport const canManageBrand = capabilityPredicate(\"brand\");\nexport const canPublish = capabilityPredicate(\"publish\");\n\n/** Users tick on any held role. */\nexport const isAdmin = capabilityPredicate(\"users\");\n\n/** This role id currently has the Users tick. */\nexport function isPrivilegedRole(\n role: unknown,\n matrix: readonly RoleRow[] = DEFAULT_ROLE_MATRIX,\n): boolean {\n return isRole(role) && matrix.some((row) => row.role === role && row.users);\n}\n\nexport function isAuthenticated(user: MaybeUser): boolean;\nexport function isAuthenticated(args: AccessArgs): boolean;\nexport function isAuthenticated(userOrArgs: MaybeUser | AccessArgs): boolean {\n if (isAccessArgs(userOrArgs)) return Boolean(userOrArgs.req.user);\n return Boolean(userOrArgs);\n}\n\nexport const isAdminOrSelf: Access = async ({ req }) => {\n if (!req.user) return false;\n if (await isAdmin({ req })) return true;\n return { id: { equals: req.user.id } };\n};\n\nexport const authenticatedOrPublished: Access = ({ req: { user } }) => {\n if (isAuthenticated(user as MaybeUser)) return true;\n return { _status: { equals: \"published\" } };\n};\n\n/** API tab condition: Developer only, not the Users tick. */\nexport function isDeveloperTab({ req }: AccessArgs): boolean {\n return isDeveloper(req.user as MaybeUser);\n}\n","export const ROLES_MATRIX_FIELD = \"@bison-lab/payload-core/admin#RolesMatrixField\";\nexport const ROLES_FIELD = \"@bison-lab/payload-core/admin#RolesField\";\n","import type { ArrayField, GlobalConfig } from \"payload\";\n\nimport { isAdmin } from \"./access\";\nimport { ROLES_MATRIX_FIELD } from \"./fields\";\nimport { defaultRolesFieldValue, ROLES_SLUG, validateRolesMatrix } from \"./matrix\";\nimport { ROLE_LABELS, ROLES } from \"./types\";\n\n/**\n * Settings → Roles. Rank is the array order (Payload's drag handle). Ticks\n * are Content, Brand, Publish, Users. The API tab is not a column.\n */\nexport function createRoles(): GlobalConfig {\n const rolesField: ArrayField = {\n name: \"roles\",\n type: \"array\",\n label: \"Roles\",\n labels: { singular: \"Role\", plural: \"Roles\" },\n minRows: 4,\n maxRows: 4,\n required: true,\n defaultValue: defaultRolesFieldValue(),\n validate: validateRolesMatrix,\n admin: {\n components: { Field: ROLES_MATRIX_FIELD },\n description:\n \"Drag to change rank. Ticks are Content, Brand, Publish, and Users. The API tab is locked to Developer.\",\n initCollapsed: false,\n },\n fields: [\n {\n name: \"role\",\n type: \"select\",\n label: \"Role\",\n required: true,\n options: ROLES.map((value) => ({ label: ROLE_LABELS[value], value })),\n admin: { readOnly: true },\n },\n { name: \"content\", type: \"checkbox\", label: \"Content\" },\n { name: \"brand\", type: \"checkbox\", label: \"Brand\" },\n { name: \"publish\", type: \"checkbox\", label: \"Publish\" },\n { name: \"users\", type: \"checkbox\", label: \"Users\" },\n ],\n };\n\n return {\n slug: ROLES_SLUG,\n label: \"Roles\",\n admin: {\n group: \"Settings\",\n hidden: ({ user }) => !isAdmin(user),\n description: \"Who may do what. Drag to change rank. The API tab is locked to Developer.\",\n },\n access: {\n read: (args) => isAdmin(args),\n update: (args) => isAdmin(args),\n },\n fields: [rolesField],\n };\n}\n","import { defaultRolesFieldValue, ROLES_SLUG } from \"./matrix\";\nimport type { RolesDoc } from \"./types\";\n\nexport interface SeedRolesPayload {\n updateGlobal: (args: { slug: string; data: RolesDoc }) => Promise<unknown>;\n}\n\n/**\n * Writes the default matrix. For a site's migration `up()`. Never writes a\n * user row — seeding `developer` on a person stays a site concern.\n */\nexport async function seedRoles(payload: SeedRolesPayload): Promise<unknown> {\n return payload.updateGlobal({\n slug: ROLES_SLUG,\n data: { roles: defaultRolesFieldValue() },\n });\n}\n","import type {\n CollectionSlug,\n PayloadRequest,\n TextField,\n TextFieldSingleValidation,\n Where,\n} from \"payload\";\nimport { ValidationError, validations } from \"payload\";\n\nexport interface SlugFieldOptions {\n /** The collection the field sits on — what the duplicate check searches. */\n collection: CollectionSlug;\n /**\n * True when a slug belongs to something the CMS does not own: a path the app\n * already serves from code, or one reserved for the framework. Which paths\n * those are is knowable only to the site, so it arrives as an option.\n */\n isReserved: (slug: string) => boolean;\n}\n\ninterface SlugCheckArgs extends SlugFieldOptions {\n id: number | string | undefined;\n req: PayloadRequest | undefined;\n}\n\n/**\n * A stored slug from whatever was typed: lowercased, with any run of\n * whitespace and slashes stripped from either end in one pass, so `/ about-us /`\n * does not keep the inner spaces a trim-then-strip would leave.\n */\nexport function normalizeSlug(value: string): string {\n return value.replace(/^[\\s/]+|[\\s/]+$/g, \"\").toLowerCase();\n}\n\nasync function slugProblem(\n slug: string,\n { collection, id, isReserved, req }: SlugCheckArgs,\n): Promise<string | undefined> {\n if (!slug) return undefined;\n\n if (isReserved(slug)) {\n return `\"/${slug}\" is a built-in page on this site, so a page here cannot use it. Please choose a different address.`;\n }\n\n if (typeof req?.payload?.find !== \"function\") return undefined;\n\n const where: Where = { slug: { equals: slug } };\n if (id !== undefined) {\n where.id = { not_equals: id };\n }\n\n const query = {\n collection,\n depth: 0,\n limit: 1,\n overrideAccess: true,\n pagination: false,\n req,\n where,\n };\n\n const live = await req.payload.find(query);\n const taken =\n live.docs.length > 0 || (await req.payload.find({ ...query, draft: true })).docs.length > 0;\n\n return taken ? `Another page is already using \"/${slug}\". Please choose a different address.` : undefined;\n}\n\n/**\n * The path a document is published at, normalised on the way in. Unique\n * across the collection. `validate` is the message under the field;\n * `beforeChange` is the enforcement on a draft save, where Payload skips\n * field validation.\n */\nexport function slugField({ collection, isReserved }: SlugFieldOptions): TextField {\n const validate: TextFieldSingleValidation = async (value, options) => {\n try {\n const builtIn = await validations.text(value, options);\n if (builtIn !== true) return builtIn;\n } catch {\n // form-state without a request still runs the reserved / duplicate checks\n }\n\n const problem = await slugProblem(typeof value === \"string\" ? value : \"\", {\n collection,\n id: options.id,\n isReserved,\n req: options.req,\n });\n return problem ?? true;\n };\n\n return {\n name: \"slug\",\n type: \"text\",\n required: true,\n unique: true,\n admin: {\n description: 'Path under the site root, no leading slash: \"about-us\" or \"patients/stories\".',\n },\n hooks: {\n beforeValidate: [({ value }) => (typeof value === \"string\" ? normalizeSlug(value) : value)],\n beforeChange: [\n async ({ data, originalDoc, req, value }) => {\n if (typeof value !== \"string\") return value;\n\n const problem = await slugProblem(value, {\n collection,\n id: originalDoc?.id ?? data?.id,\n isReserved,\n req,\n });\n if (problem) {\n throw new ValidationError(\n { collection, errors: [{ message: problem, path: \"slug\" }], req },\n req?.t,\n );\n }\n return value;\n },\n ],\n },\n validate,\n };\n}\n","import type { Block, CollectionConfig } from \"payload\";\n\nimport { authenticatedOrPublished, canManageContent, isAdmin, isAuthenticated } from \"../roles/access\";\nimport { slugField } from \"../fields/slug\";\n\nexport interface PagesOptions {\n /**\n * What a page may open with. One row, so this is the site's hero variants,\n * not a list a page picks several from.\n */\n heroBlocks: Block[];\n /** The sections a page body can be built out of. */\n layoutBlocks: Block[];\n /**\n * True when a slug is not the CMS's to hand out — a path the app already\n * serves from code, or one the framework reserves.\n */\n isReservedSlug: (slug: string) => boolean;\n /**\n * Public path of a page from its stored slug — the site's own route helper.\n * Drives the page preview overlay's iframe.\n */\n previewPath: (slug: string) => string;\n /**\n * Component path for Payload's native Preview button, as a key into the\n * site's committed `importMap.js`. Omit it for Payload's own button.\n */\n previewButton?: string;\n}\n\n/**\n * CMS-managed pages: one required hero, then a reorderable body of sections.\n * The CMS owns copy and the order of sections; what a section looks like is\n * code-owned, which is why the blocks are an argument.\n */\nexport function createPages({\n heroBlocks,\n isReservedSlug,\n layoutBlocks,\n previewPath,\n previewButton,\n}: PagesOptions): CollectionConfig {\n const [defaultHero] = heroBlocks;\n if (!defaultHero) throw new Error(\"createPages needs at least one hero block\");\n\n return {\n slug: \"pages\",\n admin: {\n useAsTitle: \"title\",\n group: \"Content\",\n defaultColumns: [\"title\", \"slug\", \"_status\", \"updatedAt\"],\n preview: (doc) => previewPath(typeof doc.slug === \"string\" ? doc.slug : \"\"),\n ...(previewButton ? { components: { edit: { PreviewButton: previewButton } } } : {}),\n },\n access: {\n read: authenticatedOrPublished,\n readVersions: isAuthenticated,\n create: canManageContent,\n update: canManageContent,\n delete: isAdmin,\n },\n versions: {\n drafts: { autosave: { interval: 375 } },\n maxPerDoc: 50,\n },\n fields: [\n { name: \"title\", type: \"text\", required: true },\n slugField({ collection: \"pages\", isReserved: isReservedSlug }),\n {\n name: \"hero\",\n type: \"blocks\",\n required: true,\n minRows: 1,\n maxRows: 1,\n blocks: heroBlocks,\n defaultValue: [{ blockType: defaultHero.slug }],\n admin: {\n description: \"Every page opens with one hero. A page cannot be published without one.\",\n },\n },\n {\n name: \"layout\",\n type: \"blocks\",\n required: true,\n minRows: 1,\n blocks: layoutBlocks,\n labels: { singular: \"Section\", plural: \"Sections\" },\n },\n ],\n };\n}\n","import type {\n CollectionConfig,\n PayloadRequest,\n SelectFieldManyValidation,\n TextField,\n TextFieldSingleValidation,\n Where,\n} from \"payload\";\nimport { APIError } from \"payload\";\nimport { select, text } from \"payload/shared\";\n\nimport { getRolesMatrix, isAdmin, isAdminOrSelf, isPrivilegedRole } from \"../roles/access\";\nimport { ROLES_FIELD } from \"../roles/fields\";\nimport { normalizeStoredRoles, roleSelectOptions } from \"../roles/matrix\";\nimport type { Role, RoleRow } from \"../roles/types\";\n\nconst nameValidate: TextFieldSingleValidation = (value, options) => {\n if (options.operation === \"update\" && value === \"\" && options.previousValue === \"\") return true;\n return text(value, options);\n};\n\nfunction nameField(name: \"firstName\" | \"lastName\"): TextField {\n return { name, type: \"text\", required: true, validate: nameValidate };\n}\n\n/** Refuses the save that would remove Users from the only privileged user. */\nexport const LAST_ADMIN_DEMOTE_MESSAGE = \"Make another user an admin before removing it from this one.\";\n/** Refuses the delete that would remove the only privileged user. */\nexport const LAST_ADMIN_DELETE_MESSAGE = \"Make another user an admin before deleting this one.\";\n\ninterface TransactionalAdapter {\n execute?: (args: { db?: unknown; raw?: string }) => Promise<unknown>;\n sessions?: Record<string, { db: unknown } | undefined>;\n}\n\nconst LAST_ADMIN_LOCK = \"select pg_advisory_xact_lock(hashtext('users:last-admin'))\";\n\nasync function lockLastAdminDecision(req: PayloadRequest): Promise<void> {\n const { execute, sessions } = req.payload.db as unknown as TransactionalAdapter;\n const id = req.transactionID;\n const session = typeof id === \"string\" || typeof id === \"number\" ? sessions?.[id] : undefined;\n if (!session || typeof execute !== \"function\") return;\n await execute({ db: session.db, raw: LAST_ADMIN_LOCK });\n}\n\nfunction privilegedRoles(matrix: readonly RoleRow[]): Role[] {\n return matrix.filter((row) => row.users).map((row) => row.role);\n}\n\nfunction holdsPrivilegedRole(roles: unknown, matrix: readonly RoleRow[]): boolean {\n return Array.isArray(roles) && roles.some((role) => isPrivilegedRole(role, matrix));\n}\n\nfunction privilegedWhere(matrix: readonly RoleRow[]): Where {\n const roles = privilegedRoles(matrix);\n if (roles.length === 0) return { id: { equals: \"__none__\" } };\n return { or: roles.map((role) => ({ roles: { contains: role } })) };\n}\n\nasync function adminCount(req: PayloadRequest, excluding?: number | string): Promise<number> {\n await lockLastAdminDecision(req);\n const matrix = await getRolesMatrix(req);\n const holdsPrivileged = privilegedWhere(matrix);\n const { totalDocs } = await req.payload.count({\n collection: \"users\",\n overrideAccess: true,\n req,\n where:\n excluding === undefined\n ? holdsPrivileged\n : { and: [holdsPrivileged, { id: { not_equals: excluding } }] },\n });\n return totalDocs;\n}\n\nconst rolesValidate: SelectFieldManyValidation = async (value, options) => {\n const builtIn = select(value, options);\n if (builtIn !== true) return builtIn;\n if (options.operation !== \"update\" || options.id === undefined) return true;\n const matrix = await getRolesMatrix(options.req);\n if (holdsPrivilegedRole(value, matrix) || !holdsPrivilegedRole(options.previousValue, matrix)) {\n return true;\n }\n return (await adminCount(options.req, options.id)) > 0 ? true : LAST_ADMIN_DEMOTE_MESSAGE;\n};\n\nexport interface UsersOptions {\n /**\n * Whether the auth cookie is marked Secure. Passed in rather than read from\n * the environment here: which env var means \"served over HTTPS\" is the\n * host's business, not the CMS's.\n */\n secureCookies: boolean;\n /**\n * Component path for the roles field, as a key into the site's committed\n * `importMap.js`. Defaults to the package checklist. A value nothing\n * resolves renders the field as nothing.\n */\n rolesField?: string;\n}\n\nexport function createUsers({ secureCookies, rolesField }: UsersOptions): CollectionConfig {\n return {\n slug: \"users\",\n auth: {\n maxLoginAttempts: 5,\n lockTime: 10 * 60 * 1000,\n tokenExpiration: 2 * 60 * 60,\n cookies: {\n sameSite: \"Lax\",\n secure: secureCookies,\n },\n },\n admin: {\n useAsTitle: \"email\",\n defaultColumns: [\"email\", \"firstName\", \"lastName\", \"roles\"],\n hidden: ({ user }) => !isAdmin(user),\n },\n access: {\n create: isAdmin,\n delete: isAdmin,\n unlock: isAdmin,\n read: isAdminOrSelf,\n update: isAdminOrSelf,\n },\n hooks: {\n beforeDelete: [\n async ({ id, req }) => {\n const doomed = await req.payload.findByID({\n collection: \"users\",\n id,\n depth: 0,\n disableErrors: true,\n overrideAccess: true,\n req,\n });\n if (!isAdmin(doomed) || (await adminCount(req, id)) > 0) return;\n throw new APIError(LAST_ADMIN_DELETE_MESSAGE, 400);\n },\n ],\n afterOperation: [\n async (arg) => {\n const { operation, req } = arg;\n const touchesRoles =\n (operation === \"update\" || operation === \"updateByID\") &&\n arg.args.data?.roles !== undefined;\n const deletes = operation === \"delete\" || operation === \"deleteByID\";\n if ((touchesRoles || deletes) && (await adminCount(req)) === 0) {\n throw new APIError(deletes ? LAST_ADMIN_DELETE_MESSAGE : LAST_ADMIN_DEMOTE_MESSAGE, 400);\n }\n return arg.result;\n },\n ],\n },\n fields: [\n {\n type: \"row\",\n fields: [nameField(\"firstName\"), nameField(\"lastName\")],\n },\n {\n name: \"roles\",\n type: \"select\",\n hasMany: true,\n required: true,\n defaultValue: [\"author\"],\n // Seed set and rank. Same four names as the Roles Global; an empty\n // Global falls back to this seed. Payload select options are static.\n options: roleSelectOptions(),\n admin: {\n components: { Field: rolesField ?? ROLES_FIELD },\n description: \"One person can hold several, e.g. Author + Designer.\",\n },\n access: {\n update: isAdmin,\n },\n validate: rolesValidate,\n hooks: {\n beforeValidate: [\n ({ value }) => (Array.isArray(value) ? normalizeStoredRoles(value) : value),\n ],\n },\n },\n ],\n };\n}\n","import type { CollectionConfig, ImageSize } from \"payload\";\n\nimport { canManageContent, isAdmin } from \"../roles/access\";\n\nexport interface MediaOptions {\n /** Where uploads land, relative to the app root. */\n staticDir?: string;\n mimeTypes?: string[];\n /**\n * Renditions sharp cuts on upload, each served at its own URL under\n * `sizes.<name>` on the document. Which sizes a site needs is the site's\n * call. A file uploaded before a size was added has no rendition for it.\n */\n imageSizes?: ImageSize[];\n}\n\n/**\n * Uploads, readable by anyone: the public site serves these files. Writing is\n * an editorial action, deleting is not — removing an asset a live page\n * references breaks that page.\n */\nexport function createMedia({\n staticDir = \"media\",\n mimeTypes = [\"image/*\"],\n imageSizes,\n}: MediaOptions = {}): CollectionConfig {\n return {\n slug: \"media\",\n access: {\n read: () => true,\n create: canManageContent,\n update: canManageContent,\n delete: isAdmin,\n },\n upload: { staticDir, mimeTypes, imageSizes },\n fields: [\n {\n name: \"alt\",\n type: \"text\",\n required: true,\n },\n ],\n };\n}\n","import type { CollectionConfig, GlobalConfig } from \"payload\";\nimport { definePlugin } from \"payload\";\n\nimport { isDeveloperTab } from \"../roles/access\";\n\ntype Entity = CollectionConfig | GlobalConfig;\n\nfunction gateApiTab<T extends Entity>(entity: T): T {\n const components = entity.admin?.components;\n const edit = components?.views?.edit;\n const api = edit && \"api\" in edit ? edit.api : undefined;\n return {\n ...entity,\n admin: {\n ...entity.admin,\n components: {\n ...components,\n views: {\n ...components?.views,\n edit: {\n ...edit,\n api: { ...api, tab: { ...api?.tab, condition: isDeveloperTab } },\n },\n },\n },\n },\n };\n}\n\n/**\n * Gates the document API tab to Developer, config-wide. After plugins that\n * use `definePlugin` order (MCP is 10), so a collection those plugins\n * register is still covered.\n */\nexport const adminOnlyApiTab = definePlugin({\n slug: \"admin-only-api-tab\",\n order: 1000,\n plugin: ({ config }) => ({\n ...config,\n collections: config.collections?.map(gateApiTab),\n globals: config.globals?.map(gateApiTab),\n }),\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"looks-BbL309kO.mjs","names":[],"sources":["../src/theme/map.ts","../src/theme/types.ts","../src/theme/looks.ts"],"sourcesContent":["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. Predicates live in each site's `src/platform`\n * until BIS-43; pass `canManageBrand` as `update` (and typically\n * `isAuthenticated` as `read`).\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\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 { 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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"looks-BbL309kO.mjs","names":[],"sources":["../src/theme/map.ts","../src/theme/types.ts","../src/theme/looks.ts"],"sourcesContent":["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\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 { 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"],"mappings":"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"}
|
|
@@ -108,9 +108,8 @@ interface ThemeUploadDoc {
|
|
|
108
108
|
type EditableTheme = Omit<ThemeConfig, "darkSelector" | "fontWeights">;
|
|
109
109
|
interface CreateThemeOptions {
|
|
110
110
|
/**
|
|
111
|
-
* Required, no default.
|
|
112
|
-
*
|
|
113
|
-
* `isAuthenticated` as `read`).
|
|
111
|
+
* Required, no default. Pass `canManageBrand` as `update` (and typically
|
|
112
|
+
* `isAuthenticated` as `read`). Theme does not read the Roles Global.
|
|
114
113
|
*/
|
|
115
114
|
access: {
|
|
116
115
|
read: Access;
|
|
@@ -240,4 +239,4 @@ declare function pageEditorLooks(doc: ThemeDoc | null | undefined): LookOption[]
|
|
|
240
239
|
declare function pageEditorTokens(doc: ThemeDoc | null | undefined): LookOption[];
|
|
241
240
|
//#endregion
|
|
242
241
|
export { ThemeIdentityFallback as A, ThemeColorDoc as C, ThemeLibraryColorDoc as D, ThemeHeadOptions as E, ThemeUploadDoc as O, THEME_TYPOGRAPHY_SLUG as S, ThemeHead as T, SystemColorKey as _, pageEditorTokens as a, THEME_IDENTITY_SLUG as b, LookFieldOptions as c, deleteLibraryColor as d, rewriteColorToken as f, SYSTEM_COLOR_KEYS as g, EditableTheme as h, pageEditorLooks as i, resolveThemeIdentity as j, ResolvedThemeIdentity as k, colorTokenField as l, CreateThemeOptions as m, PAGE_EDITOR_SYSTEM_KEYS as n, themeLibraryFromDoc as o, themeColorKeys as p, PageEditorSystemKey as r, LookFieldMode as s, LookOption as t, lookField as u, THEME_APPEARANCE_SLUG as v, ThemeDoc as w, THEME_SLUG as x, THEME_COLORS_SLUG as y };
|
|
243
|
-
//# sourceMappingURL=looks-
|
|
242
|
+
//# sourceMappingURL=looks-CLxXwASa.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"looks-
|
|
1
|
+
{"version":3,"file":"looks-CLxXwASa.d.mts","names":[],"sources":["../src/theme/identity.ts","../src/theme/types.ts","../src/theme/library.ts","../src/theme/look-field.ts","../src/theme/looks.ts"],"mappings":";;;;;UAEiB,kBAAA;EACf,GAAA;EACA,GAAA;AAAA;AAAA,UAGe,qBAAA;EACf,MAAA,GAAS,kBAAA;EACT,IAAA,GAAO,kBAAA;EACP,OAAA,GAAU,kBAAA;AAAA;AAAA,UAGK,qBAAA;EACf,MAAA,EAAQ,kBAAA;EACR,IAAA,EAAM,kBAAA;EACN,OAAA,EAAS,kBAAA;AAAA;;;;;;iBAkBK,oBAAA,CACd,GAAA,EAAK,QAAA,qBACL,QAAA,GAAW,qBAAA,GACV,qBAAA;;;;AAnCH;;;;;AAKA;;UCOiB,aAAA;EACf,GAAA;EACA,UAAA,GAAa,SAAA;EACb,KAAA,GAAQ,UAAA;EACR,KAAA;EACA,OAAA,GAAU,iBAAA;EACV,aAAA,GAAgB,SAAA;AAAA;AAAA,UAGD,oBAAA,SAA6B,aAAA;EAC5C,GAAA;EACA,KAAA;AAAA;AAAA,UAGe,QAAA;EACf,MAAA;IACE,KAAA;MACE,OAAA,GAAU,aAAA;MACV,SAAA,GAAY,aAAA;MACZ,MAAA,GAAS,aAAA;MACT,SAAA,GAAY,aAAA;MACZ,OAAA,GAAU,aAAA;MACV,WAAA,GAAc,aAAA;IAAA;IAEhB,OAAA,GAAU,oBAAA;IACV,SAAA,GAAY,WAAA;EAAA;EAEd,UAAA;IACE,IAAA;IACA,OAAA;EAAA;EAEF,UAAA;IACE,YAAA,GAAe,WAAA;IACf,MAAA,GAAS,WAAA;IACT,MAAA,GAAS,WAAA;IACT,MAAA,GAAS,WAAA;IACT,OAAA,GAAU,WAAA;EAAA;EDbU;ECgBtB,KAAA;IACE,OAAA,GAAU,aAAA;IACV,SAAA,GAAY,aAAA;IACZ,MAAA,GAAS,aAAA;IACT,SAAA,GAAY,aAAA;IACZ,OAAA,GAAU,aAAA;IACV,WAAA,GAAc,aAAA;EAAA;;EAGhB,SAAA,GAAY,WAAA;;EAEZ,MAAA,GAAS,WAAA;EACT,MAAA,GAAS,WAAA;EACT,MAAA,GAAS,WAAA;EACT,OAAA,GAAU,WAAA;EACV,YAAA,GAAe,WAAA;EAjDL;EAmDV,KAAA;IACE,IAAA;IACA,OAAA;EAAA;EAEF,IAAA,qBAAyB,cAAA;EACzB,OAAA,qBAA4B,cAAA;EAC5B,QAAA,qBAA6B,cAAA;AAAA;AAAA,UAGd,cAAA;EACf,EAAA;EACA,GAAA;EACA,GAAA;AAAA;;;AA3DF;;;KAmEY,aAAA,GAAgB,IAAA,CAAK,WAAA;AAAA,UAEhB,kBAAA;EApEf;;;;EAyEA,MAAA;IAAU,IAAA,EAAM,MAAA;IAAQ,MAAA,EAAQ,MAAA;EAAA;EAjEhB;EAmEhB,IAAA,EAAM,WAAA;EAjEU;;;;EAsEhB,WAAA;EA1DiB;EA4DjB,KAAA,YAAiB,SAAA;EA1DN;EA4DX,YAAA;EA1DY;EA4DZ,cAAA;EAvDc;;;;EA4Dd,WAAA;EArDY;;;;;EA2DZ,IAAA;IAAS,UAAA;EAAA;EA7CoB;;;;EAkD7B,QAAA;IAAa,QAAA,GAAW,qBAAA;EAAA;EA9FpB;EAgGJ,SAAA,IAAa,KAAA,EAAO,WAAA,EAAa,GAAA,EAAK,QAAA,YAAoB,OAAA;AAAA;AAAA,UAG3C,gBAAA;EACf,YAAA;EACA,SAAA;EACA,QAAA,GAAW,qBAAA;EAnGG;;;;EAwGd,OAAA,GAAU,oBAAA;AAAA;AAAA,UAGK,SAAA;EACf,GAAA;EACA,QAAA;IAAY,IAAA;IAAc,IAAA;EAAA;EAC1B,QAAA,GAAW,qBAAA;AAAA;AAAA,cAGA,UAAA;AAAA,cACA,iBAAA;AAAA,cACA,qBAAA;AAAA,cACA,qBAAA;AAAA,cACA,mBAAA;AAAA,cAEA,iBAAA;AAAA,KASD,cAAA,WAAyB,iBAAA;;;;;;AD5JrC;;iBEagB,iBAAA,CAAkB,KAAA,UAAe,OAAA,UAAiB,KAAA;AAAA,iBAUlD,cAAA,CAAe,GAAA,EAAK,QAAA;;;;;;;iBAapB,kBAAA,CAAmB,GAAA,EAAK,QAAA,EAAU,GAAA,UAAa,WAAA,YAAuB,QAAA;;;UCtCrE,gBAAA;EACf,IAAA;EACA,KAAA,GAAQ,WAAA;EACR,QAAA;EACA,KAAA,GAAQ,SAAA;EACR,QAAA,GAAW,yBAAA;AAAA;AAAA,KAGD,aAAA;;;;;;;iBA2CI,SAAA,CAAU,SAAA,GAAW,gBAAA,GAAwB,SAAA;;;;iBAO7C,eAAA,CAAgB,SAAA,GAAW,gBAAA,GAAwB,SAAA;;;;;;;cCtDtD,uBAAA;AAAA,KAED,mBAAA,WAA8B,uBAAA;AAAA,UAEzB,UAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,iBAiBc,mBAAA,CAAoB,GAAA,EAAK,QAAA,sBAA8B,oBAAA;;;;;;iBAqBvD,eAAA,CAAgB,GAAA,EAAK,QAAA,sBAA8B,UAAA;;;;;iBAmBnD,gBAAA,CAAiB,GAAA,EAAK,QAAA,sBAA8B,UAAA"}
|
package/dist/theme.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as ThemeIdentityFallback, C as ThemeColorDoc, D as ThemeLibraryColorDoc, E as ThemeHeadOptions, O as ThemeUploadDoc, T as ThemeHead, _ as SystemColorKey, a as pageEditorTokens, 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, t as LookOption, u as lookField, w as ThemeDoc, x as THEME_SLUG } from "./looks-
|
|
1
|
+
import { A as ThemeIdentityFallback, C as ThemeColorDoc, D as ThemeLibraryColorDoc, E as ThemeHeadOptions, O as ThemeUploadDoc, T as ThemeHead, _ as SystemColorKey, a as pageEditorTokens, 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, t as LookOption, u as lookField, w as ThemeDoc, x as THEME_SLUG } from "./looks-CLxXwASa.mjs";
|
|
2
2
|
import { ShadeStep, ThemeConfig } from "@bison-lab/tokens";
|
|
3
3
|
|
|
4
4
|
//#region src/theme/published.d.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bison-lab/payload-core",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Site-agnostic Payload CMS configuration for Bison Lab sites: the SEO tab, the Theme global, the brand-assets cupboard, and the metadata and theme readers for the pages they describe",
|
|
3
|
+
"version": "3.12.0",
|
|
4
|
+
"description": "Site-agnostic Payload CMS configuration for Bison Lab sites: the SEO tab, the Theme global, the Roles Global, the brand-assets cupboard, and the metadata and theme readers for the pages they describe",
|
|
5
5
|
"homepage": "https://components.bisonlab.ai",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -84,9 +84,9 @@
|
|
|
84
84
|
"tsdown": "^0.21.0",
|
|
85
85
|
"typescript": "^5.9.3",
|
|
86
86
|
"vitest": "^4.1.2",
|
|
87
|
-
"@bison-lab/fonts": "3.6.0",
|
|
88
87
|
"@bison-lab/tokens": "3.11.0",
|
|
89
|
-
"@bison-lab/ui": "3.11.0"
|
|
88
|
+
"@bison-lab/ui": "3.11.0",
|
|
89
|
+
"@bison-lab/fonts": "3.6.0"
|
|
90
90
|
},
|
|
91
91
|
"publishConfig": {
|
|
92
92
|
"access": "public"
|