@tenphi/docs 0.16.0 → 0.17.1

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-CY8WfBmM.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;iBA63BpC,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-CW0iIYp8.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,30 @@
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
+ "heading": { "$ref": "#/$defs/paletteColor" },
203
+ "textSoft": { "$ref": "#/$defs/paletteColor" },
204
+ "info": { "$ref": "#/$defs/paletteColor" },
205
+ "success": { "$ref": "#/$defs/paletteColor" },
206
+ "warning": { "$ref": "#/$defs/paletteColor" },
207
+ "danger": { "$ref": "#/$defs/paletteColor" },
208
+ "header": { "$ref": "#/$defs/paletteColor" },
209
+ "overlay": { "$ref": "#/$defs/paletteColor" }
210
+ }
211
+ },
212
+ "fonts": {
213
+ "type": "object",
214
+ "additionalProperties": false,
215
+ "properties": {
216
+ "body": { "$ref": "#/$defs/themeFont" },
217
+ "heading": { "$ref": "#/$defs/themeFont" },
218
+ "code": { "$ref": "#/$defs/themeFont" }
196
219
  }
197
220
  },
198
221
  "states": {
@@ -268,6 +291,14 @@
268
291
  }
269
292
  }
270
293
  },
294
+ "Heading": {
295
+ "type": "object",
296
+ "propertyNames": {
297
+ "not": {
298
+ "const": "mode"
299
+ }
300
+ }
301
+ },
271
302
  "LanguageSelect": {
272
303
  "type": "object",
273
304
  "propertyNames": {
@@ -356,6 +387,14 @@
356
387
  }
357
388
  }
358
389
  },
390
+ "VersionSwitcher": {
391
+ "type": "object",
392
+ "propertyNames": {
393
+ "not": {
394
+ "const": "mode"
395
+ }
396
+ }
397
+ },
359
398
  "Preview": {
360
399
  "type": "object",
361
400
  "propertyNames": {
@@ -609,6 +648,207 @@
609
648
  }
610
649
  },
