@tenphi/docs 0.16.0 → 0.17.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 CHANGED
@@ -27,4 +27,4 @@ extraction root.
27
27
 
28
28
  Most applications should install `@tenphi/cookbook`, which combines this engine
29
29
  with the supported Astro/Starlight renderer. This package is useful for custom
30
- tooling, validation, and alternate renderers. Node.js 22.14 or newer is required.
30
+ tooling, validation, and alternate renderers. Node.js 22.19 or newer is required.
@@ -1,4 +1,4 @@
1
- import { A as MarkdownConfig, B as SiteConfig, D as HeadConfig, E as EditLinkConfig, F as NormalizedDocsConfig, G as ThemeTokens, H as ThemeConfig, K as TypographyPreset, M as NavigationItem, N as NavigationPlacement, O as HeaderLink, P as NavigationTab, T as DocsSource, U as ThemePaletteConfig, V as SiteIconConfig, W as ThemeTokenValue, _ as DocsConfig, a as ComponentStyleConfig, c as ComponentsConfig, i as COOKBOOK_COMPONENT_SUB_ELEMENTS, j as NavigationConfig, k as LocaleConfig, l as ContentConfig, n as BuildConfig, o as ComponentStyles, q as TypographyPresets, r as COOKBOOK_COMPONENT_NAMES, s as ComponentStylesConfig, t as BrandConfig, u as CookbookComponentName, v as DocsDiagnostic, z as SearchConfig } from "../types-CH6E2v-J.js";
1
+ import { $ as TypographyPresets, A as LocaleConfig, B as SearchConfig, D as EditLinkConfig, E as DocsSource, F as NavigationTab, G as ThemeFont, H as SiteIconConfig, I as NormalizedDocsConfig, J as ThemePaletteColor, K as ThemeFontFile, M as NavigationConfig, N as NavigationItem, O as HeadConfig, P as NavigationPlacement, Q as TypographyPreset, V as SiteConfig, W as ThemeConfig, X as ThemeTokenValue, Y as ThemePaletteConfig, Z as ThemeTokens, a as COOKBOOK_COMPONENT_SUB_ELEMENTS, c as ComponentStylesConfig, d as CookbookComponentName, i as COOKBOOK_COMPONENT_NAMES, j as MarkdownConfig, k as HeaderLink, l as ComponentsConfig, n as BrandDeclaration, o as ComponentStyleConfig, q as ThemeFonts, r as BuildConfig, s as ComponentStyles, t as BrandConfig, u as ContentConfig, v as DocsConfig, y as DocsDiagnostic } from "../types-C-AgUcAb.js";
2
2
  //#region src/config/index.d.ts
