@astrojs/starlight 0.39.3 → 0.41.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.
@@ -1,25 +1,30 @@
1
- import { resolve } from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
- import type { AstroConfig } from 'astro';
4
- import { rehypeHeadingIds } from '@astrojs/markdown-remark';
1
+ import { isUnifiedProcessor, rehypeHeadingIds } from '@astrojs/markdown-remark';
5
2
  import type { Root as RehypeRoot } from 'hast';
6
3
  import type { Root as RemarkRoot } from 'mdast';
7
4
  import remarkDirective from 'remark-directive';
8
5
  import type { Plugin } from 'unified';
9
6
  import type { VFile } from 'vfile';
10
- import { resolveCollectionPath } from '../utils/collection-fs';
11
- import type { HookParameters, StarlightConfig } from '../types';
12
- import { remarkAsides } from './asides';
13
- import { rehypeRtlCodeSupport } from './code-rtl-support';
14
- import rehypeAutolinkHeadings from './heading-links';
7
+ import { remarkAsides, remarkDirectivesRestoration } from './remark-asides';
8
+ import { rehypeRtlCodeSupport } from './rehype-code-rtl-support';
9
+ import rehypeAutolinkHeadings from './rehype-heading-links';
10
+ import {
11
+ getMarkdownProcessorPaths,
12
+ shouldTransformPath,
13
+ type MarkdownProcessorPluginOptions,
14
+ } from './markdown-processor';
15
+
16
+ // Re-exported so callers can narrow `markdown.processor` to the Unified processor and use the
17
+ // remark directive-restoration plugin through the same lazy import that loads the optional
18
+ // `@astrojs/markdown-remark` peer dependency.
19
+ export { isUnifiedProcessor, remarkDirectivesRestoration };
15
20
 
16
21
  /** List of remark plugins to apply. */
