@astrojs/starlight 0.33.1 → 0.34.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/components/AnchorHeading.astro +53 -0
  3. package/components/Banner.astro +14 -12
  4. package/components/ContentNotice.astro +13 -11
  5. package/components/ContentPanel.astro +17 -15
  6. package/components/EditLink.astro +10 -8
  7. package/components/Footer.astro +30 -28
  8. package/components/Header.astro +54 -51
  9. package/components/Hero.astro +60 -58
  10. package/components/MobileMenuFooter.astro +19 -16
  11. package/components/MobileMenuToggle.astro +41 -37
  12. package/components/MobileTableOfContents.astro +71 -69
  13. package/components/Page.astro +8 -8
  14. package/components/PageFrame.astro +61 -57
  15. package/components/PageSidebar.astro +33 -31
  16. package/components/PageTitle.astro +8 -6
  17. package/components/Pagination.astro +38 -36
  18. package/components/Search.astro +277 -272
  19. package/components/Select.astro +50 -46
  20. package/components/SidebarPersister.astro +4 -2
  21. package/components/SidebarSublist.astro +78 -76
  22. package/components/SiteTitle.astro +17 -15
  23. package/components/SkipLink.astro +17 -15
  24. package/components/SocialIcons.astro +9 -7
  25. package/components/TableOfContents/TableOfContentsList.astro +49 -47
  26. package/components/TwoColumnContent.astro +32 -30
  27. package/index.ts +11 -4
  28. package/integrations/expressive-code/index.ts +2 -0
  29. package/integrations/heading-links.ts +104 -0
  30. package/integrations/virtual-user-config.ts +9 -0
  31. package/package.json +12 -7
  32. package/schemas/head.ts +2 -2
  33. package/schemas/i18n.ts +2 -0
  34. package/style/anchor-links.css +125 -0
  35. package/style/asides.css +46 -44
  36. package/style/layers.css +1 -0
  37. package/style/markdown.css +195 -189
  38. package/style/print.css +30 -35
  39. package/style/props.css +180 -178
  40. package/style/reset.css +42 -40
  41. package/style/util.css +51 -49
  42. package/translations/ar.json +2 -1
  43. package/translations/ca.json +2 -1
  44. package/translations/cs.json +2 -1
  45. package/translations/da.json +2 -1
  46. package/translations/de.json +2 -1
  47. package/translations/en.json +2 -1
  48. package/translations/es.json +2 -1
  49. package/translations/fa.json +2 -1
  50. package/translations/fr.json +2 -1
  51. package/translations/gl.json +2 -1
  52. package/translations/he.json +2 -1
  53. package/translations/hi.json +2 -1
  54. package/translations/id.json +2 -1
  55. package/translations/it.json +2 -1
  56. package/translations/ja.json +2 -1
  57. package/translations/ko.json +2 -1
  58. package/translations/lv.json +2 -1
  59. package/translations/nb.json +2 -1
  60. package/translations/nl.json +2 -1
  61. package/translations/pl.json +2 -1
  62. package/translations/pt.json +2 -1
  63. package/translations/ro.json +2 -1
  64. package/translations/ru.json +2 -1
  65. package/translations/sk.json +2 -1
  66. package/translations/sv.json +2 -1
  67. package/translations/tr.json +2 -1
  68. package/translations/uk.json +2 -1
  69. package/translations/vi.json +2 -1
  70. package/translations/zh-CN.json +2 -1
  71. package/translations/zh-TW.json +2 -1
  72. package/user-components/Badge.astro +118 -116
  73. package/user-components/Card.astro +44 -42
  74. package/user-components/CardGrid.astro +20 -18
  75. package/user-components/FileTree.astro +105 -103
  76. package/user-components/Icon.astro +7 -5
  77. package/user-components/LinkButton.astro +43 -41
  78. package/user-components/LinkCard.astro +45 -43
  79. package/user-components/Steps.astro +65 -61
  80. package/user-components/Tabs.astro +35 -33
  81. package/utils/head.ts +12 -4
  82. package/utils/starlight-page.ts +16 -19
  83. package/utils/user-config.ts +14 -0
  84. package/virtual-internal.d.ts +2 -0
  85. package/style/shiki.css +0 -13