3
3
  declare class DocsConfigError extends Error {
4
4
  readonly diagnostics: DocsDiagnostic[];
@@ -7,8 +7,8 @@ declare class DocsConfigError extends Error {
7
7
  declare function validateConfig(config: DocsConfig): DocsDiagnostic[];
8
8
  declare function normalizeDocsConfig(config?: DocsConfig): NormalizedDocsConfig;
9
9
  declare function defineDocsConfig(config: DocsConfig): DocsConfig;
10
- /** Compose presets. Objects merge, arrays replace, and styles use Tasty semantics. */
10
+ /** Compose presets. Color roles and fonts replace, other objects merge, and styles use Tasty semantics. */
11
11
  declare function mergeDocsConfig(...configs: DocsConfig[]): DocsConfig;
12
12
  //#endregion
13
- export { type BrandConfig, type BuildConfig, COOKBOOK_COMPONENT_NAMES, COOKBOOK_COMPONENT_SUB_ELEMENTS, type ComponentStyleConfig, type ComponentStyles, type ComponentStylesConfig, type ComponentsConfig, type ContentConfig, type CookbookComponentName, type DocsConfig, DocsConfigError, type DocsSource, type EditLinkConfig, type HeadConfig, type HeaderLink, type LocaleConfig, type MarkdownConfig, type NavigationConfig, type NavigationItem, type NavigationPlacement, type NavigationTab, type NormalizedDocsConfig, type SearchConfig, type SiteConfig, type SiteIconConfig, type ThemeConfig, type ThemePaletteConfig, type ThemeTokenValue, type ThemeTokens, type TypographyPreset, type TypographyPresets, defineDocsConfig, mergeDocsConfig, normalizeDocsConfig, validateConfig };
13
+ export { type BrandConfig, type BrandDeclaration, type BuildConfig, COOKBOOK_COMPONENT_NAMES, COOKBOOK_COMPONENT_SUB_ELEMENTS, type ComponentStyleConfig, type ComponentStyles, type ComponentStylesConfig, type ComponentsConfig, type ContentConfig, type CookbookComponentName, type DocsConfig, DocsConfigError, type DocsSource, type EditLinkConfig, type HeadConfig, type HeaderLink, type LocaleConfig, type MarkdownConfig, type NavigationConfig, type NavigationItem, type NavigationPlacement, type NavigationTab, type NormalizedDocsConfig, type SearchConfig, type SiteConfig, type SiteIconConfig, type ThemeConfig, type ThemeFont, type ThemeFontFile, type ThemeFonts, type ThemePaletteColor, type ThemePaletteConfig, type ThemeTokenValue, type ThemeTokens, type TypographyPreset, type TypographyPresets, defineDocsConfig, mergeDocsConfig, normalizeDocsConfig, validateConfig };
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/config/index.ts"],"mappings":";;cA0Ea,wBAAwB;WAC1B,aAAa;EAEtB,YAAY,aAAa;;iBAOX,eAAe,QAAQ,aAAa;iBAmqBpC,oBACd,SAAQ,aACP;iBA4Ca,iBAAiB,QAAQ,aAAa;;iBAOtC,mBAAmB,SAAS,eAAe"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/config/index.ts"],"mappings":";;cA8Fa,wBAAwB;WAC1B,aAAa;EAEtB,YAAY,aAAa;;iBAOX,eAAe,QAAQ,aAAa;iBA23BpC,oBACd,SAAQ,aACP;iBA4Ca,iBAAiB,QAAQ,aAAa;;iBAOtC,mBAAmB,SAAS,eAAe"}
@@ -1,2 +1,2 @@
1
- import { a as validateConfig, i as normalizeDocsConfig, n as defineDocsConfig, o as COOKBOOK_COMPONENT_NAMES, r as mergeDocsConfig, s as COOKBOOK_COMPONENT_SUB_ELEMENTS, t as DocsConfigError } from "../config-CmoSqvii.js";
1
+ import { a as validateConfig, i as normalizeDocsConfig, n as defineDocsConfig, o as COOKBOOK_COMPONENT_NAMES, r as mergeDocsConfig, s as COOKBOOK_COMPONENT_SUB_ELEMENTS, t as DocsConfigError } from "../config-Ci9Nv0wQ.js";
2
2
  export { COOKBOOK_COMPONENT_NAMES, COOKBOOK_COMPONENT_SUB_ELEMENTS, DocsConfigError, defineDocsConfig, mergeDocsConfig, normalizeDocsConfig, validateConfig };
@@ -15,6 +15,19 @@
15
15
  "version": {
16
16
  "type": "string"
17
17
  },
18
+ "versions": {
19
+ "type": "array",
20
+ "minItems": 2,
21
+ "items": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "required": ["label", "routeBase"],
25
+ "properties": {
26
+ "label": { "type": "string", "minLength": 1 },
27
+ "routeBase": { "type": "string", "pattern": "^/(?!/)" }
28
+ }
29
+ }
30
+ },
18
31
  "description": {
19
32
  "type": "string"
20
33
  },
@@ -179,20 +192,29 @@
179
192
  "type": "object",
180
193
  "additionalProperties": false,
