@bison-lab/payload-core 3.9.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/theme.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { a as SYSTEM_COLOR_KEYS, c as themeConfigFromDoc, i as themeColorKeys, n as deleteLibraryColor, o as THEME_SLUG, r as rewriteColorToken, t as resolveThemeIdentity } from "./identity-D1X0Om4Z.mjs";
1
+ import { a as pageEditorLooks, c as deleteLibraryColor, d as SYSTEM_COLOR_KEYS, h as THEME_SLUG, i as PAGE_EDITOR_SYSTEM_KEYS, l as rewriteColorToken, n as colorTokenField, o as pageEditorTokens, r as lookField, s as themeLibraryFromDoc, t as resolveThemeIdentity, u as themeColorKeys, v as stateFromColorDoc, y as themeConfigFromDoc } from "./identity-DZOb3_Gk.mjs";
2
2
  import { fontFaceCss, fontPreloads } from "@bison-lab/fonts";
3
- import { buildThemeCss, themeFontIds } from "@bison-lab/tokens";
3
+ import { buildThemeCss, generateNamedScaleCSS, lookBindingCss, themeFontIds } from "@bison-lab/tokens";
4
4
  //#region src/theme/published.ts
5
5
  /**
6
6
  * The Theme row, or `seed` when the Global is empty. Theme has no draft
@@ -28,25 +28,61 @@ async function getPublishedIdentity(payload, fallback) {
28
28
  }) ?? {}, fallback);
29
29
  }
30
30
  //#endregion
31
+ //#region src/theme/look-css.ts
32
+ /**
33
+ * Custom `--coral-*` variables plus `[data-look]` bindings for every
34
+ * page-editor family. System bindings always ship so a look key paints
35
+ * before anyone adds a custom scale.
36
+ */
37
+ function themeLookCss(library) {
38
+ const rows = Array.isArray(library) ? library : [];
39
+ const customKeys = [];
40
+ const scaleLines = [];
41
+ for (const row of rows) {
42
+ const key = row.key?.trim();
43
+ if (!key) continue;
44
+ customKeys.push(key);
45
+ const state = stateFromColorDoc(row, row.hex ?? "#000000");
46
+ scaleLines.push(generateNamedScaleCSS(key, state.scale));
47
+ }
48
+ return [scaleLines.length ? `:root {\n${scaleLines.join("\n")}\n}` : "", lookBindingCss([...PAGE_EDITOR_SYSTEM_KEYS, ...customKeys])].filter(Boolean).join("\n\n");
49
+ }
50
+ //#endregion
31
51
  //#region src/theme/head.ts
32
52
  /**
33
53
  * `@font-face` plus `buildThemeCss` as one stylesheet, and the preload
34
54
  * list for the families the config names. A root layout (and the admin
35
55
  * layout, if it should restyle too) renders `css` in a `<style>` and
36
56
  * each preload as `<link rel="preload" as="font">`.
57
+ *
58
+ * `library` adds custom `--coral-*` variables and `[data-look]` bindings
59
+ * so a Color Settings scale paints the moment it is published.
37
60
  */
38
61
  function themeHead(config, options = {}) {
39
62
  const fontsBaseUrl = options.fontsBaseUrl ?? "/fonts";
40
63
  const ids = themeFontIds(config);
41
64
  const faces = fontFaceCss(ids, fontsBaseUrl);
42
65
  const theme = buildThemeCss(config, { attribute: options.attribute });
66
+ const looks = themeLookCss(options.library);
67
+ const body = looks ? `${theme}\n\n${looks}` : theme;
43
68
  return {
44
- css: faces ? `${faces}\n\n${theme}` : theme,
69
+ css: faces ? `${faces}\n\n${body}` : body,
45
70
  preloads: fontPreloads(ids, fontsBaseUrl),
46
71
  identity: options.identity
47
72
  };
48
73
  }
74
+ /**
75
+ * `themeHead` plus the custom scales on the Theme document. Sites that
76
+ * still call `themeHead(getPublishedTheme())` keep working; they miss
77
+ * `--coral-*` until they pass `library` or switch here.
78
+ */
79
+ function themeHeadFromDoc(doc, seed, options = {}) {
80
+ return themeHead(themeConfigFromDoc(doc, seed), {
81
+ ...options,
82
+ library: themeLibraryFromDoc(doc)
83
+ });
84
+ }
49
85
  //#endregion
