Skip to content

Commit a22e322

Browse files
pyxelrPawel Cislo
authored andcommitted
fix: respect global tableOfContents config with zod v4
With zod v4, `.default(value).optional()` returns the default value for `undefined` input instead of `undefined` itself. This caused frontmatter `tableOfContents` to always resolve to the schema default, preventing the global Starlight config from being used as a fallback. This fix introduces a separate `FrontmatterTableOfContentsSchema` that omits `.default()` so `undefined` is preserved, and updates `getToC()` to properly merge frontmatter values with the global config. Closes #3748
1 parent 7568842 commit a22e322

4 files changed

Lines changed: 40 additions & 8 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/starlight': patch
3+
---
4+
5+
Fixes global `tableOfContents` config being ignored due to a zod v4 behavior change where `.default().optional()` returns the default value for `undefined` input instead of `undefined` itself.

packages/starlight/schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { z } from 'astro/zod';
22
import type { SchemaContext } from 'astro:content';
33
import { HeadConfigSchema } from './schemas/head';
44
import { PrevNextLinkConfigSchema } from './schemas/prevNextLink';
5-
import { TableOfContentsSchema } from './schemas/tableOfContents';
5+
import { FrontmatterTableOfContentsSchema, TableOfContentsSchema } from './schemas/tableOfContents';
66
import { BadgeConfigSchema } from './schemas/badge';
77
import { HeroSchema } from './schemas/hero';
88
import { SidebarLinkItemHTMLAttributesSchema } from './schemas/sidebar';
@@ -33,7 +33,7 @@ const StarlightFrontmatterSchema = (context: SchemaContext) =>
3333
head: HeadConfigSchema({ source: 'content' }),
3434

3535
/** Override global table of contents configuration for this page. */
36-
tableOfContents: TableOfContentsSchema().optional(),
36+
tableOfContents: FrontmatterTableOfContentsSchema(),
3737

3838
/**
3939
* Set the layout style for this page.

packages/starlight/schemas/tableOfContents.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,22 @@ export const TableOfContentsSchema = () =>
1717
.refine((toc) => (toc ? toc.minHeadingLevel <= toc.maxHeadingLevel : true), {
1818
error: 'minHeadingLevel must be less than or equal to maxHeadingLevel',
1919
});
20+
21+
/**
22+
* Schema for the `tableOfContents` frontmatter field.
23+
* Unlike `TableOfContentsSchema`, this does not include a `.default()` so that
24+
* `undefined` is preserved when the field is not set in frontmatter, allowing
25+
* the global config to be used as a fallback.
26+
*/
27+
export const FrontmatterTableOfContentsSchema = () =>
28+
z
29+
.union([
30+
z.object({
31+
/** The level to start including headings at in the table of contents. Default: 2. */
32+
minHeadingLevel: z.int().min(1).max(6).optional(),
33+
/** The level to stop including headings at in the table of contents. Default: 3. */
34+
maxHeadingLevel: z.int().min(1).max(6).optional(),
35+
}),
36+
z.boolean(),
37+
])
38+
.optional();

packages/starlight/utils/routing/data.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,20 @@ export function generateRouteData({
6161
}
6262

6363
export function getToC({ entry, lang, headings }: PageProps) {
64-
const tocConfig =
65-
entry.data.template === 'splash'
66-
? false
67-
: entry.data.tableOfContents !== undefined
68-
? entry.data.tableOfContents
69-
: config.tableOfContents;
64+
const frontmatterToC = entry.data.tableOfContents;
65+
let tocConfig;
66+
if (entry.data.template === 'splash') {
67+
tocConfig = false;
68+
} else if (frontmatterToC === undefined) {
69+
tocConfig = config.tableOfContents;
70+
} else if (typeof frontmatterToC === 'boolean') {
71+
tocConfig = frontmatterToC ? config.tableOfContents : false;
72+
} else {
73+
tocConfig = {
74+
minHeadingLevel: frontmatterToC.minHeadingLevel ?? config.tableOfContents.minHeadingLevel,
75+
maxHeadingLevel: frontmatterToC.maxHeadingLevel ?? config.tableOfContents.maxHeadingLevel,
76+
};
77+
}
7078
if (!tocConfig) return;
7179
const t = useTranslations(lang);
7280
return {

0 commit comments

Comments
 (0)