@@ -0,0 +1,104 @@
1
+ import { rehypeHeadingIds } from '@astrojs/markdown-remark';
2
+ import type { AstroConfig, AstroUserConfig } from 'astro';
3
+ import type { Nodes, Root } from 'hast';
4
+ import { toString } from 'hast-util-to-string';
5
+ import { h } from 'hastscript';
6
+ import type { Transformer } from 'unified';
7
+ import { SKIP, visit } from 'unist-util-visit';
8
+ import type { HookParameters, StarlightConfig } from '../types';
9
+
10
+ const AnchorLinkIcon = h(
11
+ 'span',
12
+ { ariaHidden: 'true', class: 'sl-anchor-icon' },
13
+ h(
14
+ 'svg',
15
+ { width: 16, height: 16, viewBox: '0 0 24 24' },
16
+ h('path', {
17
+ fill: 'currentcolor',
18
+ d: 'm12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z',
19
+ })
20
+ )
21
+ );
22
+
23
+ /**
24
+ * Add anchor links to headings.
25
+ */
26
+ export default function rehypeAutolinkHeadings(
27
+ useTranslationsForLang: HookParameters<'config:setup'>['useTranslations'],
28
+ absolutePathToLang: HookParameters<'config:setup'>['absolutePathToLang']
29
+ ) {
30
+ const transformer: Transformer<Root> = (tree, file) => {
31
+ const pageLang = absolutePathToLang(file.path);
32
+ const t = useTranslationsForLang(pageLang);
33
+
34
+ visit(tree, 'element', function (node, index, parent) {
35
+ if (!headingRank(node) || !node.properties.id || typeof index !== 'number' || !parent) {
36
+ return;
37
+ }
38
+
39
+ const accessibleLabel = t('heading.anchorLabel', {
40
+ title: toString(node),
41
+ interpolation: { escapeValue: false },
42
+ });
43
+
44
+ // Wrap the heading in a div and append the anchor link.
45
+ parent.children[index] = h(
46
+ 'div',
47
+ { class: `sl-heading-wrapper level-${node.tagName}` },
48
+ // Heading
49
+ node,
50
+ // Anchor link
51
+ {
52
+ type: 'element',
53
+ tagName: 'a',
54
+ properties: { class: 'sl-anchor-link', href: '#' + node.properties.id },
55
+ children: [AnchorLinkIcon, h('span', { class: 'sr-only' }, accessibleLabel)],
56
+ }
57
+ );
58
+
59
+ return SKIP;
60
+ });
61
+ };
62
+
63
+ return function attacher() {
64
+ return transformer;
65
+ };
66
+ }
67
+
68
+ interface AutolinkHeadingsOptions {
69
+ starlightConfig: Pick<StarlightConfig, 'markdown'>;
70
+ astroConfig: { experimental: Pick<AstroConfig['experimental'], 'headingIdCompat'> };
71
+ useTranslations: HookParameters<'config:setup'>['useTranslations'];
72
+ absolutePathToLang: HookParameters<'config:setup'>['absolutePathToLang'];
73
+ }
74
+ type RehypePlugins = NonNullable<NonNullable<AstroUserConfig['markdown']>['rehypePlugins']>;
75
+
76
+ export const starlightAutolinkHeadings = ({
77
+ starlightConfig,
78
+ astroConfig,
79
+ useTranslations,
80
+ absolutePathToLang,
81
+ }: AutolinkHeadingsOptions): RehypePlugins =>
82
+ starlightConfig.markdown.headingLinks
83
+ ? [
84
+ [
85
+ rehypeHeadingIds,
86
+ { experimentalHeadingIdCompat: astroConfig.experimental?.headingIdCompat },
87
+ ],
88
+ rehypeAutolinkHeadings(useTranslations, absolutePathToLang),
89
+ ]
90
+ : [];
91
+
92
+ // This utility is inlined from https://github.com/syntax-tree/hast-util-heading-rank
93
+ // Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
94
+ // MIT License: https://github.com/syntax-tree/hast-util-heading-rank/blob/main/license
95
+ /**
96
+ * Get the rank (`1` to `6`) of headings (`h1` to `h6`).
97
+ * @param node Node to check.
98
+ * @returns Rank of the heading or `undefined` if not a heading.
99
+ */
100
+ function headingRank(node: Nodes): number | undefined {
101
+ const name = node.type === 'element' ? node.tagName.toLowerCase() : '';
102
+ const code = name.length === 2 && name.charCodeAt(0) === 104 /* `h` */ ? name.charCodeAt(1) : 0;
103
+ return code > 48 /* `0` */ && code < 55 /* `7` */ ? code - 48 /* `0` */ : undefined;
104
+ }
@@ -84,6 +84,15 @@ export function vitePluginStarlightUserConfig(
84
84
  : `import { makeAPI } from ${resolveLocalPath('../utils/gitInlined.ts')};` +
85
85
  `const api = makeAPI(${JSON.stringify(getAllNewestCommitDate(rootPath, docsPath))});`) +