181
194
  "properties": {
182
- "brand": {},
195
+ "brand": { "$ref": "#/$defs/brandColor" },
183
196
  "palette": {
184
197
  "type": "object",
185
198
  "additionalProperties": false,
186
199
  "properties": {
187
- "surface": {},
188
- "text": {},
189
- "textSoft": {},
190
- "info": {},
191
- "success": {},
192
- "warning": {},
193
- "danger": {},
194
- "header": {},
195
- "overlay": {}
200
+ "surface": { "$ref": "#/$defs/paletteColor" },
201
+ "text": { "$ref": "#/$defs/paletteColor" },
202
+ "textSoft": { "$ref": "#/$defs/paletteColor" },
203
+ "info": { "$ref": "#/$defs/paletteColor" },
204
+ "success": { "$ref": "#/$defs/paletteColor" },
205
+ "warning": { "$ref": "#/$defs/paletteColor" },
206
+ "danger": { "$ref": "#/$defs/paletteColor" },
207
+ "header": { "$ref": "#/$defs/paletteColor" },
208
+ "overlay": { "$ref": "#/$defs/paletteColor" }
209
+ }
210
+ },
211
+ "fonts": {
212
+ "type": "object",
213
+ "additionalProperties": false,
214
+ "properties": {
215
+ "body": { "$ref": "#/$defs/themeFont" },
216
+ "heading": { "$ref": "#/$defs/themeFont" },
217
+ "code": { "$ref": "#/$defs/themeFont" }
196
218
  }
197
219
  },
198
220
  "states": {
@@ -356,6 +378,14 @@
356
378
  }
357
379
  }
358
380
  },
381
+ "VersionSwitcher": {
382
+ "type": "object",
383
+ "propertyNames": {
384
+ "not": {
385
+ "const": "mode"
386
+ }
387
+ }
388
+ },
359
389
  "Preview": {
360
390
  "type": "object",
361
391
  "propertyNames": {
@@ -609,6 +639,207 @@
609
639
  }
610
640
  },