611
650
  "$defs": {
651
+ "brandColor": {
652
+ "description": "A Glaze from declaration for the brand seed, optionally with APCA targets. Literal shorthand and structured hue, saturation, and tone are also supported.",
653
+ "oneOf": [
654
+ { "$ref": "#/$defs/glazeColorValue" },
655
+ {
656
+ "type": "object",
657
+ "additionalProperties": false,
658
+ "required": ["from"],
659
+ "properties": {
660
+ "from": { "$ref": "#/$defs/glazeColorValue" },
661
+ "contrast": { "$ref": "#/$defs/brandContrast" },
662
+ "unsafeContrast": { "type": "boolean" }
663
+ }
664
+ },
665
+ {
666
+ "type": "object",
667
+ "additionalProperties": false,
668
+ "required": ["hue", "saturation", "tone"],
669
+ "properties": {
670
+ "hue": { "type": "number", "minimum": 0, "maximum": 360 },
671
+ "saturation": { "type": "number", "minimum": 0, "maximum": 100 },
672
+ "tone": { "type": "number", "minimum": 0, "maximum": 100 },
673
+ "contrast": { "$ref": "#/$defs/brandContrast" },
674
+ "unsafeContrast": { "type": "boolean" }
675
+ }
676
+ }
677
+ ]
678
+ },
679
+ "brandContrast": {
680
+ "type": "object",
681
+ "additionalProperties": false,
682
+ "required": ["apca"],
683
+ "properties": {
684
+ "apca": {
685
+ "oneOf": [
686
+ { "type": "number" },
687
+ {
688
+ "type": "array",
689
+ "prefixItems": [{ "type": "number" }, { "type": "number" }],
690
+ "minItems": 2,
691
+ "maxItems": 2
692
+ }
693
+ ]
694
+ }
695
+ }
696
+ },
697
+ "paletteColor": {
698
+ "description": "A Glaze color declaration; literal colors are convenient seed shorthands.",
699
+ "oneOf": [
700
+ { "$ref": "#/$defs/glazeColorValue" },
701
+ { "$ref": "#/$defs/glazeDeclaration" }
702
+ ]
703
+ },
704
+ "glazeColorValue": {
705
+ "oneOf": [
706
+ { "type": "string" },
707
+ {
708
+ "type": "object",
709
+ "additionalProperties": false,
710
+ "required": ["h", "s", "l"],
711
+ "properties": {
712
+ "h": { "type": "number" },
713
+ "s": { "type": "number" },
714
+ "l": { "type": "number" }
715
+ }
716
+ },
717
+ {
718
+ "type": "object",
719
+ "additionalProperties": false,
720
+ "required": ["h", "s", "t"],
721
+ "properties": {
722
+ "h": { "type": "number" },
723
+ "s": { "type": "number" },
724
+ "t": { "type": "number" }
725
+ }
726
+ },
727
+ {
728
+ "type": "object",
729
+ "additionalProperties": false,
730
+ "required": ["r", "g", "b"],
731
+ "properties": {
732
+ "r": { "type": "number" },
733
+ "g": { "type": "number" },
734
+ "b": { "type": "number" }
735
+ }
736
+ },
737
+ {
738
+ "type": "object",
739
+ "additionalProperties": false,
740
+ "required": ["l", "c", "h"],
741
+ "properties": {
742
+ "l": { "type": "number" },
743
+ "c": { "type": "number" },
744
+ "h": { "type": "number" }
745
+ }
746
+ }
747
+ ]
748
+ },
749
+ "glazeDeclaration": {
750
+ "type": "object",
751
+ "additionalProperties": false,
752
+ "properties": {
753
+ "from": { "$ref": "#/$defs/glazeColorValue" },
754
+ "tone": {
755
+ "oneOf": [
756
+ { "type": "number", "minimum": 0, "maximum": 100 },
757
+ { "type": "string" },
758
+ {
759
+ "type": "array",
760
+ "prefixItems": [
761
+ { "oneOf": [{ "type": "number" }, { "type": "string" }] },
762
+ { "oneOf": [{ "type": "number" }, { "type": "string" }] }
763
+ ],
764
+ "minItems": 2,
765
+ "maxItems": 2
766
+ }
767
+ ]
768
+ },
769
+ "saturation": { "type": "number", "minimum": 0, "maximum": 1 },
770
+ "hue": { "oneOf": [{ "type": "number" }, { "type": "string" }] },
771
+ "darkHue": { "oneOf": [{ "type": "number" }, { "type": "string" }] },
772
+ "darkSaturation": { "type": "number", "minimum": 0, "maximum": 1 },
773
+ "base": { "type": "string" },
774
+ "contrast": {
775
+ "oneOf": [
776
+ { "type": "number" },
777
+ { "type": "string" },
778
+ {
779
+ "type": "array",
780
+ "minItems": 2,
781
+ "maxItems": 2
782
+ },
783
+ {
784
+ "type": "object",
785
+ "additionalProperties": false,
786
+ "properties": {
787
+ "apca": {},
788
+ "wcag": {}
789
+ }
790
+ }
791
+ ]
792
+ },
793
+ "mode": { "enum": ["auto", "fixed", "static"] },
794
+ "autoFlip": { "type": "boolean" },
795
+ "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
796
+ "pastel": { "type": "boolean" },
797
+ "role": { "type": "string" },
798
+ "inherit": { "type": "boolean" }
799
+ }
800
+ },
801
+ "themeFont": {
802
+ "oneOf": [
803
+ { "type": "string", "minLength": 1 },
804
+ {
805
+ "type": "object",
806
+ "additionalProperties": false,
807
+ "required": ["google"],
808
+ "properties": {
809
+ "google": { "type": "string", "minLength": 1 },
810
+ "weights": {
811
+ "type": "array",
812
+ "minItems": 1,
813
+ "items": { "type": "integer", "minimum": 1, "maximum": 1000 }
814
+ }
815
+ }
816
+ },
817
+ {
818
+ "type": "object",
819
+ "additionalProperties": false,
820
+ "required": ["family", "files"],
821
+ "properties": {
822
+ "family": { "type": "string", "minLength": 1 },
823
+ "files": {
824
+ "type": "array",
825
+ "minItems": 1,
826
+ "items": {
827
+ "type": "object",
828
+ "additionalProperties": false,
829
+ "required": ["src"],
830
+ "properties": {
831
+ "src": {
832
+ "type": "string",
833
+ "pattern": "^/(?!/).+\\.(woff2?|ttf|otf)$"
834
+ },
835
+ "weight": {
836
+ "oneOf": [
837
+ { "type": "integer", "minimum": 1, "maximum": 1000 },
838
+ {
839
+ "type": "string",
840
+ "pattern": "^(?:[1-9][0-9]{0,2}|1000) (?:[1-9][0-9]{0,2}|1000)$"
841
+ }
842
+ ]
843
+ },
844
+ "style": { "enum": ["normal", "italic"] }
845
+ }
846
+ }
847
+ }
848
+ }
849
+ }
850
+ ]
851
+ },
612
852
  "locale": {
613
853
  "type": "object",
614
854
  "additionalProperties": false,
@@ -872,6 +1112,17 @@
872
1112
  "format": "uri"
873
1113
  }
874
1114
  }