86
86
  'export const getNewestCommitDate = api.getNewestCommitDate;',
87
+ /**
88
+ * Module containing styles for features that can be toggled on or off such as heading anchor links.
89
+ */
90
+ 'virtual:starlight/optional-css': opts.markdown.headingLinks
91
+ ? `import ${resolveLocalPath('../style/anchor-links.css')};`
92
+ : '',
93
+ /**
94
+ * Module containing imports of user-specified custom CSS files.
95
+ */
87
96
  'virtual:starlight/user-css': opts.customCss.map((id) => `import ${resolveId(id)};`).join(''),
88
97
  'virtual:starlight/user-images': opts.logo
89
98
  ? 'src' in opts.logo
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.33.1",
3
+ "version": "0.34.0",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
@@ -103,6 +103,10 @@
103
103
  "types": "./components/StarlightPage.astro.tsx",
104
104
  "import": "./components/StarlightPage.astro"
105
105
  },
106
+ "./components/AnchorHeading.astro": {
107
+ "types": "./components/AnchorHeading.astro.tsx",
108
+ "import": "./components/AnchorHeading.astro"
109
+ },
106
110
  "./components/Footer.astro": {
107
111
  "types": "./components/Footer.astro.tsx",
108
112
  "import": "./components/Footer.astro"
@@ -173,25 +177,25 @@
173
177
  "./style/markdown.css": "./style/markdown.css"
174
178
  },
175
179
  "peerDependencies": {
176
- "astro": "^5.1.5"
180
+ "astro": "^5.5.0"
177
181
  },
178
182
  "devDependencies": {
179
- "@astrojs/markdown-remark": "^6.0.1",
180
183
  "@playwright/test": "^1.45.0",
181
184
  "@types/node": "^18.16.19",
182
185
  "@vitest/coverage-v8": "^3.0.5",
183
- "astro": "^5.5.3",
186
+ "astro": "^5.6.1",
184
187
  "linkedom": "^0.18.4",
185
188
  "vitest": "^3.0.5"
186
189
  },
