react-shiki 0.6.0 → 0.7.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
@@ -14,27 +14,30 @@ A performant client-side syntax highlighting component and hook for React, built
14
14
  - [Features](#features)
15
15
  - [Installation](#installation)
16
16
  - [Usage](#usage)
17
+ - [Bundle Options](#bundle-options)
18
+ - [Configuration](#configuration)
17
19
  - [Common Configuration Options](#common-configuration-options)
18
20
  - [Component-specific Props](#component-specific-props)
19
- - [Integration with react-markdown](#integration-with-react-markdown)
20
- - [Handling Inline Code](#handling-inline-code)
21
21
  - [Multi-theme Support](#multi-theme-support)
22
22
  - [Custom Themes](#custom-themes)
23
23
  - [Custom Languages](#custom-languages)
24
24
  - [Preloading Custom Languages](#preloading-custom-languages)
25
25
  - [Custom Transformers](#custom-transformers)
26
- - [Performance](#performance)
27
- - [Throttling Real-time Highlighting](#throttling-real-time-highlighting)
28
- - [Streaming and LLM Chat UI](#streaming-and-llm-chat-ui)
26
+ - [Integration](#integration)
27
+ - [Integration with react-markdown](#integration-with-react-markdown)
28
+ - [Handling Inline Code](#handling-inline-code)
29
+ - [Performance](#performance)
30
+ - [Throttling Real-time Highlighting](#throttling-real-time-highlighting)
31
+ - [Streaming and LLM Chat UI](#streaming-and-llm-chat-ui)
29
32
  <!--toc:end-->
30
33
 
31
34
  ## Features
32
35
 
33
36
  - 🖼️ Provides both a `ShikiHighlighter` component and a `useShikiHighlighter` hook for more flexibility
34
- - 🔐 No `dangerouslySetInnerHTML` - output from Shiki is parsed using `html-react-parser`
35
- - 📦 Supports all built-in Shiki languages and themes
37
+ - 🔐 Shiki output is processed from HAST directly into React elements, no `dangerouslySetInnerHTML` required
38
+ - 📦 Multiple bundle options: Full bundle (~1.2MB gz), web bundle (~695KB gz), or minimal core bundle for fine-grained bundle control
36
39
  - 🖌️ Full support for custom TextMate themes and languages
37
- - 🔧 Supports passing custom Shiki transformers to the highlighter
40
+ - 🔧 Supports passing custom Shiki transformers to the highlighter, in addition to all other options supported by `codeToHast`
38
41
  - 🚰 Performant highlighting of streamed code, with optional throttling
39
42
  - 📚 Includes minimal default styles for code blocks
40
43
  - 🚀 Shiki dynamically imports only the languages and themes used on a page for optimal performance
@@ -55,7 +58,7 @@ You can use either the `ShikiHighlighter` component or the `useShikiHighlighter`
55
58
  **Using the Component:**
56
59
 
57
60
  ```tsx
58
- import { ShikiHighlighter } from "react-shiki";
61
+ import ShikiHighlighter from "react-shiki";
59
62
 
60
63
  function CodeBlock() {
61
64
  return (
@@ -78,18 +81,87 @@ function CodeBlock({ code, language }) {
78
81
  }
79
82
  ```
80
83
 
84
+ ## Bundle Options
85
+ `react-shiki`, like `shiki`, offers three entry points to balance convenience and bundle optimization:
86
+
87
+ ### `react-shiki` (Full Bundle)
88
+ ```tsx
89
+ import ShikiHighlighter from 'react-shiki';
90
+ ```
91
+ - **Size**: ~6.4MB minified, 1.2MB gzipped
92
+ - **Languages**: All Shiki languages and themes
93
+ - **Use case**: Unknown language requirements, maximum language support
94
+ - **Setup**: Zero configuration required
95
+
96
+ ### `react-shiki/web` (Web Bundle)
97
+ ```tsx
98
+ import ShikiHighlighter from 'react-shiki/web';
99
+ ```
100
+ - **Size**: ~3.8MB minified, 695KB gzipped
101
+ - **Languages**: Web-focused languages (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte)
102
+ - **Use case**: Web applications with balanced size/functionality
103
+ - **Setup**: Drop-in replacement for main entry point
104
+
105
+ ### `react-shiki/core` (Minimal Bundle)
106
+ ```tsx
107
+ import ShikiHighlighter, {
108
+ createHighlighterCore, // re-exported from shiki/core
109
+ createOnigurumaEngine, // re-exported from shiki/engine/oniguruma
110
+ createJavaScriptRegexEngine, // re-exported from shiki/engine/javascript
111
+ } from 'react-shiki/core';
112
+
113
+ // Create custom highlighter with dynamic imports to optimize client-side bundle size
114
+ const highlighter = await createHighlighterCore({
115
+ themes: [import('@shikijs/themes/nord')],
116
+ langs: [import('@shikijs/langs/typescript')],
117
+ engine: createOnigurumaEngine(import('shiki/wasm'))
118
+ // or createJavaScriptRegexEngine()
119
+ });
120
+
121
+ <ShikiHighlighter highlighter={highlighter} language="typescript" theme="nord">
122
+ {code}
123
+ </ShikiHighlighter>
124
+ ```
125
+ - **Size**: Minimal (only what you import)
126
+ - **Languages**: User-defined via custom highlighter
127
+ - **Use case**: Production apps requiring maximum bundle control
128
+ - **Setup**: Requires custom highlighter configuration
129
+ - **Engine options**: Choose JavaScript engine (smaller bundle, faster startup) or Oniguruma (WASM, maximum language support)
130
+
131
+ ### RegExp Engines
132
+
133
+ Shiki offers two built-in engines:
134
+ - **Oniguruma** - default, uses the compiled Oniguruma WebAssembly, and offer maximum language support
135
+ - **JavaScript** - smaller bundle, faster startup, recommended when running highlighting on the client
136
+
137
+ Unlike the Oniguruma engine, the JavaScript engine is [strict by default](https://shiki.style/guide/regex-engines#use-with-unsupported-languages). It will throw an error if it encounters an invalid Oniguruma pattern or a pattern that it cannot convert. If you want best-effort results for unsupported grammars, you can enable the forgiving option to suppress any conversion errors:
138
+
139
+ ```tsx
140
+ createJavaScriptRegexEngine({ forgiving: true });
141
+ ```
142
+
143
+ See [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for more info.
144
+
145
+
146
+ ## Configuration
147
+
81
148
  ### Common Configuration Options
82
149
 
83
- | Option | Type | Default | Description |
84
- | ------------------- | ------------------ | --------------- | ------------------------------------------------------------------------- |
85
- | `code` | `string` | - | Code to highlight |
86
- | `language` | `string \| object` | - | Language to highlight, built-in or custom textmate grammer object |
150
+
151
+ | Option | Type | Default | Description |
152
+ | ------------------- | ------------------ | --------------- | ----------------------------------------------------------------------------- |
153
+ | `code` | `string` | - | Code to highlight |
154
+ | `language` | `string \| object` | - | Language to highlight, built-in or custom textmate grammer object |
87
155
  | `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom textmate theme object |
88
156
  | `delay` | `number` | `0` | Delay between highlights (in milliseconds) |
89
- | `transformers` | `array` | `[]` | Custom Shiki transformers for modifying the highlighting output |
90
157
  | `customLanguages` | `array` | `[]` | Array of custom languages to preload |
158
+ | `transformers` | `array` | `[]` | Custom Shiki transformers for modifying the highlighting output |
91
159
  | `cssVariablePrefix` | `string` | `'--shiki'` | Prefix for CSS variables storing theme colors |
92
160
  | `defaultColor` | `string \| false` | `'light'` | Default theme mode when using multiple themes, can also disable default theme |
161
+ | `tabindex` | `number` | `0` | Tab index for the code block |
162
+ | `decorations` | `array` | `[]` | Custom decorations to wrap the highlighted tokens with |
163
+ | `structure` | `string` | `classic` | The structure of the generated HAST and HTML - `classic` or `inline` |
164
+ | [`codeToHastOptions`](https://github.com/shikijs/shiki/blob/main/packages/types/src/options.ts#L121) | - | - | All other options supported by Shiki's `codeToHast` |
93
165
 
94
166
  ### Component-specific Props
95
167
 
@@ -105,13 +177,123 @@ The `ShikiHighlighter` component offers minimal built-in styling and customizati
105
177
  | `style` | `object` | - | Inline style object for the code block |
106
178
  | `langStyle` | `object` | - | Inline style object for the language label |
107
179
 
180
+ ### Multi-theme Support
181
+
182
+ To use multiple theme modes, pass an object with your multi-theme configuration to the `theme` prop in the `ShikiHighlighter` component:
183
+
184
+ ```tsx
185
+ <ShikiHighlighter
186
+ language="tsx"
187
+ theme={{
188
+ light: "github-light",
189
+ dark: "github-dark",
190
+ dim: "github-dark-dimmed",
191
+ }}
192
+ defaultColor="dark"
193
+ >
194
+ {code.trim()}
195
+ </ShikiHighlighter>
196
+ ```
197
+
198
+ Or, when using the hook, pass it to the `theme` parameter:
199
+
200
+ ```tsx
201
+ const highlightedCode = useShikiHighlighter(
202
+ code,
203
+ "tsx",
204
+ {
205
+ light: "github-light",
206
+ dark: "github-dark",
207
+ dim: "github-dark-dimmed",
208
+ },
209
+ {
210
+ defaultColor: "dark",
211
+ }
212
+ );
213
+ ```
214
+
215
+ See [shiki's documentation](https://shiki.matsu.io/docs/themes) for more information on dual and multi theme support, and for the CSS needed to make the themes reactive to your site's theme.
216
+
217
+ ### Custom Themes
218
+
219
+ Custom themes can be passed as a TextMate theme in JavaScript object. For example, [it should look like this](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-themes/themes/dark-plus.json).
220
+
221
+ ```tsx
222
+ import tokyoNight from "../styles/tokyo-night.json";
223
+
224
+ // Using the component
225
+ <ShikiHighlighter language="tsx" theme={tokyoNight}>
226
+ {code.trim()}
227
+ </ShikiHighlighter>
228
+
229
+ // Using the hook
230
+ const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
231
+ ```
232
+
233
+ ### Custom Languages
234
+
235
+ Custom languages should be passed as a TextMate grammar in JavaScript object. For example, [it should look like this](https://github.com/shikijs/textmate-grammars-themes/blob/main/packages/tm-grammars/grammars/typescript.json)
236
+
237
+ ```tsx
238
+ import mcfunction from "../langs/mcfunction.tmLanguage.json";
239
+
240
+ // Using the component
241
+ <ShikiHighlighter language={mcfunction} theme="github-dark">
242
+ {code.trim()}
243
+ </ShikiHighlighter>
244
+
245
+ // Using the hook
246
+ const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
247
+ ```
248
+
249
+ #### Preloading Custom Languages
250
+
251
+ For dynamic highlighting scenarios where language selection happens at runtime:
252
+
253
+ ```tsx
254
+ import mcfunction from "../langs/mcfunction.tmLanguage.json";
255
+ import bosque from "../langs/bosque.tmLanguage.json";
256
+
257
+ // With the component
258
+ <ShikiHighlighter
259
+ language="typescript"
260
+ theme="github-dark"
261
+ customLanguages={[mcfunction, bosque]}
262
+ >
263
+ {code.trim()}
264
+ </ShikiHighlighter>
265
+
266
+ // With the hook
267
+ const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
268
+ customLanguages: [mcfunction, bosque],
269
+ });
270
+ ```
271
+
272
+ ### Custom Transformers
273
+
274
+ ```tsx
275
+ import { customTransformer } from "../utils/shikiTransformers";
276
+
277
+ // Using the component
278
+ <ShikiHighlighter language="tsx" transformers={[customTransformer]}>
279
+ {code.trim()}
280
+ </ShikiHighlighter>
281
+
282
+ // Using the hook
283
+ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
284
+ transformers: [customTransformer],
285
+ });
286
+ ```
287
+
288
+ ## Integration
289
+
108
290
  ### Integration with react-markdown
109
291
 
110
292
  Create a component to handle syntax highlighting:
111
293
 
112
294
  ```tsx
113
295
  import ReactMarkdown from "react-markdown";
114
- import { ShikiHighlighter, isInlineCode } from "react-shiki";
296
+ import ShikiHighlighter, { isInlineCode } from "react-shiki";
115
297
 
116
298
  const CodeHighlight = ({ className, children, node, ...props }) => {
117
299
  const code = String(children).trim();
@@ -155,7 +337,7 @@ ways to replicate this functionality and API.
155
337
  `react-shiki` exports `isInlineCode` which parses the `node` prop from `react-markdown` and identifies inline code by checking for the absence of newline characters:
156
338
 
157
339
  ```tsx
158
- import { isInlineCode, ShikiHighlighter } from "react-shiki";
340
+ import ShikiHighlighter, { isInlineCode } from "react-shiki";
159
341
 
160
342
  const CodeHighlight = ({ className, children, node, ...props }) => {
161
343
  const match = className?.match(/language-(\w+)/);
@@ -224,114 +406,6 @@ const CodeHighlight = ({
224
406
  };
225
407
  ```
226
408
 
227
- ### Multi-theme Support
228
-
229
- To use multiple theme modes, pass an object with your multi-theme configuration to the `theme` prop in the `ShikiHighlighter` component:
230
-
231
- ```tsx
232
- <ShikiHighlighter
233
- language="tsx"
234
- theme={{
235
- light: "github-light",
236
- dark: "github-dark",
237
- dim: "github-dark-dimmed",
238
- }}
239
- defaultColor="dark"
240
- >
241
- {code.trim()}
242
- </ShikiHighlighter>
243
- ```
244
-
245
- Or, when using the hook, pass it to the `theme` parameter:
246
-
247
- ```tsx
248
- const highlightedCode = useShikiHighlighter(
249
- code,
250
- "tsx",
251
- {
252
- light: "github-light",
253
- dark: "github-dark",
254
- dim: "github-dark-dimmed",
255
- },
256
- {
257
- defaultColor: "dark",
258
- }
259
- );
260
- ```
261
-
262
- See [shiki's documentation](https://shiki.matsu.io/docs/themes) for more information on dual and multi theme support, and for the CSS needed to make the themes reactive to your site's theme.
263
-
264
- ### Custom Themes
265
-
266
- Custom themes can be passed as a TextMate theme in JavaScript object. For example, [it should look like this](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-themes/themes/dark-plus.json).
267
-
268
- ```tsx
269
- import tokyoNight from "../styles/tokyo-night.json";
270
-
271
- // Using the component
272
- <ShikiHighlighter language="tsx" theme={tokyoNight}>
273
- {code.trim()}
274
- </ShikiHighlighter>
275
-
276
- // Using the hook
277
- const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
278
- ```
279
-
280
- ### Custom Languages
281
-
282
- Custom languages should be passed as a TextMate grammar in JavaScript object. For example, [it should look like this](https://github.com/shikijs/textmate-grammars-themes/blob/main/packages/tm-grammars/grammars/typescript.json)
283
-
284
- ```tsx
285
- import mcfunction from "../langs/mcfunction.tmLanguage.json";
286
-
287
- // Using the component
288
- <ShikiHighlighter language={mcfunction} theme="github-dark">
289
- {code.trim()}
290
- </ShikiHighlighter>
291
-
292
- // Using the hook
293
- const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
294
- ```
295
-
296
- #### Preloading Custom Languages
297
-
298
- For dynamic highlighting scenarios where language selection happens at runtime:
299
-
300
- ```tsx
301
- import mcfunction from "../langs/mcfunction.tmLanguage.json";
302
- import bosque from "../langs/bosque.tmLanguage.json";
303
-
304
- // With the component
305
- <ShikiHighlighter
306
- language="typescript"
307
- theme="github-dark"
308
- customLanguages={[mcfunction, bosque]}
309
- >
310
- {code.trim()}
311
- </ShikiHighlighter>
312
-
313
- // With the hook
314
- const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
315
- customLanguages: [mcfunction, bosque],
316
- });
317
- ```
318
-
319
- ### Custom Transformers
320
-
321
- ```tsx
322
- import { customTransformer } from "../utils/shikiTransformers";
323
-
324
- // Using the component
325
- <ShikiHighlighter language="tsx" transformers={[customTransformer]}>
326
- {code.trim()}
327
- </ShikiHighlighter>
328
-
329
- // Using the hook
330
- const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
331
- transformers: [customTransformer],
332
- });
333
- ```
334
-
335
409
  ## Performance
336
410
 
337
411
  ### Throttling Real-time Highlighting
@@ -447,7 +521,7 @@ export const CodeHighlight = ({
447
521
  };
448
522
  ```
449
523
 
450
- Passed to `react-markdown` as a `code` component in memo-ized chat messages:
524
+ Passed to `react-markdown` as a `code` component in memoized chat messages:
451
525
 
452
526
  ```tsx
453
527
  const RenderedMessage = React.memo(({ message }: { message: Message }) => (
@@ -0,0 +1,283 @@
1
+ // src/lib/utils.ts
2
+ import { visit } from "unist-util-visit";
3
+ function rehypeInlineCodeProperty() {
4
+ return (tree) => {
5
+ visit(tree, "element", (node, _index, parent) => {
6
+ if (node.tagName === "code" && parent.tagName !== "pre") {
7
+ node.properties.inline = true;
8
+ }
9
+ });
10
+ };
11
+ }
12
+ var isInlineCode = (node) => {
13
+ const textContent = (node.children || []).filter((child) => child.type === "text").map((child) => child.value).join("");
14
+ return !textContent.includes("\n");
15
+ };
16
+ var throttleHighlighting = (performHighlight, timeoutControl, throttleMs) => {
17
+ const now = Date.now();
18
+ clearTimeout(timeoutControl.current.timeoutId);
19
+ const delay = Math.max(0, timeoutControl.current.nextAllowedTime - now);
20
+ timeoutControl.current.timeoutId = setTimeout(() => {
21
+ performHighlight().catch(console.error);
22
+ timeoutControl.current.nextAllowedTime = now + throttleMs;
23
+ }, delay);
24
+ };
25
+
26
+ // src/lib/hook.ts
27
+ import {
28
+ useEffect,
29
+ useMemo,
30
+ useRef,
31
+ useState
32
+ } from "react";
33
+ import { dequal } from "dequal/lite";
34
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
35
+ import { toJsxRuntime } from "hast-util-to-jsx-runtime";
36
+
37
+ // src/lib/resolvers.ts
38
+ var resolveLanguage = (lang, customLanguages) => {
39
+ const normalizedCustomLangs = customLanguages ? Array.isArray(customLanguages) ? customLanguages : [customLanguages] : [];
40
+ if (lang == null || typeof lang === "string" && !lang.trim()) {
41
+ return {
42
+ languageId: "plaintext",
43
+ displayLanguageId: "plaintext",
44
+ langsToLoad: void 0
45
+ };
46
+ }
47
+ if (typeof lang === "object") {
48
+ return {
49
+ languageId: lang.name,
50
+ displayLanguageId: lang.name || null,
51
+ langsToLoad: lang
52
+ };
53
+ }
54
+ const lowerLang = lang.toLowerCase();
55
+ const matches = (str) => str?.toLowerCase() === lowerLang;
56
+ const customMatch = normalizedCustomLangs.find(
57
+ (cl) => matches(cl.name) || matches(cl.scopeName) || matches(cl.scopeName?.split(".").pop()) || cl.aliases?.some(matches) || cl.fileTypes?.some(matches)
58
+ );
59
+ if (customMatch) {
60
+ return {
61
+ languageId: customMatch.name || lang,
62
+ displayLanguageId: lang,
63
+ langsToLoad: customMatch
64
+ };
65
+ }
66
+ return {
67
+ languageId: lang,
68
+ displayLanguageId: lang,
69
+ langsToLoad: lang
70
+ };
71
+ };
72
+ function resolveTheme(themeInput) {
73
+ const isTextmateTheme = typeof themeInput === "object" && "tokenColors" in themeInput && Array.isArray(themeInput.tokenColors);
74
+ const isMultiThemeConfig = typeof themeInput === "object" && themeInput !== null && !isTextmateTheme;
75
+ const validMultiThemeObj = typeof themeInput === "object" && themeInput !== null && !isTextmateTheme && Object.entries(themeInput).some(
76
+ ([key, value]) => key && value && key.trim() !== "" && value !== "" && (typeof value === "string" || isTextmateTheme)
77
+ );
78
+ if (isMultiThemeConfig) {
79
+ const themeId = validMultiThemeObj ? `multi-${Object.values(themeInput).map(
80
+ (theme) => (typeof theme === "string" ? theme : theme?.name) || "custom"
81
+ ).sort().join("-")}` : "multi-default";
82
+ return {
83
+ isMultiTheme: true,
84
+ themeId,
85
+ multiTheme: validMultiThemeObj ? themeInput : null,
86
+ themesToLoad: validMultiThemeObj ? Object.values(themeInput) : []
87
+ };
88
+ }
89
+ return {
90
+ isMultiTheme: false,
91
+ themeId: typeof themeInput === "string" ? themeInput : themeInput?.name || "custom",
92
+ singleTheme: themeInput,
93
+ themesToLoad: [themeInput]
94
+ };
95
+ }
96
+
97
+ // src/lib/hook.ts
98
+ var DEFAULT_THEMES = {
99
+ light: "github-light",
100
+ dark: "github-dark"
101
+ };
102
+ var useStableOptions = (value) => {
103
+ const ref = useRef(value);
104
+ const revision = useRef(0);
105
+ if (typeof value !== "object" || value === null) {
106
+ if (value !== ref.current) {
107
+ ref.current = value;
108
+ revision.current += 1;
109
+ }
110
+ return [value, revision.current];
111
+ }
112
+ if (value !== ref.current) {
113
+ if (!dequal(value, ref.current)) {
114
+ ref.current = value;
115
+ revision.current += 1;
116
+ }
117
+ }
118
+ return [ref.current, revision.current];
119
+ };
120
+ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options = {}) => {
121
+ const [highlightedCode, setHighlightedCode] = useState(null);
122
+ const [stableLang, langRev] = useStableOptions(lang);
123
+ const [stableTheme, themeRev] = useStableOptions(themeInput);
124
+ const [stableOpts, optsRev] = useStableOptions(options);
125
+ const { languageId, langsToLoad } = useMemo(
126
+ () => resolveLanguage(stableLang, stableOpts.customLanguages),
127
+ [stableLang, stableOpts.customLanguages]
128
+ );
129
+ const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } = useMemo(() => resolveTheme(stableTheme), [stableTheme]);
130
+ const timeoutControl = useRef({
131
+ nextAllowedTime: 0,
132
+ timeoutId: void 0
133
+ });
134
+ const shikiOptions = useMemo(() => {
135
+ const { defaultColor, cssVariablePrefix, ...restOptions } = stableOpts;
136
+ const languageOption = { lang: languageId };
137
+ const themeOptions = isMultiTheme ? {
138
+ themes: multiTheme || DEFAULT_THEMES,
139
+ defaultColor,
140
+ cssVariablePrefix
141
+ } : {
142
+ theme: singleTheme || DEFAULT_THEMES.dark
143
+ };
144
+ return { ...languageOption, ...themeOptions, ...restOptions };
145
+ }, [languageId, themeId, langRev, themeRev, optsRev]);
146
+ useEffect(() => {
147
+ let isMounted = true;
148
+ const highlightCode = async () => {
149
+ if (!languageId) return;
150
+ const highlighter = stableOpts.highlighter ? stableOpts.highlighter : await createHighlighter(
151
+ langsToLoad,
152
+ themesToLoad
153
+ );
154
+ const loadedLanguages = highlighter.getLoadedLanguages();
155
+ const langToUse = loadedLanguages.includes(languageId) ? languageId : "plaintext";
156
+ const finalOptions = { ...shikiOptions, lang: langToUse };
157
+ const hast = highlighter.codeToHast(code, finalOptions);
158
+ if (isMounted) {
159
+ setHighlightedCode(toJsxRuntime(hast, { jsx, jsxs, Fragment }));
160
+ }
161
+ };
162
+ const { delay } = stableOpts;
163
+ if (delay) {
164
+ throttleHighlighting(highlightCode, timeoutControl, delay);
165
+ } else {
166
+ highlightCode().catch(console.error);
167
+ }
168
+ return () => {
169
+ isMounted = false;
170
+ clearTimeout(timeoutControl.current.timeoutId);
171
+ };
172
+ }, [
173
+ code,
174
+ shikiOptions,
175
+ stableOpts.delay,
176
+ stableOpts.highlighter,
177
+ langsToLoad,
178
+ themesToLoad
179
+ ]);
180
+ return highlightedCode;
181
+ };
182
+
183
+ // #style-inject:#style-inject
184
+ function styleInject(css, { insertAt } = {}) {
185
+ if (!css || typeof document === "undefined") return;
186
+ const head = document.head || document.getElementsByTagName("head")[0];
187
+ const style = document.createElement("style");
188
+ style.type = "text/css";
189
+ if (insertAt === "top") {
190
+ if (head.firstChild) {
191
+ head.insertBefore(style, head.firstChild);
192
+ } else {
193
+ head.appendChild(style);
194
+ }
195
+ } else {
196
+ head.appendChild(style);
197
+ }
198
+ if (style.styleSheet) {
199
+ style.styleSheet.cssText = css;
200
+ } else {
201
+ style.appendChild(document.createTextNode(css));
202
+ }
203
+ }
204
+
205
+ // src/lib/styles.css
206
+ styleInject(".relative {\n position: relative;\n}\n.defaultStyles pre {\n overflow: auto;\n border-radius: 0.5rem;\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n padding-top: 1.25rem;\n padding-bottom: 1.25rem;\n}\n.languageLabel {\n position: absolute;\n right: 0.75rem;\n top: 0.5rem;\n font-family: monospace;\n font-size: 0.75rem;\n letter-spacing: -0.05em;\n color: rgba(107, 114, 128, 0.85);\n}\n");
207
+
208
+ // src/lib/component.tsx
209
+ import { clsx } from "clsx";
210
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
211
+ var createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
212
+ return ({
213
+ language,
214
+ theme,
215
+ delay,
216
+ transformers,
217
+ defaultColor,
218
+ cssVariablePrefix,
219
+ addDefaultStyles = true,
220
+ style,
221
+ langStyle,
222
+ className,
223
+ langClassName,
224
+ showLanguage = true,
225
+ children: code,
226
+ as: Element = "pre",
227
+ customLanguages,
228
+ ...shikiOptions
229
+ }) => {
230
+ const options = {
231
+ delay,
232
+ transformers,
233
+ customLanguages,
234
+ defaultColor,
235
+ cssVariablePrefix,
236
+ ...shikiOptions
237
+ };
238
+ const { displayLanguageId } = resolveLanguage(
239
+ language,
240
+ customLanguages
241
+ );
242
+ const highlightedCode = useShikiHighlighterImpl(
243
+ code,
244
+ language,
245
+ theme,
246
+ options
247
+ );
248
+ return /* @__PURE__ */ jsxs2(
249
+ Element,
250
+ {
251
+ "data-testid": "shiki-container",
252
+ className: clsx(
253
+ "relative",
254
+ "not-prose",
255
+ addDefaultStyles && "defaultStyles",
256
+ className
257
+ ),
258
+ style,
259
+ id: "shiki-container",
260
+ children: [
261
+ showLanguage && displayLanguageId ? /* @__PURE__ */ jsx2(
262
+ "span",
263
+ {
264
+ className: clsx("languageLabel", langClassName),
265
+ style: langStyle,
266
+ id: "language-label",
267
+ children: displayLanguageId
268
+ }
269
+ ) : null,
270
+ highlightedCode
271
+ ]
272
+ }
273
+ );
274
+ };
275
+ };
276
+
277
+ export {
278
+ rehypeInlineCodeProperty,
279
+ isInlineCode,
280
+ useShikiHighlighter,
281
+ createShikiHighlighterComponent
282
+ };
283
+ //# sourceMappingURL=chunk-XAMWR6XF.js.map