611
641
  "$defs": {
642
+ "brandColor": {
643
+ "description": "A Glaze from declaration for the brand seed, optionally with APCA targets. Literal shorthand and structured hue, saturation, and tone are also supported.",
644
+ "oneOf": [
645
+ { "$ref": "#/$defs/glazeColorValue" },
646
+ {
647
+ "type": "object",
648
+ "additionalProperties": false,
649
+ "required": ["from"],
650
+ "properties": {
651
+ "from": { "$ref": "#/$defs/glazeColorValue" },
652
+ "contrast": { "$ref": "#/$defs/brandContrast" },
653
+ "unsafeContrast": { "type": "boolean" }
654
+ }
655
+ },
656
+ {
657
+ "type": "object",
658
+ "additionalProperties": false,
659
+ "required": ["hue", "saturation", "tone"],
660
+ "properties": {
661
+ "hue": { "type": "number", "minimum": 0, "maximum": 360 },
662
+ "saturation": { "type": "number", "minimum": 0, "maximum": 100 },
663
+ "tone": { "type": "number", "minimum": 0, "maximum": 100 },
664
+ "contrast": { "$ref": "#/$defs/brandContrast" },
665
+ "unsafeContrast": { "type": "boolean" }
666
+ }
667
+ }
668
+ ]
669
+ },
670
+ "brandContrast": {
671
+ "type": "object",
672
+ "additionalProperties": false,
673
+ "required": ["apca"],
674
+ "properties": {
675
+ "apca": {
676
+ "oneOf": [
677
+ { "type": "number" },
678
+ {
679
+ "type": "array",
680
+ "prefixItems": [{ "type": "number" }, { "type": "number" }],
681
+ "minItems": 2,
682
+ "maxItems": 2
683
+ }
684
+ ]
685
+ }
686
+ }
687
+ },
688
+ "paletteColor": {
689
+ "description": "A Glaze color declaration; literal colors are convenient seed shorthands.",
690
+ "oneOf": [
691
+ { "$ref": "#/$defs/glazeColorValue" },
692
+ { "$ref": "#/$defs/glazeDeclaration" }
693
+ ]
694
+ },
695
+ "glazeColorValue": {
696
+ "oneOf": [
697
+ { "type": "string" },
698
+ {
699
+ "type": "object",
700
+ "additionalProperties": false,
701
+ "required": ["h", "s", "l"],
702
+ "properties": {
703
+ "h": { "type": "number" },
704
+ "s": { "type": "number" },
705
+ "l": { "type": "number" }
706
+ }
707
+ },
708
+ {
709
+ "type": "object",
710
+ "additionalProperties": false,
711
+ "required": ["h", "s", "t"],
712
+ "properties": {
713
+ "h": { "type": "number" },
714
+ "s": { "type": "number" },
715
+ "t": { "type": "number" }
716
+ }
717
+ },
718
+ {
719
+ "type": "object",
720
+ "additionalProperties": false,
721
+ "required": ["r", "g", "b"],
722
+ "properties": {
723
+ "r": { "type": "number" },
724
+ "g": { "type": "number" },
725
+ "b": { "type": "number" }
726
+ }
727
+ },
728
+ {
729
+ "type": "object",
730
+ "additionalProperties": false,
731
+ "required": ["l", "c", "h"],
732
+ "properties": {
733
+ "l": { "type": "number" },
734
+ "c": { "type": "number" },
735
+ "h": { "type": "number" }
736
+ }
737
+ }
738
+ ]
739
+ },
740
+ "glazeDeclaration": {
741
+ "type": "object",
742
+ "additionalProperties": false,
743
+ "properties": {
744
+ "from": { "$ref": "#/$defs/glazeColorValue" },
745
+ "tone": {
746
+ "oneOf": [
747
+ { "type": "number", "minimum": 0, "maximum": 100 },
748
+ { "type": "string" },
749
+ {
750
+ "type": "array",
751
+ "prefixItems": [
752
+ { "oneOf": [{ "type": "number" }, { "type": "string" }] },
753
+ { "oneOf": [{ "type": "number" }, { "type": "string" }] }
754
+ ],
755
+ "minItems": 2,
756
+ "maxItems": 2
757
+ }
758
+ ]
759
+ },
760
+ "saturation": { "type": "number", "minimum": 0, "maximum": 1 },
761
+ "hue": { "oneOf": [{ "type": "number" }, { "type": "string" }] },
762
+ "darkHue": { "oneOf": [{ "type": "number" }, { "type": "string" }] },
763
+ "darkSaturation": { "type": "number", "minimum": 0, "maximum": 1 },
764
+ "base": { "type": "string" },
765
+ "contrast": {
766
+ "oneOf": [
767
+ { "type": "number" },
768
+ { "type": "string" },
769
+ {
770
+ "type": "array",
771
+ "minItems": 2,
772
+ "maxItems": 2
773
+ },
774
+ {
775
+ "type": "object",
776
+ "additionalProperties": false,
777
+ "properties": {
778
+ "apca": {},
779
+ "wcag": {}
780
+ }
781
+ }
782
+ ]
783
+ },
784
+ "mode": { "enum": ["auto", "fixed", "static"] },
785
+ "autoFlip": { "type": "boolean" },
786
+ "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
787
+ "pastel": { "type": "boolean" },
788
+ "role": { "type": "string" },
789
+ "inherit": { "type": "boolean" }
790
+ }
791
+ },
792
+ "themeFont": {
793
+ "oneOf": [
794
+ { "type": "string", "minLength": 1 },
795
+ {
796
+ "type": "object",
797
+ "additionalProperties": false,
798
+ "required": ["google"],
799
+ "properties": {
800
+ "google": { "type": "string", "minLength": 1 },
801
+ "weights": {
802
+ "type": "array",
803
+ "minItems": 1,
804
+ "items": { "type": "integer", "minimum": 1, "maximum": 1000 }
805
+ }
806
+ }
807
+ },
808
+ {
809
+ "type": "object",
810
+ "additionalProperties": false,
811
+ "required": ["family", "files"],
812
+ "properties": {
813
+ "family": { "type": "string", "minLength": 1 },
814
+ "files": {
815
+ "type": "array",
816
+ "minItems": 1,
817
+ "items": {
818
+ "type": "object",
819
+ "additionalProperties": false,
820
+ "required": ["src"],
821
+ "properties": {
822
+ "src": {
823
+ "type": "string",
824
+ "pattern": "^/(?!/).+\\.(woff2?|ttf|otf)$"
825
+ },
826
+ "weight": {
827
+ "oneOf": [
828
+ { "type": "integer", "minimum": 1, "maximum": 1000 },
829
+ {
830
+ "type": "string",
831
+ "pattern": "^(?:[1-9][0-9]{0,2}|1000) (?:[1-9][0-9]{0,2}|1000)$"
832
+ }
833
+ ]
834
+ },
835
+ "style": { "enum": ["normal", "italic"] }
836
+ }
837
+ }
838
+ }
839
+ }
840
+ }
841
+ ]
842
+ },
612
843
  "locale": {
613
844
  "type": "object",
614
845
  "additionalProperties": false,
@@ -872,6 +1103,17 @@
872
1103
  "format": "uri"
873
1104
  }
874
1105
  }