187
190
  "dependencies": {
188
- "@astrojs/mdx": "^4.0.5",
189
- "@astrojs/sitemap": "^3.2.1",
191
+ "@astrojs/markdown-remark": "^6.3.1",
192
+ "@astrojs/mdx": "^4.2.3",
193
+ "@astrojs/sitemap": "^3.3.0",
190
194
  "@pagefind/default-ui": "^1.3.0",
191
195
  "@types/hast": "^3.0.4",
192
196
  "@types/js-yaml": "^4.0.9",
193
197
  "@types/mdast": "^4.0.4",
194
- "astro-expressive-code": "^0.40.0",
198
+ "astro-expressive-code": "^0.41.1",
195
199
  "bcp-47": "^2.1.0",
196
200
  "hast-util-from-html": "^2.0.1",
197
201
  "hast-util-select": "^6.0.2",
@@ -207,6 +211,7 @@
207
211
  "rehype": "^13.0.1",
208
212
  "rehype-format": "^5.0.0",
209
213
  "remark-directive": "^3.0.0",
214
+ "ultrahtml": "^1.6.0",
210
215
  "unified": "^11.0.5",
211
216
  "unist-util-visit": "^5.0.0",
212
217
  "vfile": "^6.0.2"
package/schemas/head.ts CHANGED
@@ -7,9 +7,9 @@ export const HeadConfigSchema = () =>
7
7
  /** Name of the HTML tag to add to `<head>`, e.g. `'meta'`, `'link'`, or `'script'`. */
8
8
  tag: z.enum(['title', 'base', 'link', 'style', 'meta', 'script', 'noscript', 'template']),
9
9
  /** Attributes to set on the tag, e.g. `{ rel: 'stylesheet', href: '/custom.css' }`. */
10
- attrs: z.record(z.union([z.string(), z.boolean(), z.undefined()])).default({}),
10
+ attrs: z.record(z.union([z.string(), z.boolean(), z.undefined()])).optional(),
11
11
  /** Content to place inside the tag (optional). */
12
- content: z.string().default(''),
12
+ content: z.string().optional(),
13
13
  })
14
14
  )
15
15
  .default([]);
package/schemas/i18n.ts CHANGED
@@ -158,6 +158,8 @@ function starlightI18nSchema() {
158
158
  .describe(
159
159
  'Label for the “Built with Starlight” badge optionally displayed in the site footer.'
160
160
  ),
161
+
162
+ 'heading.anchorLabel': z.string().describe('Label for anchor links in Markdown content.'),
161
163
  })
162
164
  .partial();
163
165
  }