50
- export { SYSTEM_COLOR_KEYS, THEME_SLUG, deleteLibraryColor, getPublishedIdentity, getPublishedTheme, resolveThemeIdentity, rewriteColorToken, themeColorKeys, themeConfigFromDoc, themeHead };
86
+ export { PAGE_EDITOR_SYSTEM_KEYS, SYSTEM_COLOR_KEYS, THEME_SLUG, colorTokenField, deleteLibraryColor, getPublishedIdentity, getPublishedTheme, lookField, pageEditorLooks, pageEditorTokens, resolveThemeIdentity, rewriteColorToken, themeColorKeys, themeConfigFromDoc, themeHead, themeHeadFromDoc, themeLibraryFromDoc };
51
87
 
52
88
  //# sourceMappingURL=theme.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme.mjs","names":[],"sources":["../src/theme/published.ts","../src/theme/head.ts"],"sourcesContent":["import type { ThemeConfig } from \"@bison-lab/tokens\";\n\nimport { resolveThemeIdentity, type ResolvedThemeIdentity, type ThemeIdentityFallback } from \"./identity\";\nimport { themeConfigFromDoc } from \"./map\";\nimport { THEME_SLUG } from \"./types\";\nimport type { ThemeDoc } from \"./types\";\n\nexport interface ThemePayload {\n findGlobal: (args: {\n slug: string;\n draft?: boolean;\n depth?: number;\n overrideAccess?: boolean;\n }) => Promise<ThemeDoc | null | undefined>;\n}\n\n/**\n * The Theme row, or `seed` when the Global is empty. Theme has no draft\n * mode: `draft: false` is the live document. Access is overridden so a\n * logged-out request still gets the colours.\n */\nexport async function getPublishedTheme(payload: ThemePayload, seed: ThemeConfig): Promise<ThemeConfig> {\n const doc = await payload.findGlobal({\n slug: THEME_SLUG,\n draft: false,\n depth: 0,\n overrideAccess: true,\n });\n return themeConfigFromDoc(doc ?? {}, seed);\n}\n\n/**\n * Uploaded logo, favicon, and mobile-menu mark, or the fallback the site\n * passed into `createTheme`. Depth 1 so the upload url is populated.\n */\nexport async function getPublishedIdentity(\n payload: ThemePayload,\n fallback?: ThemeIdentityFallback,\n): Promise<ResolvedThemeIdentity> {\n const doc = await payload.findGlobal({\n slug: THEME_SLUG,\n draft: false,\n depth: 1,\n overrideAccess: true,\n });\n return resolveThemeIdentity(doc ?? {}, fallback);\n}\n","import { fontFaceCss, fontPreloads } from \"@bison-lab/fonts\";\nimport { buildThemeCss, themeFontIds, type ThemeConfig } from \"@bison-lab/tokens\";\n\nimport type { ThemeHead, ThemeHeadOptions } from \"./types\";\n\n/**\n * `@font-face` plus `buildThemeCss` as one stylesheet, and the preload\n * list for the families the config names. A root layout (and the admin\n * layout, if it should restyle too) renders `css` in a `<style>` and\n * each preload as `<link rel=\"preload\" as=\"font\">`.\n */\nexport function themeHead(config: ThemeConfig, options: ThemeHeadOptions = {}): ThemeHead {\n const fontsBaseUrl = options.fontsBaseUrl ?? \"/fonts\";\n const ids = themeFontIds(config);\n const faces = fontFaceCss(ids, fontsBaseUrl);\n const theme = buildThemeCss(config, { attribute: options.attribute });\n return {\n css: faces ? `${faces}\\n\\n${theme}` : theme,\n preloads: fontPreloads(ids, fontsBaseUrl),\n identity: options.identity,\n };\n}\n"],"mappings":";;;;;;;;;AAqBA,eAAsB,kBAAkB,SAAuB,MAAyC;AAOtG,QAAO,mBANK,MAAM,QAAQ,WAAW;EACnC,MAAA;EACA,OAAO;EACP,OAAO;EACP,gBAAgB;EACjB,CAAC,IAC+B,EAAE,EAAE,KAAK;;;;;;AAO5C,eAAsB,qBACpB,SACA,UACgC;AAOhC,QAAO,qBANK,MAAM,QAAQ,WAAW;EACnC,MAAA;EACA,OAAO;EACP,OAAO;EACP,gBAAgB;EACjB,CAAC,IACiC,EAAE,EAAE,SAAS;;;;;;;;;;AClClD,SAAgB,UAAU,QAAqB,UAA4B,EAAE,EAAa;CACxF,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,MAAM,aAAa,OAAO;CAChC,MAAM,QAAQ,YAAY,KAAK,aAAa;CAC5C,MAAM,QAAQ,cAAc,QAAQ,EAAE,WAAW,QAAQ,WAAW,CAAC;AACrE,QAAO;EACL,KAAK,QAAQ,GAAG,MAAM,MAAM,UAAU;EACtC,UAAU,aAAa,KAAK,aAAa;EACzC,UAAU,QAAQ;EACnB"}