1115
+ },
1116
+ {
1117
+ "type": "object",
1118
+ "additionalProperties": false,
1119
+ "required": ["openapi"],
1120
+ "properties": {
1121
+ "openapi": { "type": "string", "minLength": 1 },
1122
+ "id": { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" },
1123
+ "root": { "type": "string" },
1124
+ "routeBase": { "type": "string" }
1125
+ }
875
1126
  }
876
1127
  ]
877
1128
  },
@@ -12,6 +12,7 @@ const COOKBOOK_COMPONENT_NAMES = [
12
12
  "PageFrame",
13
13
  "HeaderFrame",
14
14
  "HeaderLinks",
15
+ "Heading",
15
16
  "Layout",
16
17
  "SearchButton",
17
18
  "TableOfContentsLayout",
@@ -27,6 +28,7 @@ const COOKBOOK_COMPONENT_NAMES = [
27
28
  "MobileNavigationTabs",
28
29
  "MobileTableOfContents",
29
30
  "PackageVersion",
31
+ "VersionSwitcher",
30
32
  "Preview",
31
33
  "Sidebar",
32
34
  "SocialIcons",
@@ -45,6 +47,15 @@ const COOKBOOK_COMPONENT_SUB_ELEMENTS = {
45
47
  "Columns"
46
48
  ],
47
49
  HeaderFrame: [],
50
+ Heading: [
51
+ "Level1",
52
+ "Level2",
53
+ "Level3",
54
+ "Level4",
55
+ "Level5",
56
+ "Level6",
57
+ "PageTitle"
58
+ ],
48
59
  HeaderLinks: [
49
60
  "Desktop",
50
61
  "Link",
@@ -190,6 +201,13 @@ const COOKBOOK_COMPONENT_SUB_ELEMENTS = {
190
201
  "CurrentIndicator"
191
202
  ],
192
203
  PackageVersion: [],
204
+ VersionSwitcher: [
205
+ "Trigger",
206
+ "Panel",
207
+ "OpenPanel",
208
+ "Link",
209
+ "CurrentLink"
210
+ ],
193
211
  Preview: [
194
212
  "Caption",
195
213
  "Stage",
@@ -306,6 +324,22 @@ const HEAD_KEYS = /* @__PURE__ */ new Set([
306
324
  "content"
307
325
  ]);
308
326
  const SITE_ICON_KEYS = /* @__PURE__ */ new Set(["source", "background"]);
327
+ const COLOR_DECLARATION_KEYS = /* @__PURE__ */ new Set([
328
+ "from",
329
+ "tone",
330
+ "saturation",
331
+ "hue",
332
+ "darkHue",
333
+ "darkSaturation",
334
+ "base",
335
+ "contrast",
336
+ "mode",
337
+ "autoFlip",
338
+ "opacity",
339
+ "pastel",
340
+ "role",
341
+ "inherit"
342
+ ]);
309
343
  const ROOT_KEYS = /* @__PURE__ */ new Set([
310
344
  "root",
311
345
  "redirects",
@@ -327,6 +361,7 @@ const OBJECT_KEYS = {
327
361
  site: /* @__PURE__ */ new Set([
328
362
  "title",
329
363
  "version",
364
+ "versions",
330
365
  "description",
331
366
  "url",
332
367
  "repository",
@@ -345,6 +380,7 @@ const OBJECT_KEYS = {
345
380
  theme: /* @__PURE__ */ new Set([
346
381
  "brand",
347
382
  "palette",
383
+ "fonts",
348
384
  "states",
349
385
  "tokens",
350
386
  "presets",
@@ -388,6 +424,25 @@ function validateConfig(config) {
388
424
  }
389
425
  for (const key of Object.keys(value)) if (!keys.has(key)) unknown(diagnostics, `${section}.${key}`);
390
426
  }
427
+ if (isRecord(config.site) && config.site.versions !== void 0) {
428
+ if (!Array.isArray(config.site.versions) || config.site.versions.length < 2) invalid(diagnostics, "site.versions must contain at least two versions.");
429
+ else {
430
+ const bases = /* @__PURE__ */ new Set();
431
+ for (const [index, version] of config.site.versions.entries()) {
432
+ const path = `site.versions[${index}]`;
433
+ if (!isRecord(version)) {
434
+ invalid(diagnostics, `${path} must be an object.`);
435
+ continue;
436
+ }
437
+ for (const key of Object.keys(version)) if (key !== "label" && key !== "routeBase") unknown(diagnostics, `${path}.${key}`);
438
+ if (typeof version.label !== "string" || !version.label.trim()) invalid(diagnostics, `${path}.label must be a non-empty string.`);
439
+ const base = version.routeBase;
440
+ 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.`);
441
+ else if (bases.has(base)) invalid(diagnostics, `${path}.routeBase duplicates ${base}.`);
442
+ else bases.add(base);
443
+ }
444
+ }
445
+ }
391
446
  if (isRecord(config.site) && config.site.headerLinks !== void 0) {
392
447
  if (!Array.isArray(config.site.headerLinks)) invalid(diagnostics, "site.headerLinks must be an array.");
393
448
  else config.site.headerLinks.forEach((item, index) => {
@@ -495,13 +550,14 @@ function validateConfig(config) {
495
550
  const variants = [
496
551
  "file",
497
552
  "glob",
498
- "package"
553
+ "package",
554
+ "openapi"
499
555
  ].filter((key) => key in source);
500
556
  if (variants.length !== 1) {
501
557
  diagnostics.push({
502
558
  code: "DOCS_CONFIG_INVALID",
503
559
  severity: "error",
504
- message: `content.sources[${index}] must select exactly one of file, glob, or package.`
560
+ message: `content.sources[${index}] must select exactly one of file, glob, package, or openapi.`
505
561
  });
506
562
  continue;
507
563
  }
@@ -535,6 +591,12 @@ function validateConfig(config) {
535
591
  "index",
536
592
  "routeBase",
537
593
  "trust"
594
+ ]),
595
+ openapi: /* @__PURE__ */ new Set([
596
+ "id",
597
+ "root",
598
+ "routeBase",
599
+ "openapi"
538
600
  ])
539
601
  }[variant];
540
602
  for (const key of Object.keys(source)) if (!allowed.has(key)) unknown(diagnostics, `content.sources[${index}].${key}`);
@@ -597,10 +659,58 @@ function validateConfig(config) {
597
659
  "styles",
598
660
  "customStyles",
599
661
  "palette",
662
+ "fonts",
600
663
  "tokens",
601
664
  "states",
602
665
  "presets"
603
666
  ]) if (config.theme?.[field] !== void 0 && !isRecord(config.theme[field])) invalid(diagnostics, `theme.${field} must be an object.`);
667
+ if (isRecord(config.theme?.fonts)) for (const [role, font] of Object.entries(config.theme.fonts)) {
668
+ const path = `theme.fonts.${role}`;
669
+ if (![
670
+ "body",
671
+ "heading",
672
+ "code"
673
+ ].includes(role)) {
674
+ unknown(diagnostics, path);
675
+ continue;
676
+ }
677
+ if (font === void 0) continue;
678
+ if (typeof font === "string") {
679
+ if (!validFontFamily(font)) invalid(diagnostics, `${path} must name a Google Fonts family.`);
680
+ continue;
681
+ }
682
+ if (!isRecord(font)) {
683
+ invalid(diagnostics, `${path} must be a Google Fonts name or font definition.`);
684
+ continue;
685
+ }
686
+ if ("google" in font) {
687
+ for (const key of Object.keys(font)) if (!["google", "weights"].includes(key)) unknown(diagnostics, `${path}.${key}`);
688
+ if (!validFontFamily(font.google)) invalid(diagnostics, `${path}.google must name a Google Fonts family.`);
689
+ 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.`);
690
+ } else {
691
+ for (const key of Object.keys(font)) if (!["family", "files"].includes(key)) unknown(diagnostics, `${path}.${key}`);
692
+ if (!validFontFamily(font.family)) invalid(diagnostics, `${path}.family must be a font family name.`);
693
+ if (!Array.isArray(font.files) || font.files.length === 0) {
694
+ invalid(diagnostics, `${path}.files must contain at least one font file.`);
695
+ continue;
696
+ }
697
+ font.files.forEach((file, index) => {
698
+ const filePath = `${path}.files[${index}]`;
699
+ if (!isRecord(file)) {
700
+ invalid(diagnostics, `${filePath} must be an object.`);
701
+ return;
702
+ }
703
+ for (const key of Object.keys(file)) if (![
704
+ "src",
705
+ "weight",
706
+ "style"
707
+ ].includes(key)) unknown(diagnostics, `${filePath}.${key}`);
708
+ 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.`);
709
+ 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.`);
710
+ if (file.style !== void 0 && !["normal", "italic"].includes(file.style)) invalid(diagnostics, `${filePath}.style must be normal or italic.`);
711
+ });
712
+ }
713
+ }
604
714
  for (const [name, entry] of Object.entries(config.theme?.styles ?? {})) {
605
715
  if (!COOKBOOK_COMPONENT_NAMES.includes(name)) {
606
716
  unknown(diagnostics, `theme.styles.${name}; register custom components under theme.customStyles`);
@@ -623,20 +733,22 @@ function validateConfig(config) {
623
733
  "header",
624
734
  "overlay",
625
735
  "text",
736
+ "heading",
626
737
  "textSoft",
627
738
  "info",
628
739
  "success",
629
740
  "warning",
630
741
  "danger"
631
742
  ].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
- }
743
+ validatePaletteColor(name, value, diagnostics);
637
744
  }
638
745
  if (brand !== void 0) try {
639
- glaze.color({ from: typeof brand === "object" && brand !== null && "from" in brand ? brand.from : brand }).resolve();
746
+ if (isBrandDeclaration(brand)) glaze.color({
747
+ hue: brand.hue,
748
+ saturation: brand.saturation,
749
+ tone: brand.tone
750
+ }).resolve();
751
+ else glaze.color({ from: typeof brand === "object" && brand !== null && "from" in brand ? brand.from : brand }).resolve();
640
752
  } catch (error) {
641
753
  invalid(diagnostics, `theme.brand: ${error instanceof Error ? error.message : String(error)}`);
642
754
  }
@@ -651,6 +763,50 @@ function validateConfig(config) {
651
763
  }
652
764
  return diagnostics;
653
765
  }
766
+ function validatePaletteColor(name, value, diagnostics) {
767
+ try {
768
+ if (!isPaletteDeclaration(value)) {
769
+ glaze.color({ from: value }).resolve();
770
+ return;
771
+ }
772
+ for (const key of Object.keys(value)) if (!COLOR_DECLARATION_KEYS.has(key)) unknown(diagnostics, `theme.palette.${name}.${key}`);
773
+ const colorName = name === "textSoft" ? "text-soft" : name;
774
+ const theme = glaze(266, 68);
775
+ theme.colors({
776
+ surface: { tone: 98 },
777
+ "surface-2": {
778
+ base: "surface",
779
+ tone: "-2"
780
+ },
781
+ "accent-surface": { tone: 48 },
782
+ text: {
783
+ base: "surface",
784
+ contrast: { apca: 75 }
785
+ },
786
+ heading: {
787
+ base: "surface",
788
+ tone: 4
789
+ },
790
+ "text-soft": {
791
+ base: "surface",
792
+ contrast: { apca: 60 }
793
+ },
794
+ [colorName]: {
795
+ tone: colorName === "surface" ? 98 : 50,
796
+ ...value
797
+ }
798
+ });
799
+ theme.resolve();
800
+ } catch (error) {
801
+ invalid(diagnostics, `theme.palette.${name}: ${error instanceof Error ? error.message : String(error)}`);
802
+ }
803
+ }
804
+ function isPaletteDeclaration(value) {
805
+ return typeof value === "object" && value !== null && !("h" in value || "r" in value || "c" in value);
806
+ }
807
+ function isBrandDeclaration(value) {
808
+ return isRecord(value) && "hue" in value && "saturation" in value && "tone" in value;
809
+ }
654
810
  function unknown(diagnostics, path) {
655
811
  diagnostics.push({
656
812
  code: "DOCS_CONFIG_UNKNOWN_KEY",
@@ -668,6 +824,9 @@ function invalid(diagnostics, message) {
668
824
  function isRecord(value) {
669
825
  return typeof value === "object" && value !== null && !Array.isArray(value);
670
826
  }
827
+ function validFontFamily(value) {
828
+ return typeof value === "string" && value.length <= 100 && /^[\p{L}\p{N} ._-]+$/u.test(value) && value.trim() === value && value.length > 0;
829
+ }
671
830
  function isHttpUrl(value) {
672
831
  try {
673
832
  return ["http:", "https:"].includes(new URL(value).protocol);
@@ -745,7 +904,7 @@ function defineDocsConfig(config) {
745
904
  if (diagnostics.length > 0) throw new DocsConfigError(diagnostics);
746
905
  return config;
747
906
  }
748
- /** Compose presets. Objects merge, arrays replace, and styles use Tasty semantics. */
907
+ /** Compose presets. Color roles and fonts replace, other objects merge, and styles use Tasty semantics. */
749
908
  function mergeDocsConfig(...configs) {
750
909
  const merge = (base, next) => {
751
910
  const result = { ...base };
@@ -759,6 +918,15 @@ function mergeDocsConfig(...configs) {
759
918
  const diagnostics = validateConfig(next);
760
919
  if (diagnostics.length) throw new DocsConfigError(diagnostics);
761
920
  const result = merge(base, next);
921
+ if (next.theme?.brand !== void 0) result.theme.brand = next.theme.brand;
922
+ if (next.theme?.palette) result.theme.palette = {
923
+ ...base.theme?.palette,
924
+ ...Object.fromEntries(Object.entries(next.theme.palette).filter(([, color]) => color !== void 0))
925
+ };
926
+ if (next.theme?.fonts) result.theme.fonts = {
927
+ ...base.theme?.fonts,
928
+ ...Object.fromEntries(Object.entries(next.theme.fonts).filter(([, font]) => font !== void 0))
929
+ };
762
930
  for (const field of ["styles", "customStyles"]) {
763
931
  if (!next.theme?.[field]) continue;
764
932
  const styles = { ...base.theme?.[field] };
@@ -771,4 +939,4 @@ function mergeDocsConfig(...configs) {
771
939
  //#endregion
772
940
  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
941
 
774
- //# sourceMappingURL=config-CmoSqvii.js.map
942
+ //# sourceMappingURL=config-CW0iIYp8.js.map