@@ -0,0 +1,125 @@
1
+ /*
2
+ How does anchor link placement work?
3
+
4
+ Because anchor links need to placed inline at the end of a heading, but are not children of the
5
+ heading element itself, positioning them to behave in a desirable way is a tiny bit tricky. Here’s
6
+ how we do it.
7
+
8
+ 1. We wrap the heading and anchor link in a div and make the heading element inline:
9
+ <div class="sl-heading-wrapper">
10
+ <h2>...</h2><a class="sl-anchor-link">...</a>
11
+ </a>
12
+
13
+ 2. We need to avoid the anchor link wrapping onto a new line by itself like this because it looks
14
+ broken:
15
+
16
+ Some heading text
17
+
18
+
19
+ 3. To achieve this we add an area of padding to the end of the heading and move the link over this
20
+ padding using negative margin:
21
+
22
+ padding-inline-end creates space at the end of the line
23
+
24
+ Some heading text[ ]⛓
25
+
26
+ margin-inline-start then pulls the anchor link into that space
27
+
28
+ Some heading text[ ⛓ ]
29
+
30
+ This ensures that when the anchor link wraps, the final word in the heading will wrap with it.
31
+
32
+ */
33
+ @layer starlight.content {
34
+ /* ======================================================
35
+ WRAPPER
36
+ ====================================================== */
37
+ .sl-markdown-content .sl-heading-wrapper {
38
+ /* The size of the SVG icon. */
39
+ --sl-anchor-icon-size: 0.8275em;
40
+ /* The horizontal space between the SVG icon and the end of the heading text. */
41
+ --sl-anchor-icon-gap: 0.25em;
42
+ /* The end of line space required to accommodate the anchor link. */
43
+ --sl-anchor-icon-space: calc(var(--sl-anchor-icon-size) + var(--sl-anchor-icon-gap));
44
+
45
+ line-height: var(--sl-line-height-headings);
46
+ }
47
+
48
+ /* We need to apply the same rule we use for heading spacing to the parent wrapper. */
49
+ .sl-markdown-content
50
+ :not(h1, h2, h3, h4, h5, h6, .sl-heading-wrapper)
51
+ + :is(.sl-heading-wrapper) {
52
+ margin-top: 1.5em;
53
+ }
54
+
55
+ /* These font sizes are set in `markdown.css` for heading elements, but we need them one level higher on the wrapper. */
56
+ .sl-markdown-content .sl-heading-wrapper.level-h1 {
57
+ font-size: var(--sl-text-h1);
58
+ }
59
+ .sl-markdown-content .sl-heading-wrapper.level-h2 {
60
+ font-size: var(--sl-text-h2);
61
+ }
62
+ .sl-markdown-content .sl-heading-wrapper.level-h3 {
63
+ font-size: var(--sl-text-h3);
64
+ }
65
+ .sl-markdown-content .sl-heading-wrapper.level-h4 {
66
+ font-size: var(--sl-text-h4);
67
+ }
68
+ .sl-markdown-content .sl-heading-wrapper.level-h5 {
69
+ font-size: var(--sl-text-h5);
70
+ }
71
+ .sl-markdown-content .sl-heading-wrapper.level-h6 {
72
+ font-size: var(--sl-text-h6);
73
+ }
74
+
75
+ /* ======================================================
76
+ HEADING
77
+ ====================================================== */
78
+ .sl-markdown-content .sl-heading-wrapper > :first-child {
79
+ display: inline;
80
+ /* Apply end-of-line padding to the heading element. */
81
+ padding-inline-end: var(--sl-anchor-icon-space);
82
+ }
83
+
84
+ /* ======================================================
85
+ LINK
86
+ ====================================================== */
87
+ .sl-markdown-content .sl-anchor-link {
88
+ position: relative;
89
+ /* Move the anchor link over the heading element’s end-of-line padding. */
90
+ margin-inline-start: calc(-1 * var(--sl-anchor-icon-size));
91
+ }
92
+
93
+ /* Increase clickable area for anchor links with a pseudo element that doesn’t impact layout. */
94
+ .sl-markdown-content .sl-anchor-link::after {
95
+ content: '';
96
+ position: absolute;
97
+ /* While most icon spacing is done with `em` to be relative to the heading font-size, increasing
98
+ the touch area is most important for smaller headings like h5/h6, so we use absolute units,
99
+ which have a diminishing impact at larger font-sizes. */
100
+ inset: -0.25rem -0.5rem;
101
+ }
102
+
103
+ /* Size and position the SVG icon inside the link. */
104
+ .sl-markdown-content .sl-anchor-icon > svg {
105
+ display: inline;
106
+ width: var(--sl-anchor-icon-size);
107
+ /* Center the link icon SVG vertically in the line. */
108
+ vertical-align: top;
109
+ transform: translateY(
110
+ calc((var(--sl-line-height-headings) * 1em - var(--sl-anchor-icon-size)) / 2)
111
+ );
112
+ }
113
+
114
+ /* On devices with hover capability, hide the anchor link icons and show only show them when focused
115
+ or when the heading is hovered. */
116
+ @media (hover: hover) {
117
+ .sl-markdown-content .sl-anchor-link {
118
+ opacity: 0;
119
+ }
120
+ .sl-markdown-content .sl-anchor-link:focus,
121
+ .sl-markdown-content .sl-heading-wrapper:hover .sl-anchor-link {
122
+ opacity: 1;
123
+ }
124
+ }
125
+ }
package/style/asides.css CHANGED
@@ -1,49 +1,51 @@
1
- .starlight-aside {
2
- padding: 1rem;
3
- border-inline-start: 0.25rem solid var(--sl-color-asides-border);
4
- color: var(--sl-color-white);
5
- }
6
- .starlight-aside--note {
7
- --sl-color-asides-text-accent: var(--sl-color-blue-high);
8
- --sl-color-asides-border: var(--sl-color-blue);
9
- background-color: var(--sl-color-blue-low);
10
- }
11
- .starlight-aside--tip {
12
- --sl-color-asides-text-accent: var(--sl-color-purple-high);
13
- --sl-color-asides-border: var(--sl-color-purple);
14
- background-color: var(--sl-color-purple-low);
15
- }
16
- .starlight-aside--caution {
17
- --sl-color-asides-text-accent: var(--sl-color-orange-high);
18
- --sl-color-asides-border: var(--sl-color-orange);
19
- background-color: var(--sl-color-orange-low);
20
- }
21
- .starlight-aside--danger {
22
- --sl-color-asides-text-accent: var(--sl-color-red-high);
23
- --sl-color-asides-border: var(--sl-color-red);
24
- background-color: var(--sl-color-red-low);
25
- }
1
+ @layer starlight.components {
2
+ .starlight-aside {
3
+ padding: 1rem;
4
+ border-inline-start: 0.25rem solid var(--sl-color-asides-border);
5
+ color: var(--sl-color-white);
6
+ }
7
+ .starlight-aside--note {
8
+ --sl-color-asides-text-accent: var(--sl-color-blue-high);
9
+ --sl-color-asides-border: var(--sl-color-blue);
10
+ background-color: var(--sl-color-blue-low);
11
+ }
12
+ .starlight-aside--tip {
13
+ --sl-color-asides-text-accent: var(--sl-color-purple-high);
14
+ --sl-color-asides-border: var(--sl-color-purple);
15
+ background-color: var(--sl-color-purple-low);
16
+ }
17
+ .starlight-aside--caution {
18
+ --sl-color-asides-text-accent: var(--sl-color-orange-high);
19
+ --sl-color-asides-border: var(--sl-color-orange);
20
+ background-color: var(--sl-color-orange-low);
21
+ }
22
+ .starlight-aside--danger {
23
+ --sl-color-asides-text-accent: var(--sl-color-red-high);
24
+ --sl-color-asides-border: var(--sl-color-red);
25
+ background-color: var(--sl-color-red-low);
26
+ }
26
27
 
27
- .starlight-aside__title {
28
- display: flex;
29
- gap: 0.5rem;
30
- align-items: center;
31
- font-size: var(--sl-text-h5);
32
- font-weight: 600;
33
- line-height: var(--sl-line-height-headings);
34
- color: var(--sl-color-asides-text-accent);
35
- }
28
+ .starlight-aside__title {
29
+ display: flex;
30
+ gap: 0.5rem;
31
+ align-items: center;
32
+ font-size: var(--sl-text-h5);
33
+ font-weight: 600;
34
+ line-height: var(--sl-line-height-headings);
35
+ color: var(--sl-color-asides-text-accent);
36
+ }
36
37
 
37
- .starlight-aside__icon {
38
- font-size: 1.333em;
39
- width: 1em;
40
- height: 1em;
41
- }
38
+ .starlight-aside__icon {
39
+ font-size: 1.333em;
40
+ width: 1em;
41
+ height: 1em;
42
+ }
42
43
 
43
- .starlight-aside__title + .starlight-aside__content {
44
- margin-top: 0.5rem;
45
- }
44
+ .starlight-aside__title + .starlight-aside__content {
45
+ margin-top: 0.5rem;
46
+ }
46
47
 
47
- .starlight-aside__content a {
48
- color: var(--sl-color-asides-text-accent);
48
+ .starlight-aside__content a {
49
+ color: var(--sl-color-asides-text-accent);
50
+ }
49
51
  }
@@ -0,0 +1 @@
1
+ @layer starlight.base, starlight.reset, starlight.core, starlight.content, starlight.components, starlight.utils;