1
+ {"version":3,"file":"theme.mjs","names":[],"sources":["../src/theme/published.ts","../src/theme/look-css.ts","../src/theme/head.ts"],"sourcesContent":["import type { ThemeConfig } from \"@bison-lab/tokens\";\n\nimport { resolveThemeIdentity, type ResolvedThemeIdentity, type ThemeIdentityFallback } from \"./identity\";\nimport { themeConfigFromDoc } from \"./map\";\nimport { THEME_SLUG } from \"./types\";\nimport type { ThemeDoc } from \"./types\";\n\nexport interface ThemePayload {\n findGlobal: (args: {\n slug: string;\n draft?: boolean;\n depth?: number;\n overrideAccess?: boolean;\n }) => Promise<ThemeDoc | null | undefined>;\n}\n\n/**\n * The Theme row, or `seed` when the Global is empty. Theme has no draft\n * mode: `draft: false` is the live document. Access is overridden so a\n * logged-out request still gets the colours.\n */\nexport async function getPublishedTheme(payload: ThemePayload, seed: ThemeConfig): Promise<ThemeConfig> {\n const doc = await payload.findGlobal({\n slug: THEME_SLUG,\n draft: false,\n depth: 0,\n overrideAccess: true,\n });\n return themeConfigFromDoc(doc ?? {}, seed);\n}\n\n/**\n * Uploaded logo, favicon, and mobile-menu mark, or the fallback the site\n * passed into `createTheme`. Depth 1 so the upload url is populated.\n */\nexport async function getPublishedIdentity(\n payload: ThemePayload,\n fallback?: ThemeIdentityFallback,\n): Promise<ResolvedThemeIdentity> {\n const doc = await payload.findGlobal({\n slug: THEME_SLUG,\n draft: false,\n depth: 1,\n overrideAccess: true,\n });\n return resolveThemeIdentity(doc ?? {}, fallback);\n}\n","import { generateNamedScaleCSS, lookBindingCss } from \"@bison-lab/tokens\";\n\nimport { PAGE_EDITOR_SYSTEM_KEYS } from \"./looks\";\nimport { stateFromColorDoc } from \"./map\";\nimport type { ThemeLibraryColorDoc } from \"./types\";\n\n/**\n * Custom `--coral-*` variables plus `[data-look]` bindings for every\n * page-editor family. System bindings always ship so a look key paints\n * before anyone adds a custom scale.\n */\nexport function themeLookCss(library?: ThemeLibraryColorDoc[] | null): string {\n const rows = Array.isArray(library) ? library : [];\n const customKeys: string[] = [];\n const scaleLines: string[] = [];\n\n for (const row of rows) {\n const key = row.key?.trim();\n if (!key) continue;\n customKeys.push(key);\n const state = stateFromColorDoc(row, row.hex ?? \"#000000\");\n scaleLines.push(generateNamedScaleCSS(key, state.scale));\n }\n\n const scales = scaleLines.length ? `:root {\\n${scaleLines.join(\"\\n\")}\\n}` : \"\";\n const bindings = lookBindingCss([...PAGE_EDITOR_SYSTEM_KEYS, ...customKeys]);\n return [scales, bindings].filter(Boolean).join(\"\\n\\n\");\n}\n","import { fontFaceCss, fontPreloads } from \"@bison-lab/fonts\";\nimport { buildThemeCss, themeFontIds, type ThemeConfig } from \"@bison-lab/tokens\";\n\nimport { themeLookCss } from \"./look-css\";\nimport { themeLibraryFromDoc } from \"./looks\";\nimport { themeConfigFromDoc } from \"./map\";\nimport type { ThemeDoc, ThemeHead, ThemeHeadOptions } from \"./types\";\n\n/**\n * `@font-face` plus `buildThemeCss` as one stylesheet, and the preload\n * list for the families the config names. A root layout (and the admin\n * layout, if it should restyle too) renders `css` in a `<style>` and\n * each preload as `<link rel=\"preload\" as=\"font\">`.\n *\n * `library` adds custom `--coral-*` variables and `[data-look]` bindings\n * so a Color Settings scale paints the moment it is published.\n */\nexport function themeHead(config: ThemeConfig, options: ThemeHeadOptions = {}): ThemeHead {\n const fontsBaseUrl = options.fontsBaseUrl ?? \"/fonts\";\n const ids = themeFontIds(config);\n const faces = fontFaceCss(ids, fontsBaseUrl);\n const theme = buildThemeCss(config, { attribute: options.attribute });\n const looks = themeLookCss(options.library);\n const body = looks ? `${theme}\\n\\n${looks}` : theme;\n return {\n css: faces ? `${faces}\\n\\n${body}` : body,\n preloads: fontPreloads(ids, fontsBaseUrl),\n identity: options.identity,\n };\n}\n\n/**\n * `themeHead` plus the custom scales on the Theme document. Sites that\n * still call `themeHead(getPublishedTheme())` keep working; they miss\n * `--coral-*` until they pass `library` or switch here.\n */\nexport function themeHeadFromDoc(\n doc: ThemeDoc | null | undefined,\n seed: ThemeConfig,\n options: Omit<ThemeHeadOptions, \"library\"> = {},\n): ThemeHead {\n return themeHead(themeConfigFromDoc(doc, seed), {\n ...options,\n library: themeLibraryFromDoc(doc),\n });\n}\n"],"mappings":";;;;;;;;;AAqBA,eAAsB,kBAAkB,SAAuB,MAAyC;AAOtG,QAAO,mBANK,MAAM,QAAQ,WAAW;EACnC,MAAA;EACA,OAAO;EACP,OAAO;EACP,gBAAgB;EACjB,CAAC,IAC+B,EAAE,EAAE,KAAK;;;;;;AAO5C,eAAsB,qBACpB,SACA,UACgC;AAOhC,QAAO,qBANK,MAAM,QAAQ,WAAW;EACnC,MAAA;EACA,OAAO;EACP,OAAO;EACP,gBAAgB;EACjB,CAAC,IACiC,EAAE,EAAE,SAAS;;;;;;;;;AClClD,SAAgB,aAAa,SAAiD;CAC5E,MAAM,OAAO,MAAM,QAAQ,QAAQ,GAAG,UAAU,EAAE;CAClD,MAAM,aAAuB,EAAE;CAC/B,MAAM,aAAuB,EAAE;AAE/B,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,MAAM,IAAI,KAAK,MAAM;AAC3B,MAAI,CAAC,IAAK;AACV,aAAW,KAAK,IAAI;EACpB,MAAM,QAAQ,kBAAkB,KAAK,IAAI,OAAO,UAAU;AAC1D,aAAW,KAAK,sBAAsB,KAAK,MAAM,MAAM,CAAC;;AAK1D,QAAO,CAFQ,WAAW,SAAS,YAAY,WAAW,KAAK,KAAK,CAAC,OAAO,IAC3D,eAAe,CAAC,GAAG,yBAAyB,GAAG,WAAW,CAAC,CACnD,CAAC,OAAO,QAAQ,CAAC,KAAK,OAAO;;;;;;;;;;;;;ACTxD,SAAgB,UAAU,QAAqB,UAA4B,EAAE,EAAa;CACxF,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,MAAM,aAAa,OAAO;CAChC,MAAM,QAAQ,YAAY,KAAK,aAAa;CAC5C,MAAM,QAAQ,cAAc,QAAQ,EAAE,WAAW,QAAQ,WAAW,CAAC;CACrE,MAAM,QAAQ,aAAa,QAAQ,QAAQ;CAC3C,MAAM,OAAO,QAAQ,GAAG,MAAM,MAAM,UAAU;AAC9C,QAAO;EACL,KAAK,QAAQ,GAAG,MAAM,MAAM,SAAS;EACrC,UAAU,aAAa,KAAK,aAAa;EACzC,UAAU,QAAQ;EACnB;;;;;;;AAQH,SAAgB,iBACd,KACA,MACA,UAA6C,EAAE,EACpC;AACX,QAAO,UAAU,mBAAmB,KAAK,KAAK,EAAE;EAC9C,GAAG;EACH,SAAS,oBAAoB,IAAI;EAClC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bison-lab/payload-core",
3
- "version": "3.9.0",
3
+ "version": "3.11.0",
4
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",
5
5
  "homepage": "https://components.bisonlab.ai",
6
6
  "repository": {
@@ -76,6 +76,7 @@
76
76
  "@testing-library/jest-dom": "^6.9.1",
77
77
  "@testing-library/react": "^16.3.2",
78
78
  "@types/react": "^19.2.14",
79
+ "@types/react-dom": "^19.2.3",
79
80
  "jsdom": "^29.0.1",
80
81
  "payload": "^3.88.0",
81
82
  "react": "^19.2.4",
@@ -84,8 +85,8 @@
84
85
  "typescript": "^5.9.3",
85
86
  "vitest": "^4.1.2",
86
87
  "@bison-lab/fonts": "3.6.0",
87
- "@bison-lab/tokens": "3.9.0",
88
- "@bison-lab/ui": "3.9.0"
88
+ "@bison-lab/tokens": "3.11.0",
89
+ "@bison-lab/ui": "3.11.0"
89
90
  },
90
91
  "publishConfig": {
91
92
  "access": "public"
@@ -1,198 +0,0 @@
1
- import { SHADE_STEPS, createColorScale, isThemeHex } from "@bison-lab/tokens";
2
- //#region src/theme/fields.ts
3
- const THEME_COLOR_FIELD = "@bison-lab/payload-core/admin#ColorField";
4
- const THEME_COLOR_SCALE_FIELD = "@bison-lab/payload-core/admin#ColorScaleField";
5
- const THEME_LIBRARY_FIELD = "@bison-lab/payload-core/admin#LibraryField";
6
- const THEME_FONT_FIELD = "@bison-lab/payload-core/admin#FontField";
7
- const THEME_PAIRING_FIELD = "@bison-lab/payload-core/admin#PairingField";
8
- const THEME_APPEARANCE_FIELD = "@bison-lab/payload-core/admin#AppearanceField";
9
- const THEME_GREY_SCALE_FIELD = "@bison-lab/payload-core/admin#GreyScaleField";
10
- const THEME_CONTRAST_REPORT = "@bison-lab/payload-core/admin#ContrastReport";
11
- const THEME_SECTION_HEADING = "@bison-lab/payload-core/admin#SectionHeading";
12
- const THEME_PUBLISH_FIELD = "@bison-lab/payload-core/admin#PublishChild";
13
- const THEME_SAVE_BUTTON = "@bison-lab/payload-core/admin#HiddenSaveButton";
14
- function headingSelectValue(heading) {
15
- return heading ?? "";
16
- }
17
- //#endregion
18
- //#region src/theme/map.ts
19
- function pick(value, fallback) {
20
- return value == null ? fallback : value;
21
- }
22
- function headingFromDoc(heading, seed) {
23
- if (heading === void 0) return seed;
24
- if (heading === null || heading === "") return null;
25
- return heading;
26
- }
27
- function hexFromColor(value, fallback) {
28
- if (typeof value === "string") return value || fallback;
29
- return pick(value?.hex, fallback);
30
- }
31
- function colorDocFromHex(hex, sourceStep = 500) {
32
- return colorDocFromState(createColorScale(hex, sourceStep));
33
- }
34
- function colorDocFromState(state) {
35
- return {
36
- hex: state.hex,
37
- sourceStep: state.sourceStep,
38
- scale: state.scale,
39
- stale: state.stale,
40
- include: Array.isArray(state.include) ? "custom" : state.include,
41
- includedSteps: Array.isArray(state.include) ? [...state.include] : void 0
42
- };
43
- }
44
- /**
45
- * Nested Theme document → flat `ThemeConfig`. Null or missing editor
46
- * fields take the seed; `darkSelector` and `fontWeights` always come from
47
- * the seed. Reads the BIS-87 groups and the pre-BIS-87 flat fields.
48
- */
49
- function themeConfigFromDoc(doc, seed) {
50
- const brand = doc?.colors?.brand ?? doc?.brand;
51
- const typography = doc?.typography ?? doc?.fonts;
52
- const appearance = doc?.appearance;
53
- return {
54
- brandPrimary: hexFromColor(brand?.primary, seed.brandPrimary),
55
- brandSecondary: hexFromColor(brand?.secondary, seed.brandSecondary),
56
- brandAccent: hexFromColor(brand?.accent, seed.brandAccent),
57
- brandHighlight: hexFromColor(brand?.highlight, seed.brandHighlight),
58
- brandSuccess: hexFromColor(brand?.success, seed.brandSuccess),
59
- brandDestructive: hexFromColor(brand?.destructive, seed.brandDestructive ?? "") || seed.brandDestructive,
60
- greyScale: pick(doc?.colors?.greyScale ?? doc?.greyScale, seed.greyScale),
61
- radius: pick(appearance?.radius ?? doc?.radius, seed.radius),
62
- shadow: pick(appearance?.shadow ?? doc?.shadow, seed.shadow),
63
- motion: pick(appearance?.motion ?? doc?.motion, seed.motion),
64
- density: pick(appearance?.density ?? doc?.density, seed.density),
65
- defaultTheme: pick(appearance?.defaultTheme ?? doc?.defaultTheme, seed.defaultTheme),
66
- fontBody: pick(typography?.body, seed.fontBody),
67
- fontHeading: headingFromDoc(typography?.heading, seed.fontHeading),
68
- darkSelector: seed.darkSelector,
69
- fontWeights: seed.fontWeights
70
- };
71
- }
72
- /** The document `seedTheme` writes so a fresh Global matches the seed config. */
73
- function docFromConfig(config, destructive) {
74
- const destructiveHex = destructive ?? config.brandDestructive;
75
- if (!destructiveHex) throw new Error("docFromConfig requires a destructive hex on the config or as the second argument");
76
- return {
77
- colors: {
78
- brand: {
79
- primary: colorDocFromHex(config.brandPrimary),
80
- secondary: colorDocFromHex(config.brandSecondary),
81
- accent: colorDocFromHex(config.brandAccent),
82
- highlight: colorDocFromHex(config.brandHighlight),
83
- success: colorDocFromHex(config.brandSuccess),
84
- destructive: colorDocFromHex(destructiveHex)
85
- },
86
- library: [],
87
- greyScale: config.greyScale
88
- },
89
- typography: {
90
- body: config.fontBody,
91
- heading: config.fontHeading
92
- },
93
- appearance: {
94
- defaultTheme: config.defaultTheme,
95
- radius: config.radius,
96
- shadow: config.shadow,
97
- motion: config.motion,
98
- density: config.density
99
- }
100
- };
101
- }
102
- function validateThemeHex(value) {
103
- if (!isThemeHex(value)) return "Enter a six-digit hex colour like #1e3a5f";
104
- return true;
105
- }
106
- function validateSourceIncluded(value, { siblingData }) {
107
- const include = siblingData?.include;
108
- if (include === "all" || include === "source") return true;
109
- const source = Number(siblingData?.sourceStep);
110
- const steps = Array.isArray(value) ? value.map(Number) : [];
111
- if (!SHADE_STEPS.includes(source)) return true;
112
- if (!steps.includes(source)) return "The source step cannot be excluded";
113
- return true;
114
- }
115
- //#endregion
116
- //#region src/theme/types.ts
117
- const THEME_SLUG = "theme";
118
- const SYSTEM_COLOR_KEYS = [
119
- "primary",
120
- "secondary",
121
- "accent",
122
- "highlight",
123
- "success",
124
- "destructive"
125
- ];
126
- //#endregion
127
- //#region src/theme/library.ts
128
- const STEP_SET = new Set(SHADE_STEPS);
129
- /**
130
- * Page editors pick `coral-400`, never a raw hex. Rewrite every use of
131
- * `fromKey` onto `toKey` at the same step: `coral-400` → `highlight-400`.
132
- * A bare key (`coral`) becomes `toKey`. Anything else is left alone.
133
- */
134
- function rewriteColorToken(token, fromKey, toKey) {
135
- if (token === fromKey) return toKey;
136
- const dash = token.lastIndexOf("-");
137
- if (dash <= 0) return token;
138
- const key = token.slice(0, dash);
139
- const step = Number(token.slice(dash + 1));
140
- if (key !== fromKey || !STEP_SET.has(step)) return token;
141
- return `${toKey}-${step}`;
142
- }
143
- function themeColorKeys(doc) {
144
- const custom = (doc?.colors?.library ?? []).map((row) => row.key?.trim()).filter((key) => Boolean(key));
145
- return [...SYSTEM_COLOR_KEYS, ...custom];
146
- }
147
- /**
148
- * Remove a custom color. Unused: omit `replacement` and the row is gone.
149
- * In use: pass another system or custom key; the caller rewrites page
150
- * tokens with `rewriteColorToken`. After this, no library row still
151
- * has `key`.
152
- */
153
- function deleteLibraryColor(doc, key, replacement) {
154
- const library = [...doc.colors?.library ?? []];
155
- const index = library.findIndex((row) => row.key === key);
156
- if (index === -1) throw new Error(`No custom color named ${JSON.stringify(key)}`);
157
- library.splice(index, 1);
158
- const next = {
159
- ...doc,
160
- colors: {
161
- ...doc.colors,
162
- library
163
- }
164
- };
165
- if (replacement !== void 0) {
166
- if (replacement === key) throw new Error("Replacement must be a different color");
167
- if (!themeColorKeys(next).includes(replacement)) throw new Error(`Replacement ${JSON.stringify(replacement)} is not a system or remaining custom color`);
168
- }
169
- return next;
170
- }
171
- //#endregion
172
- //#region src/theme/identity.ts
173
- function fromUpload(value, fallback) {
174
- if (value && typeof value === "object") {
175
- const upload = value;
176
- if (upload.url) return {
177
- url: upload.url,
178
- alt: upload.alt ?? fallback?.alt ?? ""
179
- };
180
- }
181
- return fallback ?? null;
182
- }
183
- /**
184
- * Uploaded logo, favicon, and mobile-menu mark, or the fallback the site
185
- * passed into `createTheme`. Empty fields keep the fallback;
186
- * `--color-primary-mark` is not a Theme field.
187
- */
188
- function resolveThemeIdentity(doc, fallback) {
189
- return {
190
- lockup: fromUpload(doc?.logo, fallback?.lockup),
191
- mark: fromUpload(doc?.logoMark, fallback?.mark),
192
- favicon: fromUpload(doc?.favicon, fallback?.favicon)
193
- };
194
- }
195
- //#endregion
196
- export { headingSelectValue as S, THEME_LIBRARY_FIELD as _, SYSTEM_COLOR_KEYS as a, THEME_SAVE_BUTTON as b, themeConfigFromDoc as c, THEME_APPEARANCE_FIELD as d, THEME_COLOR_FIELD as f, THEME_GREY_SCALE_FIELD as g, THEME_FONT_FIELD as h, themeColorKeys as i, validateSourceIncluded as l, THEME_CONTRAST_REPORT as m, deleteLibraryColor as n, THEME_SLUG as o, THEME_COLOR_SCALE_FIELD as p, rewriteColorToken as r, docFromConfig as s, resolveThemeIdentity as t, validateThemeHex as u, THEME_PAIRING_FIELD as v, THEME_SECTION_HEADING as x, THEME_PUBLISH_FIELD as y };
197
-
198
- //# sourceMappingURL=identity-D1X0Om4Z.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"identity-D1X0Om4Z.mjs","names":[],"sources":["../src/theme/fields.ts","../src/theme/map.ts","../src/theme/types.ts","../src/theme/library.ts","../src/theme/identity.ts"],"sourcesContent":["import type { ThemeConfig } from \"@bison-lab/tokens\";\n\nimport type { EditableTheme } from \"./types\";\n\nexport const THEME_COLOR_FIELD = \"@bison-lab/payload-core/admin#ColorField\";\nexport const THEME_COLOR_SCALE_FIELD = \"@bison-lab/payload-core/admin#ColorScaleField\";\nexport const THEME_LIBRARY_FIELD = \"@bison-lab/payload-core/admin#LibraryField\";\nexport const THEME_FONT_FIELD = \"@bison-lab/payload-core/admin#FontField\";\nexport const THEME_PAIRING_FIELD = \"@bison-lab/payload-core/admin#PairingField\";\nexport const THEME_APPEARANCE_FIELD = \"@bison-lab/payload-core/admin#AppearanceField\";\nexport const THEME_GREY_SCALE_FIELD = \"@bison-lab/payload-core/admin#GreyScaleField\";\nexport const THEME_CONTRAST_REPORT = \"@bison-lab/payload-core/admin#ContrastReport\";\nexport const THEME_SECTION_HEADING = \"@bison-lab/payload-core/admin#SectionHeading\";\nexport const THEME_PUBLISH_FIELD = \"@bison-lab/payload-core/admin#PublishChild\";\nexport const THEME_SAVE_BUTTON = \"@bison-lab/payload-core/admin#HiddenSaveButton\";\n\n/**\n * Document path for each editable `ThemeConfig` key. Locked with `satisfies`\n * so a new config field without a Theme field (or the reverse) fails at\n * compile time. `darkSelector` and `fontWeights` are seed-only.\n */\nexport const THEME_FIELD_PATHS = {\n brandPrimary: \"colors.brand.primary.hex\",\n brandSecondary: \"colors.brand.secondary.hex\",\n brandAccent: \"colors.brand.accent.hex\",\n brandHighlight: \"colors.brand.highlight.hex\",\n brandSuccess: \"colors.brand.success.hex\",\n brandDestructive: \"colors.brand.destructive.hex\",\n greyScale: \"colors.greyScale\",\n defaultTheme: \"appearance.defaultTheme\",\n radius: \"appearance.radius\",\n shadow: \"appearance.shadow\",\n motion: \"appearance.motion\",\n density: \"appearance.density\",\n fontBody: \"typography.body\",\n fontHeading: \"typography.heading\",\n} as const satisfies Record<keyof EditableTheme, string>;\n\nexport type ThemeFieldPath = (typeof THEME_FIELD_PATHS)[keyof typeof THEME_FIELD_PATHS];\n\n/** Empty select value for heading: same family as body. */\nexport const SAME_AS_BODY = \"\";\n\nexport function headingSelectValue(heading: ThemeConfig[\"fontHeading\"]): string {\n return heading ?? SAME_AS_BODY;\n}\n","import {\n createColorScale,\n 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: hexFromColor(brand?.destructive, seed.brandDestructive ?? \"\") || seed.brandDestructive,\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;\n if (!destructiveHex) {\n throw new Error(\"docFromConfig requires a destructive hex on the config or as the second argument\");\n }\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. Required unless `seed.brandDestructive` is set.\n * Not a package default — the site names it.\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 logo, favicon, and mobile-menu mark upload relations when\n * given. Pass `BRAND_ASSETS_SLUG` (or the slug you gave\n * `createBrandAssets`). Identity is a Theme child with its own Publish.\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\nexport interface ThemeHead {\n css: string;\n preloads: { href: string; type: \"font/woff2\" }[];\n identity?: ResolvedThemeIdentity;\n}\n\nexport const THEME_SLUG = \"theme\";\n\nexport const SYSTEM_COLOR_KEYS = [\n \"primary\",\n \"secondary\",\n \"accent\",\n \"highlight\",\n \"success\",\n \"destructive\",\n] as const;\n\nexport type SystemColorKey = (typeof SYSTEM_COLOR_KEYS)[number];\n","import { SHADE_STEPS, type ShadeStep } from \"@bison-lab/tokens\";\n\nimport { SYSTEM_COLOR_KEYS, type ThemeDoc } from \"./types\";\n\nconst STEP_SET = new Set<number>(SHADE_STEPS);\n\n/**\n * Page editors pick `coral-400`, never a raw hex. Rewrite every use of\n * `fromKey` onto `toKey` at the same step: `coral-400` → `highlight-400`.\n * A bare key (`coral`) becomes `toKey`. Anything else is left alone.\n */\nexport function rewriteColorToken(token: string, fromKey: string, toKey: string): string {\n if (token === fromKey) return toKey;\n const dash = token.lastIndexOf(\"-\");\n if (dash <= 0) return token;\n const key = token.slice(0, dash);\n const step = Number(token.slice(dash + 1));\n if (key !== fromKey || !STEP_SET.has(step)) return token;\n return `${toKey}-${step as ShadeStep}`;\n}\n\nexport function themeColorKeys(doc: ThemeDoc | null | undefined): string[] {\n const custom = (doc?.colors?.library ?? [])\n .map((row) => row.key?.trim())\n .filter((key): key is string => Boolean(key));\n return [...SYSTEM_COLOR_KEYS, ...custom];\n}\n\n/**\n * Remove a custom color. Unused: omit `replacement` and the row is gone.\n * In use: pass another system or custom key; the caller rewrites page\n * tokens with `rewriteColorToken`. After this, no library row still\n * has `key`.\n */\nexport function deleteLibraryColor(doc: ThemeDoc, key: string, replacement?: string): ThemeDoc {\n const library = [...(doc.colors?.library ?? [])];\n const index = library.findIndex((row) => row.key === key);\n if (index === -1) {\n throw new Error(`No custom color named ${JSON.stringify(key)}`);\n }\n library.splice(index, 1);\n const next: ThemeDoc = {\n ...doc,\n colors: { ...doc.colors, library },\n };\n if (replacement !== undefined) {\n if (replacement === key) {\n throw new Error(\"Replacement must be a different color\");\n }\n if (!themeColorKeys(next).includes(replacement)) {\n throw new Error(`Replacement ${JSON.stringify(replacement)} is not a system or remaining custom color`);\n }\n }\n return next;\n}\n","import type { ThemeDoc, ThemeUploadDoc } from \"./types\";\n\nexport interface ThemeIdentityAsset {\n url: string;\n alt: string;\n}\n\nexport interface ThemeIdentityFallback {\n lockup?: ThemeIdentityAsset;\n mark?: ThemeIdentityAsset;\n favicon?: ThemeIdentityAsset;\n}\n\nexport interface ResolvedThemeIdentity {\n lockup: ThemeIdentityAsset | null;\n mark: ThemeIdentityAsset | null;\n favicon: ThemeIdentityAsset | null;\n}\n\nfunction fromUpload(value: ThemeDoc[\"logo\"], fallback?: ThemeIdentityAsset): ThemeIdentityAsset | null {\n if (value && typeof value === \"object\") {\n const upload = value as ThemeUploadDoc;\n if (upload.url) {\n return { url: upload.url, alt: upload.alt ?? fallback?.alt ?? \"\" };\n }\n }\n return fallback ?? null;\n}\n\n/**\n * Uploaded logo, favicon, and mobile-menu mark, or the fallback the site\n * passed into `createTheme`. Empty fields keep the fallback;\n * `--color-primary-mark` is not a Theme field.\n */\nexport function resolveThemeIdentity(\n doc: ThemeDoc | null | undefined,\n fallback?: ThemeIdentityFallback,\n): ResolvedThemeIdentity {\n return {\n lockup: fromUpload(doc?.logo, fallback?.lockup),\n mark: fromUpload(doc?.logoMark, fallback?.mark),\n favicon: fromUpload(doc?.favicon, fallback?.favicon),\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"library-BGq4wGif.d.mts","names":[],"sources":["../src/theme/identity.ts","../src/theme/types.ts","../src/theme/library.ts"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"map-BUDkX8g1.mjs","names":[],"sources":["../src/theme/map.ts"],"sourcesContent":["import {\n createColorScale,\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: hexFromColor(brand?.destructive, seed.brandDestructive ?? \"\") || seed.brandDestructive,\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;\n if (!destructiveHex) {\n throw new Error(\"docFromConfig requires a destructive hex on the config or as the second argument\");\n }\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"],"mappings":"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"}