1106
+ },
1107
+ {
1108
+ "type": "object",
1109
+ "additionalProperties": false,
1110
+ "required": ["openapi"],
1111
+ "properties": {
1112
+ "openapi": { "type": "string", "minLength": 1 },
1113
+ "id": { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" },
1114
+ "root": { "type": "string" },
1115
+ "routeBase": { "type": "string" }
1116
+ }
875
1117
  }
876
1118
  ]
877
1119
  },
@@ -27,6 +27,7 @@ const COOKBOOK_COMPONENT_NAMES = [
27
27
  "MobileNavigationTabs",
28
28
  "MobileTableOfContents",
29
29
  "PackageVersion",
30
+ "VersionSwitcher",
30
31
  "Preview",
31
32
  "Sidebar",
32
33
  "SocialIcons",
@@ -190,6 +191,13 @@ const COOKBOOK_COMPONENT_SUB_ELEMENTS = {
190
191
  "CurrentIndicator"
191
192
  ],
192
193
  PackageVersion: [],
194
+ VersionSwitcher: [
195
+ "Trigger",
196
+ "Panel",
197
+ "OpenPanel",
198
+ "Link",
199
+ "CurrentLink"
200
+ ],
193
201
  Preview: [
194
202
  "Caption",
195
203
  "Stage",
@@ -306,6 +314,22 @@ const HEAD_KEYS = /* @__PURE__ */ new Set([
306
314
  "content"
307
315
  ]);
308
316
  const SITE_ICON_KEYS = /* @__PURE__ */ new Set(["source", "background"]);
317
+ const COLOR_DECLARATION_KEYS = /* @__PURE__ */ new Set([
318
+ "from",
319
+ "tone",
320
+ "saturation",
321
+ "hue",
322
+ "darkHue",
323
+ "darkSaturation",
324
+ "base",
325
+ "contrast",
326
+ "mode",
327
+ "autoFlip",
328
+ "opacity",
329
+ "pastel",
330
+ "role",
331
+ "inherit"
332
+ ]);
309
333
  const ROOT_KEYS = /* @__PURE__ */ new Set([
310
334
  "root",
311
335
  "redirects",
@@ -327,6 +351,7 @@ const OBJECT_KEYS = {
327
351
  site: /* @__PURE__ */ new Set([
328
352
  "title",
329
353
  "version",
354
+ "versions",
330
355
  "description",
331
356
  "url",
332
357
  "repository",
@@ -345,6 +370,7 @@ const OBJECT_KEYS = {
345
370
  theme: /* @__PURE__ */ new Set([
346
371
  "brand",
347
372
  "palette",
373
+ "fonts",
348
374
  "states",
349
375
  "tokens",
350
376
  "presets",
@@ -388,6 +414,25 @@ function validateConfig(config) {
388
414
  }
389
415
  for (const key of Object.keys(value)) if (!keys.has(key)) unknown(diagnostics, `${section}.${key}`);
390
416
  }
417
+ if (isRecord(config.site) && config.site.versions !== void 0) {
418
+ if (!Array.isArray(config.site.versions) || config.site.versions.length < 2) invalid(diagnostics, "site.versions must contain at least two versions.");
419
+ else {
420
+ const bases = /* @__PURE__ */ new Set();
421
+ for (const [index, version] of config.site.versions.entries()) {
422
+ const path = `site.versions[${index}]`;
423
+ if (!isRecord(version)) {
424
+ invalid(diagnostics, `${path} must be an object.`);
425
+ continue;
426
+ }
427
+ for (const key of Object.keys(version)) if (key !== "label" && key !== "routeBase") unknown(diagnostics, `${path}.${key}`);
428
+ if (typeof version.label !== "string" || !version.label.trim()) invalid(diagnostics, `${path}.label must be a non-empty string.`);
429
+ const base = version.routeBase;
430
+ if (typeof base !== "string" || !base.startsWith("/") || base !== "/" && base.endsWith("/") || base.includes("//") || /[?#\\\s]/.test(base) || base.split("/").some((segment) => segment === "." || segment === "..")) invalid(diagnostics, `${path}.routeBase must be a root-relative route without a trailing slash.`);
431
+ else if (bases.has(base)) invalid(diagnostics, `${path}.routeBase duplicates ${base}.`);
432
+ else bases.add(base);
433
+ }
434
+ }
435
+ }
391
436
  if (isRecord(config.site) && config.site.headerLinks !== void 0) {
392
437
  if (!Array.isArray(config.site.headerLinks)) invalid(diagnostics, "site.headerLinks must be an array.");
393
438
  else config.site.headerLinks.forEach((item, index) => {
@@ -495,13 +540,14 @@ function validateConfig(config) {
495
540
  const variants = [
496
541
  "file",
497
542
  "glob",
498
- "package"
543
+ "package",
544
+ "openapi"
499
545
  ].filter((key) => key in source);
500
546
  if (variants.length !== 1) {
501
547
  diagnostics.push({
502
548
  code: "DOCS_CONFIG_INVALID",
503
549
  severity: "error",
504
- message: `content.sources[${index}] must select exactly one of file, glob, or package.`
550
+ message: `content.sources[${index}] must select exactly one of file, glob, package, or openapi.`
505
551
  });
506
552
  continue;
507
553
  }
@@ -535,6 +581,12 @@ function validateConfig(config) {
535
581
  "index",
536
582
  "routeBase",
537
583
  "trust"
584
+ ]),
585
+ openapi: /* @__PURE__ */ new Set([
586
+ "id",
587
+ "root",
588
+ "routeBase",
589
+ "openapi"
538
590
  ])
539
591
  }[variant];
540
592
  for (const key of Object.keys(source)) if (!allowed.has(key)) unknown(diagnostics, `content.sources[${index}].${key}`);
@@ -597,10 +649,58 @@ function validateConfig(config) {
597
649
  "styles",
598
650
  "customStyles",
599
651
  "palette",
652
+ "fonts",
600
653
  "tokens",
601
654
  "states",
602
655
  "presets"
603
656
  ]) if (config.theme?.[field] !== void 0 && !isRecord(config.theme[field])) invalid(diagnostics, `theme.${field} must be an object.`);
657
+ if (isRecord(config.theme?.fonts)) for (const [role, font] of Object.entries(config.theme.fonts)) {
658
+ const path = `theme.fonts.${role}`;
659
+ if (![
660
+ "body",
661
+ "heading",
662
+ "code"
663
+ ].includes(role)) {
664
+ unknown(diagnostics, path);
665
+ continue;
666
+ }
667
+ if (font === void 0) continue;
668
+ if (typeof font === "string") {
669
+ if (!validFontFamily(font)) invalid(diagnostics, `${path} must name a Google Fonts family.`);
670
+ continue;
671
+ }
672
+ if (!isRecord(font)) {
673
+ invalid(diagnostics, `${path} must be a Google Fonts name or font definition.`);
674
+ continue;
675
+ }
676
+ if ("google" in font) {
677
+ for (const key of Object.keys(font)) if (!["google", "weights"].includes(key)) unknown(diagnostics, `${path}.${key}`);
678
+ if (!validFontFamily(font.google)) invalid(diagnostics, `${path}.google must name a Google Fonts family.`);
679
+ if (font.weights !== void 0 && (!Array.isArray(font.weights) || font.weights.length === 0 || font.weights.some((weight) => !Number.isInteger(weight) || weight < 1 || weight > 1e3))) invalid(diagnostics, `${path}.weights must be a non-empty array of font weights from 1 to 1000.`);
680
+ } else {
681
+ for (const key of Object.keys(font)) if (!["family", "files"].includes(key)) unknown(diagnostics, `${path}.${key}`);
682
+ if (!validFontFamily(font.family)) invalid(diagnostics, `${path}.family must be a font family name.`);
683
+ if (!Array.isArray(font.files) || font.files.length === 0) {
684
+ invalid(diagnostics, `${path}.files must contain at least one font file.`);
685
+ continue;
686
+ }
687
+ font.files.forEach((file, index) => {
688
+ const filePath = `${path}.files[${index}]`;
689
+ if (!isRecord(file)) {
690
+ invalid(diagnostics, `${filePath} must be an object.`);
691
+ return;
692
+ }
693
+ for (const key of Object.keys(file)) if (![
694
+ "src",
695
+ "weight",
696
+ "style"
697
+ ].includes(key)) unknown(diagnostics, `${filePath}.${key}`);
698
+ if (typeof file.src !== "string" || !/^\/(?!\/)[^\s"'()\\?#]+\.(?:woff2?|ttf|otf)$/.test(file.src) || file.src.split("/").some((segment) => segment === "." || segment === "..")) invalid(diagnostics, `${filePath}.src must be a root-relative .woff2, .woff, .ttf, or .otf public path.`);
699
+ if (file.weight !== void 0 && !(typeof file.weight === "number" && Number.isInteger(file.weight) && file.weight >= 1 && file.weight <= 1e3) && !(typeof file.weight === "string" && /^(?:[1-9]\d{0,2}|1000) (?:[1-9]\d{0,2}|1000)$/.test(file.weight) && Number(file.weight.split(" ")[0]) < Number(file.weight.split(" ")[1]))) invalid(diagnostics, `${filePath}.weight must be a weight from 1 to 1000 or an ascending range.`);
700
+ if (file.style !== void 0 && !["normal", "italic"].includes(file.style)) invalid(diagnostics, `${filePath}.style must be normal or italic.`);
701
+ });
702
+ }
703
+ }
604
704
  for (const [name, entry] of Object.entries(config.theme?.styles ?? {})) {
605
705
  if (!COOKBOOK_COMPONENT_NAMES.includes(name)) {
606
706
  unknown(diagnostics, `theme.styles.${name}; register custom components under theme.customStyles`);
@@ -629,14 +729,15 @@ function validateConfig(config) {
629
729
  "warning",
630
730
  "danger"
631
731
  ].includes(name)) unknown(diagnostics, `theme.palette.${name}`);
632
- try {
633
- glaze.color({ from: value }).resolve();
634
- } catch (error) {
635
- invalid(diagnostics, `theme.palette.${name}: ${error instanceof Error ? error.message : String(error)}`);
636
- }
732
+ validatePaletteColor(name, value, diagnostics);
637
733
  }
638
734
  if (brand !== void 0) try {
639
- glaze.color({ from: typeof brand === "object" && brand !== null && "from" in brand ? brand.from : brand }).resolve();
735
+ if (isBrandDeclaration(brand)) glaze.color({
736
+ hue: brand.hue,
737
+ saturation: brand.saturation,
738
+ tone: brand.tone
739
+ }).resolve();
740
+ else glaze.color({ from: typeof brand === "object" && brand !== null && "from" in brand ? brand.from : brand }).resolve();
640
741
  } catch (error) {
641
742
  invalid(diagnostics, `theme.brand: ${error instanceof Error ? error.message : String(error)}`);
642
743
  }
@@ -651,6 +752,46 @@ function validateConfig(config) {
651
752
  }
652
753
  return diagnostics;
653
754
  }
755
+ function validatePaletteColor(name, value, diagnostics) {
756
+ try {
757
+ if (!isPaletteDeclaration(value)) {
758
+ glaze.color({ from: value }).resolve();
759
+ return;
760
+ }
761
+ for (const key of Object.keys(value)) if (!COLOR_DECLARATION_KEYS.has(key)) unknown(diagnostics, `theme.palette.${name}.${key}`);
762
+ const colorName = name === "textSoft" ? "text-soft" : name;
763
+ const theme = glaze(266, 68);
764
+ theme.colors({
765
+ surface: { tone: 98 },
766
+ "surface-2": {
767
+ base: "surface",
768
+ tone: "-2"
769
+ },
770
+ "accent-surface": { tone: 48 },
771
+ text: {
772
+ base: "surface",
773
+ contrast: { apca: 75 }
774
+ },
775
+ "text-soft": {
776
+ base: "surface",
777
+ contrast: { apca: 60 }
778
+ },
779
+ [colorName]: {
780
+ tone: colorName === "surface" ? 98 : 50,
781
+ ...value
782
+ }
783
+ });
784
+ theme.resolve();
785
+ } catch (error) {
786
+ invalid(diagnostics, `theme.palette.${name}: ${error instanceof Error ? error.message : String(error)}`);
787
+ }
788
+ }
789
+ function isPaletteDeclaration(value) {
790
+ return typeof value === "object" && value !== null && !("h" in value || "r" in value || "c" in value);
791
+ }
792
+ function isBrandDeclaration(value) {
793
+ return isRecord(value) && "hue" in value && "saturation" in value && "tone" in value;
794
+ }
654
795
  function unknown(diagnostics, path) {
655
796
  diagnostics.push({
656
797
  code: "DOCS_CONFIG_UNKNOWN_KEY",
@@ -668,6 +809,9 @@ function invalid(diagnostics, message) {
668
809
  function isRecord(value) {
669
810
  return typeof value === "object" && value !== null && !Array.isArray(value);
670
811
  }
812
+ function validFontFamily(value) {
813
+ return typeof value === "string" && value.length <= 100 && /^[\p{L}\p{N} ._-]+$/u.test(value) && value.trim() === value && value.length > 0;
814
+ }
671
815
  function isHttpUrl(value) {
672
816
  try {
673
817
  return ["http:", "https:"].includes(new URL(value).protocol);
@@ -745,7 +889,7 @@ function defineDocsConfig(config) {
745
889
  if (diagnostics.length > 0) throw new DocsConfigError(diagnostics);
746
890
  return config;
747
891
  }
748
- /** Compose presets. Objects merge, arrays replace, and styles use Tasty semantics. */
892
+ /** Compose presets. Color roles and fonts replace, other objects merge, and styles use Tasty semantics. */
749
893
  function mergeDocsConfig(...configs) {
750
894
  const merge = (base, next) => {
751
895
  const result = { ...base };
@@ -759,6 +903,15 @@ function mergeDocsConfig(...configs) {
759
903
  const diagnostics = validateConfig(next);
760
904
  if (diagnostics.length) throw new DocsConfigError(diagnostics);
761
905
  const result = merge(base, next);
906
+ if (next.theme?.brand !== void 0) result.theme.brand = next.theme.brand;
907
+ if (next.theme?.palette) result.theme.palette = {
908
+ ...base.theme?.palette,
909
+ ...Object.fromEntries(Object.entries(next.theme.palette).filter(([, color]) => color !== void 0))
910
+ };
911
+ if (next.theme?.fonts) result.theme.fonts = {
912
+ ...base.theme?.fonts,
913
+ ...Object.fromEntries(Object.entries(next.theme.fonts).filter(([, font]) => font !== void 0))
914
+ };
762
915
  for (const field of ["styles", "customStyles"]) {
763
916
  if (!next.theme?.[field]) continue;
764
917
  const styles = { ...base.theme?.[field] };
@@ -771,4 +924,4 @@ function mergeDocsConfig(...configs) {
771
924
  //#endregion
772
925
  export { validateConfig as a, normalizeDocsConfig as i, defineDocsConfig as n, COOKBOOK_COMPONENT_NAMES as o, mergeDocsConfig as r, COOKBOOK_COMPONENT_SUB_ELEMENTS as s, DocsConfigError as t };
773
926
 
774
- //# sourceMappingURL=config-CmoSqvii.js.map
927
+ //# sourceMappingURL=config-Ci9Nv0wQ.js.map