17
- export function starlightRemarkPlugins(options: RemarkRehypePluginOptions): RemarkPlugin[] {
22
+ export function starlightRemarkPlugins(options: MarkdownProcessorPluginOptions): RemarkPlugin[] {
18
23
  return [remarkDirective, remarkPlugins(options)];
19
24
  }
20
25
 
21
26
  /** List of rehype plugins to apply. */
22
- export function starlightRehypePlugins(options: RemarkRehypePluginOptions): RehypePlugin[] {
27
+ export function starlightRehypePlugins(options: MarkdownProcessorPluginOptions): RehypePlugin[] {
23
28
  return [
24
29
  ...(options.starlightConfig.markdown.headingLinks ? [[rehypeHeadingIds]] : []),
25
30
  rehypePlugins(options),
@@ -27,8 +32,8 @@ export function starlightRehypePlugins(options: RemarkRehypePluginOptions): Rehy
27
32
  }
28
33
 
29
34
  /** Remark plugin applying other Starlight remark plugins if necessary. */
30
- function remarkPlugins(options: RemarkRehypePluginOptions): RemarkPlugin {
31
- const remarkRehypePaths = getRemarkRehypePaths(options);
35
+ function remarkPlugins(options: MarkdownProcessorPluginOptions): RemarkPlugin {
36
+ const allowedPaths = getMarkdownProcessorPaths(options);
32
37
 
33
38
  return function attacher(this) {
34
39
  const remarkAsidesTransformer = remarkAsides(options).call(this)!;
@@ -36,7 +41,7 @@ function remarkPlugins(options: RemarkRehypePluginOptions): RemarkPlugin {
36
41
  return async function transformer(...args) {
37
42
  const [, file] = args;
38
43
 
39
- if (!shouldTransformFile(file, remarkRehypePaths)) return;
44
+ if (!shouldTransformFile(file, allowedPaths)) return;
40
45
 
41
46
  await remarkAsidesTransformer(...args);
42
47
  };
@@ -44,8 +49,8 @@ function remarkPlugins(options: RemarkRehypePluginOptions): RemarkPlugin {
44
49
  }
45
50
 
46
51
  /** Rehype plugin applying other Starlight rehype plugins if necessary. */
47
- function rehypePlugins(options: RemarkRehypePluginOptions): RehypePlugin {
48
- const remarkRehypePaths = getRemarkRehypePaths(options);
52
+ function rehypePlugins(options: MarkdownProcessorPluginOptions): RehypePlugin {
53
+ const allowedPaths = getMarkdownProcessorPaths(options);
49
54
 
50
55
  return function attacher(this) {
51
56
  const rehypeRtlCodeSupportTransformer = rehypeRtlCodeSupport(options).call(this);
@@ -54,7 +59,7 @@ function rehypePlugins(options: RemarkRehypePluginOptions): RehypePlugin {
54
59
  return async function transformer(...args) {
55
60
  const [, file] = args;
56
61
 
57
- if (!shouldTransformFile(file, remarkRehypePaths)) return;
62
+ if (!shouldTransformFile(file, allowedPaths)) return;
58
63
 
59
64
  await rehypeRtlCodeSupportTransformer(...args);
60
65
 
@@ -65,52 +70,17 @@ function rehypePlugins(options: RemarkRehypePluginOptions): RehypePlugin {
65
70
  };
66
71
  }
67
72
 
68
- /**
69
- * Returns the paths to the Starlight docs collection and any additional paths defined in the
70
- * `starlightConfig.markdown.processedDirs` option that can be used with the
71
- * `shouldTransformFile()` utility to determine if a file should be transformed by a plugin or not.
72
- */
73
- function getRemarkRehypePaths(options: RemarkRehypePluginOptions): string[] {
74
- const paths = [normalizePath(resolveCollectionPath('docs', options.astroConfig.srcDir))];
75
-
76
- for (const processedDir of options.starlightConfig.markdown.processedDirs) {
77
- paths.push(normalizePath(resolve(fileURLToPath(options.astroConfig.root), processedDir)));
78
- }
79
-
80
- return paths;
81
- }
82
-
83
73
  /**
84
74
  * Determines if a file should be transformed by a remark/rehype plugin, e.g. files without a known
85
- * path or files that are not part of the allowed remark/rehype paths are skipped.
75
+ * path or files that are not part of the allowed paths are skipped.
86
76
  */
87
- function shouldTransformFile(file: VFile, remarkRehypePaths: string[]) {
77
+ function shouldTransformFile(file: VFile, allowedPaths: string[]) {
88
78
  // If the content is rendered using the content loader `renderMarkdown()` API, a file path
89
79
  // is not provided.
90
80
  // In that case, we skip the file.
91
81
  if (!file?.path) return false;
92
82
 
93
- const normalizedPath = normalizePath(file.path);
94
-
95
- // If the document is not part of the allowed remark/rehype paths, skip it.
96
- return remarkRehypePaths.some((path) => normalizedPath.startsWith(path));
97
- }
98
-
99
- /**
100
- * File path separators seems to be inconsistent on Windows between remark/rehype plugins used on
101
- * Markdown vs MDX files.
102
- * For the time being, we normalize all paths to unix style paths.
103
- */
104
- const backSlashRegex = /\\/g;
105
- function normalizePath(path: string) {
106
- return path.replace(backSlashRegex, '/');
107
- }
108
-
109
- export interface RemarkRehypePluginOptions {
110
- starlightConfig: Pick<StarlightConfig, 'defaultLocale' | 'locales' | 'markdown'>;
111
- astroConfig: Pick<AstroConfig, 'root' | 'srcDir'>;
112
- useTranslations: HookParameters<'config:setup'>['useTranslations'];
113
- absolutePathToLang: HookParameters<'config:setup'>['absolutePathToLang'];
83
+ return shouldTransformPath(file.path, allowedPaths);
114
84
  }
115
85
 
116
86
  type RemarkPlugin = Plugin<[], RemarkRoot>;
@@ -0,0 +1,288 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { satteriHeadingIdsPlugin } from '@astrojs/markdown-satteri';
3
+ import type { Element, Properties } from 'hast';
4
+ import type { Paragraph } from 'mdast';
5
+ import { directiveToMarkdown } from 'mdast-util-directive';
6
+ import { toMarkdown } from 'mdast-util-to-markdown';
7
+ import type {
8
+ HastPluginDefinition,
9
+ HastPluginInput,
10
+ MdastPluginInput,
11
+ MdastPluginDefinition,
12
+ } from 'satteri';
13
+ import { anchorLinkIconPath } from './anchor-icon';
14
+ import { asideIconPathAttrs, isAsideVariant } from './aside-icons';
15
+ import {
16
+ getMarkdownProcessorPaths,
17
+ shouldTransformPath,
18
+ type MarkdownProcessorPluginOptions,
19
+ } from './markdown-processor';
20
+ import { Icons } from '../components-internals/Icons';
21
+ import { throwInvalidAsideIconError } from './asides-error';
22
+ import type { StarlightIcon } from '../types';
23
+
24
+ export function starlightSatteriPlugins(options: MarkdownProcessorPluginOptions): {
25
+ mdastPlugins: MdastPluginInput[];
26
+ hastPlugins: HastPluginInput[];
27
+ } {
28
+ const allowedPaths = getMarkdownProcessorPaths(options);
29
+ return {
30
+ mdastPlugins: [satteriAsidesPlugin(options, allowedPaths)],
31
+ hastPlugins: [
32
+ satteriRtlCodeSupportPlugin(allowedPaths),
33
+ ...(options.starlightConfig.markdown.headingLinks
34
+ ? [() => satteriHeadingIdsPlugin(), satteriAutolinkHeadingsPlugin(options, allowedPaths)]
35
+ : []),
36
+ ],
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Recover directives Starlight didn't claim so user content isn't dropped
42
+ */
43
+ export function satteriDirectivesRestoration(): MdastPluginDefinition {
44
+ return {
45
+ name: 'starlight-directives-restoration',
46
+ textDirective(node) {
47
+ // Leave directives another plugin already handled (i.e. set `data` on) untouched.
48
+ if (node.data !== undefined) return;
49
+ return { type: 'text', value: serializeDirective(node) };
50
+ },
51
+ leafDirective(node) {
52
+ if (node.data !== undefined) return;
53
+ return {
54
+ type: 'paragraph',
55
+ children: [{ type: 'text', value: serializeDirective(node) }],
56
+ };
57
+ },
58
+ containerDirective(node) {
59
+ if (node.data !== undefined) return;
60
+ return paragraphElement('div', {}, [...node.children]);
61
+ },
62
+ };
63
+ }
64
+
65
+ function paragraphElement(
66
+ tagName: string,
67
+ properties: Properties,
68
+ children: unknown[] = []
69
+ ): Paragraph {
70
+ return {
71
+ type: 'paragraph',
72
+ data: { hName: tagName, hProperties: properties },
73
+ children: children as Paragraph['children'],
74
+ };
75
+ }
76
+
77
+ /** Convert `:::variant` directive blocks into styled asides. */
78
+ function satteriAsidesPlugin(
79
+ options: MarkdownProcessorPluginOptions,
80
+ allowedPaths: string[]
81
+ ): MdastPluginDefinition {
82
+ return {
83
+ name: 'starlight-asides',
84
+ containerDirective(node, ctx) {
85
+ if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return;
86
+ if (!isAsideVariant(node.name)) return;
87
+
88
+ const variant = node.name;
89
+ // `shouldTransformPath` above already returned for a missing `fileURL`.
90
+ const filename = fileURLToPath(ctx.fileURL!);
91
+ const t = options.useTranslations(options.absolutePathToLang(filename));
92
+
93
+ let title = t(`aside.${variant}`);
94
+ let titleNode: unknown[] = [{ type: 'text', value: title }];
95
+ const children = [...node.children];
96
+ const firstChild = children[0];
97
+ if (
98
+ firstChild?.type === 'paragraph' &&
99
+ firstChild.data?.directiveLabel &&
100
+ firstChild.children.length > 0
101
+ ) {
102
+ titleNode = firstChild.children;
103
+ title = ctx.textContent(firstChild);
104
+ children.shift();
105
+ }
106
+
107
+ const customIconName = node.attributes?.['icon'];
108
+ let innerSvgHtml: string;
109
+ if (customIconName) {
110
+ const icon = Icons[customIconName as StarlightIcon];
111
+ if (!icon) throwInvalidAsideIconError(customIconName);
112
+ innerSvgHtml = icon;
113
+ } else {
114
+ innerSvgHtml = asideIconPathAttrs[variant]
115
+ .map((attrs) => `<path${attrsToHtml(attrs)}/>`)
116
+ .join('');
117
+ }
118
+ const iconSvg = `<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon">${innerSvgHtml}</svg>`;
119
+
120
+ return paragraphElement(
121
+ 'aside',
122
+ {
123
+ 'aria-label': title,
124
+ class: `starlight-aside starlight-aside--${variant}`,
125
+ },
126
+ [
127
+ paragraphElement('p', { class: 'starlight-aside__title', 'aria-hidden': 'true' }, [
128
+ { type: 'html', value: iconSvg },
129
+ ...titleNode,
130
+ ]),
131
+ paragraphElement('div', { class: 'starlight-aside__content' }, children),
132
+ ]
133
+ );
134
+ },
135
+ };
136
+ }
137
+
138
+ function attrsToHtml(attrs: Record<string, string>): string {
139
+ let out = '';
140
+ for (const [key, value] of Object.entries(attrs)) {
141
+ out += ` ${key}="${value.replace(/&/g, '&amp;').replace(/"/g, '&quot;')}"`;
142
+ }
143
+ return out;
144
+ }
145
+
146
+ function serializeDirective(node: Parameters<typeof toMarkdown>[0]): string {
147
+ const md = toMarkdown(node, { extensions: [directiveToMarkdown()] });
148
+ return md.at(-1) === '\n' ? md.slice(0, -1) : md;
149
+ }
150
+
151
+ function satteriRtlCodeSupportPlugin(allowedPaths: string[]): () => HastPluginDefinition {
152
+ return () => {
153
+ // HACK: Sätteri currently does not expose a way to either know the parent of a node, or
154
+ // skipping a subtree visit. To work around this, we manually track the source spans of `<pre>`
155
+ // elements and skip applying `dir="auto"` to `<code>` elements inside those spans. This is:
156
+ // bad, because it means that it won't work for nodes without positions (e.g. generated nodes),
157
+ // but it's as good as it gets right now.
158
+ const preSpans: Array<[number, number]> = [];
159
+ return {
160
+ name: 'starlight-rtl-code-support',
161
+ element: [
162
+ {
163
+ filter: ['pre'],
164
+ visit(node, ctx) {
165
+ if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return;
166
+ const span = nodeSpan(node);
167
+ if (span) preSpans.push(span);
168
+ if (node.properties && 'dir' in node.properties) return;
169
+ ctx.setProperty(node, 'dir', 'ltr');
170
+ },
171
+ },
172
+ {
173
+ filter: ['code'],
174
+ visit(node, ctx) {
175
+ if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return;
176
+ if (isInsideSpan(nodeSpan(node), preSpans)) return;
177
+ if (node.properties && 'dir' in node.properties) return;
178
+ ctx.setProperty(node, 'dir', 'auto');
179
+ },
180
+ },
181
+ ],
182
+ // Shiki runs ahead of us and replaces the highlighted `<pre>` element with a raw HTML
183
+ // node, so the `pre` element visitor above never sees it. Patch the raw markup instead.
184
+ raw(node, ctx) {
185
+ if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return undefined;
186
+ const value = ltrRawPre(node.value);
187
+ if (value === null) return undefined;
188
+ return { type: 'raw', value };
189
+ },
190
+ };
191
+ };
192
+ }
193
+
194
+ /** The source byte span of a node, or `null` when it carries no position (e.g. a generated node). */
195
+ function nodeSpan(node: { position?: Element['position'] }): [number, number] | null {
196
+ const start = node.position?.start.offset;
197
+ const end = node.position?.end.offset;
198
+ return typeof start === 'number' && typeof end === 'number' ? [start, end] : null;
199
+ }
200
+
201
+ function isInsideSpan(span: [number, number] | null, spans: Array<[number, number]>): boolean {
202
+ if (!span) return false;
203
+ return spans.some(([start, end]) => span[0] >= start && span[1] <= end);
204
+ }
205
+
206
+ const rawPreOpenTag = /<pre(?=[\s>])[^>]*>/;
207
+
208
+ /**
209
+ * Add `dir="ltr"` to the opening tag of a raw `<pre>` HTML string, unless it already declares a
210
+ * `dir`. Returns `null` when the value isn’t a `<pre>`, leaving unrelated raw HTML untouched.
211
+ */
212
+ function ltrRawPre(value: string): string | null {
213
+ const openTag = value.match(rawPreOpenTag)?.[0];
214
+ if (!openTag || /\sdir\s*=/.test(openTag)) return null;
215
+ return value.replace(openTag, () => `<pre dir="ltr"${openTag.slice(4)}`);
216
+ }
217
+
218
+ function satteriAutolinkHeadingsPlugin(
219
+ options: MarkdownProcessorPluginOptions,
220
+ allowedPaths: string[]
221
+ ): HastPluginDefinition {
222
+ return {
223
+ name: 'starlight-autolink-headings',
224
+ element: {
225
+ filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
226
+ visit(node, ctx) {
227
+ if (!shouldTransformPath(ctx.fileURL, allowedPaths)) return;
228
+
229
+ const id = node.properties?.['id'];
230
+ if (typeof id !== 'string' || !id) return;
231
+
232
+ const title = ctx.textContent(node);
233
+ // `shouldTransformPath` above already returned for a missing `fileURL`.
234
+ const filename = fileURLToPath(ctx.fileURL!);
235
+ const t = options.useTranslations(options.absolutePathToLang(filename));
236
+ const accessibleLabel = t('heading.anchorLabel', {
237
+ title,
238
+ interpolation: { escapeValue: false },
239
+ });
240
+
241
+ return {
242
+ type: 'element',
243
+ tagName: 'div',
244
+ properties: { class: `sl-heading-wrapper level-${node.tagName}` },
245
+ children: [
246
+ node,
247
+ {
248
+ type: 'element',
249
+ tagName: 'a',
250
+ properties: { class: 'sl-anchor-link', href: '#' + id },
251
+ children: [
252
+ {
253
+ type: 'element',
254
+ tagName: 'span',
255
+ properties: { 'aria-hidden': 'true', class: 'sl-anchor-icon' },
256
+ children: [
257
+ {
258
+ type: 'element',
259
+ tagName: 'svg',
260
+ properties: { width: '16', height: '16', viewBox: '0 0 24 24' },
261
+ children: [
262
+ {
263
+ type: 'element',
264
+ tagName: 'path',
265
+ properties: {
266
+ fill: 'currentcolor',
267
+ d: anchorLinkIconPath,
268
+ },
269
+ children: [],
270
+ },
271
+ ],
272
+ },
273
+ ],
274
+ },
275
+ {
276
+ type: 'element',
277
+ tagName: 'span',
278
+ properties: { class: 'sr-only', 'data-pagefind-ignore': true },
279
+ children: [{ type: 'text', value: accessibleLabel }],
280
+ },
281
+ ],
282
+ },
283
+ ],
284
+ };
285
+ },
286
+ },
287
+ };
288
+ }
@@ -0,0 +1,40 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import type { ViteUserConfig } from 'astro';
3
+
4
+ // https://vite.dev/guide/api-plugin#hook-filters
5
+ const componentsBarrelIdFilter = /[\\/]components\.ts(?:\?.*)?$/;
6
+
7
+ const backSlashRegex = /\\/g;
8
+ const queryStringRegex = /\?.*$/;
9
+
10
+ const starlightComponentsBarrelId = normalizeId(
11
+ fileURLToPath(new URL('../components.ts', import.meta.url))
12
+ );
13
+
14
+ /**
15
+ * Vite plugin that marks the Starlight components barrel file as having no side effects so that
16
+ * lazy barrel optimization can be applied to it.
17
+ *
18
+ * @see https://rolldown.rs/in-depth/lazy-barrel-optimization
19
+ */
20
+ export function vitePluginStarlightLazyBarrelOptimization(): VitePlugin {
21
+ return {
22
+ name: 'vite-plugin-starlight-lazy-barrel-optimization',
23
+ enforce: 'pre',
24
+ transform: {
25
+ filter: {
26
+ id: componentsBarrelIdFilter,
27
+ },
28
+ handler(code, id) {
29
+ if (normalizeId(id) !== starlightComponentsBarrelId) return;
30
+ return { code, moduleSideEffects: false };
31
+ },
32
+ },
33
+ };
34
+ }
35
+
36
+ function normalizeId(id: string) {
37
+ return id.replace(backSlashRegex, '/').replace(queryStringRegex, '');
38
+ }
39
+
40
+ type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
@@ -17,7 +17,7 @@ function resolveVirtualModuleId<T extends string>(id: T): `\0${T}` {
17
17
  }
18
18
 
19
19
  /** Vite plugin that exposes Starlight user config and project context via virtual modules. */
20
- export function vitePluginStarlightUserConfig(
20
+ export function vitePluginStarlightVirtualModules(
21
21
  {
22
22
  command,
23
23
  isNodeCompatibleEnv,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.39.3",
3
+ "version": "0.41.0",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
@@ -42,25 +42,32 @@
42
42
  "./style/markdown.css": "./style/markdown.css"
43
43
  },
44
44
  "peerDependencies": {
45
- "astro": "^6.0.0"
45
+ "@astrojs/markdown-remark": "^7.2.0",
46
+ "astro": "^7.0.2"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "@astrojs/markdown-remark": {
50
+ "optional": true
51
+ }
46
52
  },
47
53
  "devDependencies": {
54
+ "@astrojs/markdown-remark": "^7.2.0",
48
55
  "@playwright/test": "^1.59.1",
49
56
  "@types/node": "^22.19.17",
50
57
  "@vitest/coverage-v8": "^4.1.5",
51
- "astro": "^6.3.1",
58
+ "astro": "^7.0.2",
52
59
  "linkedom": "^0.18.12",
53
60
  "vitest": "^4.1.5"
54
61
  },
55
62
  "dependencies": {
56
- "@astrojs/markdown-remark": "^7.1.1",
57
- "@astrojs/mdx": "^5.0.4",
63
+ "@astrojs/markdown-satteri": "^0.3.2",
64
+ "@astrojs/mdx": "^7.0.0",
58
65
  "@astrojs/sitemap": "^3.7.2",
59
66
  "@pagefind/default-ui": "^1.3.0",
60
67
  "@types/hast": "^3.0.4",
61
68
  "@types/js-yaml": "^4.0.9",
62
69
  "@types/mdast": "^4.0.4",
63
- "astro-expressive-code": "^0.42.0",
70
+ "astro-expressive-code": "^0.43.1",
64
71
  "bcp-47": "^2.1.0",
65
72
  "hast-util-from-html": "^2.0.3",
66
73
  "hast-util-select": "^6.0.4",
@@ -77,6 +84,7 @@
77
84
  "rehype": "^13.0.2",
78
85
  "rehype-format": "^5.0.1",
79
86
  "remark-directive": "^4.0.0",
87
+ "satteri": "^0.9.1",
80
88
  "ultrahtml": "^1.6.0",
81
89
  "unified": "^11.0.5",
82
90
  "unist-util-visit": "^5.1.0",
@@ -16,14 +16,9 @@ const {
16
16
  Astro.props,
17
17
  'Invalid prop passed to the `<Badge/>` component.'
18
18
  );
19
-
20
- /**
21
- * The fragment around the element is used as a workaround to avoid a trailing whitespace in the output.
22
- * @see https://github.com/withastro/compiler/issues/1003
23
- */
24
19
  ---
25
20
 
26
- <><span class:list={['sl-badge', variant, size, customClass]} {...attrs}>{text}</span></>
21
+ <span class:list={['sl-badge', variant, size, customClass]} {...attrs}>{text}</span>
27
22
 
28
23
  <style>
29
24
  @layer starlight.components {
@@ -11,24 +11,17 @@ interface Props {
11
11
 
12
12
  const { name, label, size = '1em', color } = Astro.props;
13
13
  const a11yAttrs = label ? ({ 'aria-label': label } as const) : ({ 'aria-hidden': 'true' } as const);
14
-
15
- /**
16
- * The fragment around the element is used as a workaround to avoid a trailing whitespace in the output.
17
- * @see https://github.com/withastro/compiler/issues/1003
18
- */
19
14
  ---
20
15
 
21
- <>
22
- <svg
23
- {...a11yAttrs}
24
- class={Astro.props.class}
25
- width="16"
26
- height="16"
27
- viewBox="0 0 24 24"
28
- fill="currentColor"
29
- set:html={Icons[name]}
30
- />
31
- </>
16
+ <svg
17
+ {...a11yAttrs}
18
+ class={Astro.props.class}
19
+ width="16"
20
+ height="16"
21
+ viewBox="0 0 24 24"
22
+ fill="currentColor"
23
+ set:html={Icons[name]}
24
+ />
32
25
 
33
26
  <style define:vars={{ 'sl-icon-color': color, 'sl-icon-size': size }}>
34
27
  @layer starlight.components {