react-shiki 0.10.1 → 0.11.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.
@@ -1,4 +1,4 @@
1
- import './style.css';
1
+ import * as React from "react";
2
2
  import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { toJsxRuntime } from "hast-util-to-jsx-runtime";
@@ -36,36 +36,103 @@ const throttleHighlighting = (performHighlight, timeoutControl, throttleMs) => {
36
36
  }, delay);
37
37
  };
38
38
  //#endregion
39
+ //#region src/styles/component.css?inline
40
+ var component_default = ":where(.rs-root){position:relative}:where(.rs-root.rs-default-styles pre){border-radius:.5rem;padding-block:1.25rem;padding-inline:1.5rem;overflow:auto}:where(.rs-root .rs-language-label){z-index:1;letter-spacing:-.05em;color:#6b7280d9;font-family:monospace;font-size:.75rem;position:absolute;top:.5rem;right:.75rem}\n";
41
+ //#endregion
42
+ //#region src/styles/features.css?inline
43
+ var features_default = ":where(.rs-has-line-numbers){counter-reset:line-number calc(var(--line-start,1) - 1)}:where(.rs-has-line-numbers .rs-line-number):before{counter-increment:line-number;content:counter(line-number);box-sizing:content-box;min-width:var(--rs-line-numbers-width,2ch);padding-left:var(--rs-line-numbers-padding-left,0ch);padding-right:var(--rs-line-numbers-padding-right,2ch);color:var(--rs-line-numbers-foreground,#6b728080);font-size:var(--rs-line-numbers-font-size,inherit);font-weight:var(--rs-line-numbers-font-weight,inherit);line-height:var(--rs-line-numbers-line-height,inherit);font-family:var(--rs-line-numbers-font-family,inherit);opacity:var(--rs-line-numbers-opacity,1);user-select:none;pointer-events:none;justify-content:flex-end;align-items:flex-start;display:inline-flex}:where(.rs-has-highlighted-lines){width:fit-content;min-width:100%;display:block}:where(.rs-highlighted-line){--_rs-hl-background:var(--rs-highlighted-line-background,color-mix(in srgb, currentColor 10%, transparent));background-color:var(--_rs-hl-background);width:100%;min-height:1lh;display:inline-block}:where(.rs-default-styles .rs-highlighted-line){border-image:linear-gradient(var(--_rs-hl-background) 0 0) fill 0 / 0 / 0 100vw;background-color:#0000}:where(.rs-has-line-numbers .rs-highlighted-line):before{color:var(--rs-highlighted-line-number-foreground,color-mix(in srgb, currentColor 65%, transparent))}\n";
44
+ //#endregion
45
+ //#region src/lib/styles.ts
46
+ const injected = /* @__PURE__ */ new Set();
47
+ const useStyleInsertionEffect = React.useInsertionEffect ?? React.useLayoutEffect;
48
+ const inject = (css, key) => {
49
+ if (injected.has(key) || typeof document === "undefined") return;
50
+ injected.add(key);
51
+ try {
52
+ const sheet = new CSSStyleSheet();
53
+ sheet.replaceSync(css);
54
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
55
+ } catch {
56
+ const style = document.createElement("style");
57
+ style.dataset.reactShiki = key;
58
+ style.textContent = css;
59
+ document.head.append(style);
60
+ }
61
+ };
62
+ /** Installs component styles before layout without triggering a render. */
63
+ const useComponentStyles = () => {
64
+ useStyleInsertionEffect(() => {
65
+ inject(component_default, "component");
66
+ }, []);
67
+ };
68
+ /** Installs line-number/highlight styles only when those options are used. */
69
+ const useFeatureStyles = (enabled) => {
70
+ useStyleInsertionEffect(() => {
71
+ if (enabled) inject(features_default, "features");
72
+ }, [enabled]);
73
+ };
74
+ //#endregion
39
75
  //#region src/lib/language.ts
40
76
  const FALLBACK_LANGUAGE = "plaintext";
41
- const getEmbeddedLanguages = (code, languageId, highlighter) => {
42
- const bundled = highlighter.getBundledLanguages();
43
- return guessEmbeddedLanguages(code, languageId).flatMap((language) => bundled[language] ?? []);
44
- };
45
77
  const toArray = (value) => value == null ? [] : Array.isArray(value) ? value : [value];
78
+ const isDynamicLanguage = (lang) => typeof lang === "function" || lang instanceof Promise || Array.isArray(lang) || typeof lang === "object" && lang != null && "default" in lang;
79
+ const isRegistration = (lang) => typeof lang === "object" && lang != null && !isDynamicLanguage(lang);
80
+ /** Dedupe identity: strings and registrations by value, dynamic inputs by reference */
46
81
  const languageKey = (lang) => {
47
82
  if (lang == null) return null;
48
83
  if (typeof lang === "string") return `s:${lang}`;
84
+ if (isDynamicLanguage(lang)) return lang;
49
85
  return `o:${lang.name}::${lang.scopeName}`;
50
86
  };
51
87
  const dedupeLanguages = (langs) => {
52
88
  const seen = /* @__PURE__ */ new Set();
53
- const deduped = [];
54
- for (const lang of langs) {
89
+ return langs.filter((lang) => {
55
90
  const key = languageKey(lang);
56
- if (key == null || seen.has(key)) continue;
91
+ if (key == null || seen.has(key)) return false;
57
92
  seen.add(key);
58
- deduped.push(lang);
59
- }
60
- return deduped;
93
+ return true;
94
+ });
95
+ };
96
+ /**
97
+ * A registration matches a query by name, scopeName (full or its last
98
+ * segment, e.g. "source.tsx" -> "tsx"), aliases, or fileTypes.
99
+ */
100
+ const registrationMatches = (candidate, matches) => matches(candidate.name) || matches(candidate.scopeName) || matches(candidate.scopeName?.split(".").pop()) || !!candidate.aliases?.some(matches) || !!candidate.fileTypes?.some(matches);
101
+ /**
102
+ * Resolves the language prop to a shiki language id plus everything the
103
+ * factory should load (primary, preloaded, and custom grammars, deduped).
104
+ */
105
+ const resolveLanguage = (lang, customLanguages, langAliases, preloadLanguages) => {
106
+ const customLangs = toArray(customLanguages);
107
+ const preloadLangs = toArray(preloadLanguages);
108
+ const result = (languageId, primary) => ({
109
+ languageId,
110
+ langsToLoad: dedupeLanguages([
111
+ primary,
112
+ ...preloadLangs,
113
+ ...customLangs
114
+ ])
115
+ });
116
+ if (lang == null || typeof lang === "string" && !lang.trim()) return result(FALLBACK_LANGUAGE);
117
+ if (typeof lang === "object") return result(lang.name, lang);
118
+ const normalized = lang.trim();
119
+ const query = normalized.toLowerCase();
120
+ const matches = (str) => str?.toLowerCase() === query;
121
+ const customMatch = [...customLangs, ...preloadLangs].filter(isRegistration).find((candidate) => registrationMatches(candidate, matches));
122
+ if (customMatch) return result(customMatch.name || normalized, customMatch);
123
+ const alias = Object.entries(langAliases ?? {}).find(([name]) => matches(name))?.[1];
124
+ if (alias) return result(alias, alias);
125
+ return result(normalized, normalized);
61
126
  };
62
127
  /**
63
128
  * Used in factories to check if language is supported.
64
- * Objects are validated as grammar registrations (name + scopeName).
129
+ * Objects are validated as grammar registrations (name + scopeName);
130
+ * dynamic imports pass through for shiki to resolve.
65
131
  */
66
132
  const isLoadableLanguage = (lang, bundledLanguages) => {
67
133
  if (lang == null) return false;
68
134
  if (typeof lang === "string") return lang in bundledLanguages;
135
+ if (isDynamicLanguage(lang)) return true;
69
136
  return typeof lang.name === "string" && typeof lang.scopeName === "string";
70
137
  };
71
138
  /**
@@ -73,50 +140,9 @@ const isLoadableLanguage = (lang, bundledLanguages) => {
73
140
  * Falls back to "plaintext" if not supported.
74
141
  */
75
142
  const resolveLoadedLanguage = (languageId, loadedLanguages) => isSpecialLang(languageId) || loadedLanguages.includes(languageId) ? languageId : FALLBACK_LANGUAGE;
76
- /**
77
- * Resolves the language input to standardized IDs and objects for Shiki
78
- * @param lang The language input from props
79
- * @param customLanguages An array of custom textmate grammar objects or a single grammar object
80
- * @returns A LanguageResult object containing:
81
- * - languageId: The resolved language ID
82
- * - langsToLoad: The language objects/string ids to load
83
- */
84
- const resolveLanguage = (lang, customLanguages, langAliases, preloadLanguages) => {
85
- const customLangs = toArray(customLanguages);
86
- const preloadLangs = toArray(preloadLanguages);
87
- const customMatchPool = [...customLangs, ...preloadLangs];
88
- let languageId = FALLBACK_LANGUAGE;
89
- let primaryLang;
90
- if (lang == null || typeof lang === "string" && !lang.trim()) return {
91
- languageId,
92
- langsToLoad: dedupeLanguages([...preloadLangs, ...customLangs])
93
- };
94
- if (typeof lang === "object") {
95
- languageId = lang.name;
96
- primaryLang = lang;
97
- } else {
98
- const lowerLang = lang.toLowerCase();
99
- const matches = (str) => str?.toLowerCase() === lowerLang;
100
- const customMatch = customMatchPool.find((candidate) => typeof candidate === "object" && candidate != null && !!(matches(candidate.name) || matches(candidate.scopeName) || matches(candidate.scopeName?.split(".").pop()) || candidate.aliases?.some(matches) || candidate.fileTypes?.some(matches)));
101
- if (customMatch) {
102
- languageId = customMatch.name || lang;
103
- primaryLang = customMatch;
104
- } else if (langAliases?.[lang]) {
105
- languageId = langAliases[lang];
106
- primaryLang = langAliases[lang];
107
- } else {
108
- languageId = lang;
109
- primaryLang = lang;
110
- }
111
- }
112
- return {
113
- languageId,
114
- langsToLoad: dedupeLanguages([
115
- primaryLang,
116
- ...preloadLangs,
117
- ...customLangs
118
- ])
119
- };
143
+ const getEmbeddedLanguages = (code, languageId, highlighter) => {
144
+ const bundled = highlighter.getBundledLanguages();
145
+ return guessEmbeddedLanguages(code, languageId).flatMap((language) => bundled[language] ?? []);
120
146
  };
121
147
  //#endregion
122
148
  //#region src/lib/theme.ts
@@ -154,7 +180,33 @@ function resolveTheme(themeInput) {
154
180
  };
155
181
  }
156
182
  //#endregion
183
+ //#region src/lib/engine.ts
184
+ const engines = /* @__PURE__ */ new Map();
185
+ const loaders = {
186
+ javascript: () => import("shiki/engine/javascript").then((m) => m.createJavaScriptRegexEngine()),
187
+ oniguruma: () => import("shiki/engine/oniguruma").then((m) => m.createOnigurumaEngine(import("shiki/wasm")))
188
+ };
189
+ /**
190
+ * Resolves a named engine to a lazily created, cached instance.
191
+ * Engine instances and undefined pass through untouched.
192
+ *
193
+ * The cache makes named engines referentially stable across renders and
194
+ * call sites, unlike inline `createJavaScriptRegexEngine()` calls.
195
+ */
196
+ const resolveEngine = (engine) => {
197
+ if (typeof engine !== "string") return engine;
198
+ const load = loaders[engine];
199
+ if (!load) throw new Error(`[react-shiki] unknown engine '${engine}', expected 'javascript' or 'oniguruma'`);
200
+ const cached = engines.get(engine);
201
+ if (cached) return cached;
202
+ const instance = load();
203
+ instance.catch(() => engines.delete(engine));
204
+ engines.set(engine, instance);
205
+ return instance;
206
+ };
207
+ //#endregion
157
208
  //#region src/lib/transformers.ts
209
+ const toLineNumberSet = (lineNumbers) => new Set(lineNumbers.filter(Number.isInteger));
158
210
  /**
159
211
  * Creates a transformer that enables line numbers display
160
212
  * @param startLine - The starting line number (defaults to 1)
@@ -165,11 +217,10 @@ function lineNumbersTransformer(startLine = 1) {
165
217
  code(node) {
166
218
  this.addClassToHast(node, "rs-has-line-numbers");
167
219
  if (startLine !== 1) {
168
- const existingStyle = node.properties?.style || "";
169
- const newStyle = existingStyle ? `${existingStyle}; --line-start: ${startLine}` : `--line-start: ${startLine}`;
220
+ const existing = node.properties?.style;
170
221
  node.properties = {
171
222
  ...node.properties,
172
- style: newStyle
223
+ style: [existing, `--line-start: ${startLine}`].filter(Boolean).join("; ")
173
224
  };
174
225
  }
175
226
  },
@@ -179,6 +230,25 @@ function lineNumbersTransformer(startLine = 1) {
179
230
  }
180
231
  };
181
232
  }
233
+ /**
234
+ * Creates a transformer that marks selected displayed line numbers.
235
+ * @param lineNumbers - Displayed line numbers to highlight
236
+ * @param startLine - The starting line number (defaults to 1)
237
+ */
238
+ function highlightedLinesTransformer(lineNumbers, startLine = 1) {
239
+ const highlightedLines = toLineNumberSet(lineNumbers);
240
+ return {
241
+ name: "react-shiki:highlight-lines",
242
+ code(node) {
243
+ this.addClassToHast(node, "rs-has-highlighted-lines");
244
+ },
245
+ line(node, line) {
246
+ const displayedLine = startLine + line - 1;
247
+ if (highlightedLines.has(displayedLine)) this.addClassToHast(node, "rs-highlighted-line");
248
+ return node;
249
+ }
250
+ };
251
+ }
182
252
  //#endregion
183
253
  //#region src/lib/options.ts
184
254
  const buildThemeOptions = (resolvedTheme, defaultColor, cssVariablePrefix) => {
@@ -190,9 +260,10 @@ const buildThemeOptions = (resolvedTheme, defaultColor, cssVariablePrefix) => {
190
260
  return { theme: resolvedTheme.theme };
191
261
  };
192
262
  const buildShikiOptions = (languageId, resolvedTheme, options) => {
193
- const { delay, customLanguages, preloadLanguages, outputFormat, highlighter, langAlias, engine, defaultColor, cssVariablePrefix, showLineNumbers, startingLineNumber, transformers: userTransformers, ...shikiPassthrough } = options;
263
+ const { delay, customLanguages, preloadLanguages, outputFormat, highlighter, langAlias, engine, defaultColor, cssVariablePrefix, showLineNumbers, startingLineNumber, highlightLineNumbers, transformers: userTransformers, ...shikiPassthrough } = options;
194
264
  const transformers = [...userTransformers || []];
195
265
  if (showLineNumbers) transformers.push(lineNumbersTransformer(startingLineNumber));
266
+ if (highlightLineNumbers?.length) transformers.push(highlightedLinesTransformer(highlightLineNumbers, startingLineNumber));
196
267
  return {
197
268
  lang: languageId,
198
269
  ...buildThemeOptions(resolvedTheme, defaultColor, cssVariablePrefix),
@@ -204,8 +275,9 @@ const buildShikiOptions = (languageId, resolvedTheme, options) => {
204
275
  //#region src/lib/hook.ts
205
276
  async function highlight(code, resolved, opts, factory) {
206
277
  const { languageId, langsToLoad, themesToLoad, shikiOptions } = resolved;
278
+ const format = opts.outputFormat ?? "react";
207
279
  const highlighter = opts.highlighter ?? await (async () => {
208
- const hl = await factory(langsToLoad, themesToLoad, opts.engine);
280
+ const hl = await factory(langsToLoad, themesToLoad, resolveEngine(opts.engine));
209
281
  await hl.loadLanguage(...getEmbeddedLanguages(code, languageId, hl));
210
282
  return hl;
211
283
  })();
@@ -214,14 +286,20 @@ async function highlight(code, resolved, opts, factory) {
214
286
  ...shikiOptions,
215
287
  lang: language
216
288
  };
217
- return opts.outputFormat === "html" ? highlighter.codeToHtml(code, options) : toJsxRuntime(highlighter.codeToHast(code, options), {
218
- jsx,
219
- jsxs,
220
- Fragment
221
- });
289
+ const outputs = {
290
+ react: () => toJsxRuntime(highlighter.codeToHast(code, options), {
291
+ jsx,
292
+ jsxs,
293
+ Fragment
294
+ }),
295
+ html: () => highlighter.codeToHtml(code, options),
296
+ tokens: () => highlighter.codeToTokens(code, options)
297
+ };
298
+ return (outputs[format] ?? outputs.react)();
222
299
  }
223
300
  const useHighlight = (code, lang, themeInput, options = {}, highlighterFactory) => {
224
301
  const [highlightedCode, setHighlightedCode] = useState(null);
302
+ useFeatureStyles(Boolean(options.showLineNumbers || options.highlightLineNumbers?.length));
225
303
  const stableLang = useStableValue(lang);
226
304
  const stableTheme = useStableValue(themeInput);
227
305
  const stableOpts = useStableValue(options);
@@ -300,25 +378,32 @@ const isInlineCode = (node) => {
300
378
  };
301
379
  //#endregion
302
380
  //#region src/lib/component.tsx
381
+ let warnedTokensOutputFormat = false;
382
+ /**
383
+ * The component's props exclude `outputFormat: 'tokens'`, but plain-JS
384
+ * callers can still pass it. Tokens are not renderable React children,
385
+ * so fall back to 'react' instead of crashing the render.
386
+ */
387
+ const resolveOutputFormat = (outputFormat) => {
388
+ if (outputFormat !== "tokens") return outputFormat;
389
+ if (!warnedTokensOutputFormat) {
390
+ warnedTokensOutputFormat = true;
391
+ console.warn("[react-shiki] outputFormat 'tokens' is only supported by the useShikiHighlighter hook, falling back to 'react'");
392
+ }
393
+ return "react";
394
+ };
303
395
  /**
304
396
  * Base ShikiHighlighter component factory.
305
397
  * This creates a component that uses the provided hook implementation.
306
398
  */
307
399
  const createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
308
- return forwardRef(({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles = true, style, langStyle, className, langClassName, showLanguage = true, showLineNumbers = false, startingLineNumber = 1, children: code, as: Element = "div", customLanguages, preloadLanguages, ...shikiOptions }, ref) => {
309
- const options = {
310
- delay,
311
- transformers,
312
- customLanguages,
313
- preloadLanguages,
314
- showLineNumbers,
315
- defaultColor,
316
- cssVariablePrefix,
317
- startingLineNumber,
318
- ...shikiOptions
319
- };
400
+ return forwardRef(({ language, theme, addDefaultStyles = true, style, langStyle, className, langClassName, showLanguage = true, children: code, as: Element = "div", outputFormat, ...options }, ref) => {
401
+ useComponentStyles();
320
402
  const displayLanguageId = typeof language === "object" ? language.name || null : language?.trim() || null;
321
- const highlightedCode = useShikiHighlighterImpl(code, language, theme, options);
403
+ const highlightedCode = useShikiHighlighterImpl(code, language, theme, {
404
+ ...options,
405
+ outputFormat: resolveOutputFormat(outputFormat)
406
+ });
322
407
  return /* @__PURE__ */ jsxs(Element, {
323
408
  ref,
324
409
  "data-testid": "shiki-container",
@@ -326,7 +411,6 @@ const createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
326
411
  className: clsx("rs-root", "not-prose", addDefaultStyles && "rs-default-styles", className),
327
412
  style,
328
413
  children: [showLanguage && displayLanguageId ? /* @__PURE__ */ jsx("span", {
329
- id: "language-label",
330
414
  "data-slot": "language-label",
331
415
  className: clsx("rs-language-label", langClassName),
332
416
  style: langStyle,
@@ -341,4 +425,4 @@ const createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
341
425
  //#endregion
342
426
  export { isLoadableLanguage as a, useHighlight as i, isInlineCode as n, rehypeInlineCodeProperty as r, createShikiHighlighterComponent as t };
343
427
 
344
- //# sourceMappingURL=component-CYq7SnJ0.mjs.map
428
+ //# sourceMappingURL=component-Cf8Topxy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-Cf8Topxy.mjs","names":["componentCss","featuresCss"],"sources":["../src/lib/utils.ts","../src/styles/component.css?inline","../src/styles/features.css?inline","../src/lib/styles.ts","../src/lib/language.ts","../src/lib/theme.ts","../src/lib/engine.ts","../src/lib/transformers.ts","../src/lib/options.ts","../src/lib/hook.ts","../src/lib/plugins.ts","../src/lib/component.tsx"],"sourcesContent":["import { useRef } from 'react';\nimport { dequal } from 'dequal';\n\nimport type { TimeoutState } from './types';\n\n/**\n * Returns a referentially stable version of `value` that only updates when content changes.\n * Uses reference equality as a fast path and deep comparison to prevent unnecessary updates.\n */\nexport const useStableValue = <T>(value: T): T => {\n const ref = useRef(value);\n\n if (value !== ref.current && !dequal(value, ref.current)) {\n ref.current = value;\n }\n\n return ref.current;\n};\n\n/**\n * Optionally throttles rapid sequential highlighting operations\n *\n * @example\n * const timeoutControl = useRef<TimeoutState>({\n * nextAllowedTime: 0,\n * timeoutId: undefined\n * });\n *\n * throttleHighlighting(highlightCode, timeoutControl, 1000);\n */\nexport const throttleHighlighting = (\n performHighlight: () => Promise<void>,\n timeoutControl: React.RefObject<TimeoutState>,\n throttleMs: number\n) => {\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(\n 0,\n timeoutControl.current.nextAllowedTime - Date.now()\n );\n timeoutControl.current.timeoutId = setTimeout(() => {\n performHighlight();\n timeoutControl.current.nextAllowedTime = Date.now() + throttleMs;\n }, delay);\n};\n","/* Unlayered :where() defaults: immune to layered resets, overridden by any user rule. */\n:where(.rs-root) {\n position: relative;\n}\n\n:where(.rs-root.rs-default-styles pre) {\n overflow: auto;\n border-radius: 0.5rem;\n padding-inline: 1.5rem;\n padding-block: 1.25rem;\n}\n\n:where(.rs-root .rs-language-label) {\n position: absolute;\n z-index: 1;\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",":where(.rs-has-line-numbers) {\n counter-reset: line-number calc(var(--line-start, 1) - 1);\n}\n\n:where(.rs-has-line-numbers .rs-line-number)::before {\n counter-increment: line-number;\n content: counter(line-number);\n display: inline-flex;\n justify-content: flex-end;\n align-items: flex-start;\n box-sizing: content-box;\n min-width: var(--rs-line-numbers-width, 2ch);\n padding-left: var(--rs-line-numbers-padding-left, 0ch);\n padding-right: var(--rs-line-numbers-padding-right, 2ch);\n color: var(--rs-line-numbers-foreground, rgba(107, 114, 128, 0.5));\n font-size: var(--rs-line-numbers-font-size, inherit);\n font-weight: var(--rs-line-numbers-font-weight, inherit);\n line-height: var(--rs-line-numbers-line-height, inherit);\n font-family: var(--rs-line-numbers-font-family, inherit);\n opacity: var(--rs-line-numbers-opacity, 1);\n user-select: none;\n pointer-events: none;\n}\n\n:where(.rs-has-highlighted-lines) {\n display: block;\n width: fit-content;\n min-width: 100%;\n}\n\n:where(.rs-highlighted-line) {\n --_rs-hl-background: var(\n --rs-highlighted-line-background,\n color-mix(in srgb, currentColor 10%, transparent)\n );\n display: inline-block;\n width: 100%;\n min-height: 1lh;\n background-color: var(--_rs-hl-background);\n}\n\n/* Full bleed: the 100vw outset paints into the pre's padding, clipped by its overflow. */\n:where(.rs-default-styles .rs-highlighted-line) {\n background-color: transparent;\n border-image:\n linear-gradient(var(--_rs-hl-background) 0 0) fill 0 / 0 / 0 100vw;\n}\n\n:where(.rs-has-line-numbers .rs-highlighted-line)::before {\n color: var(\n --rs-highlighted-line-number-foreground,\n color-mix(in srgb, currentColor 65%, transparent)\n );\n}\n","import componentCss from '../styles/component.css?inline';\nimport featuresCss from '../styles/features.css?inline';\nimport * as React from 'react';\n// Side-effect import so tsdown emits dist/style.css for the ./css export;\n// stripped from the built JS, guarded by check:package\nimport '../styles/index.css';\n\nconst injected = new Set<string>();\nconst useStyleInsertionEffect =\n React.useInsertionEffect ?? React.useLayoutEffect;\n\nconst inject = (css: string, key: string) => {\n if (injected.has(key) || typeof document === 'undefined') return;\n injected.add(key);\n try {\n // Not governed by CSP style-src in current browsers, unlike the <style> fallback\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(css);\n document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];\n } catch {\n const style = document.createElement('style');\n style.dataset.reactShiki = key;\n style.textContent = css;\n document.head.append(style);\n }\n};\n\n/** Installs component styles before layout without triggering a render. */\nexport const useComponentStyles = () => {\n useStyleInsertionEffect(() => {\n inject(componentCss, 'component');\n }, []);\n};\n\n/** Installs line-number/highlight styles only when those options are used. */\nexport const useFeatureStyles = (enabled: boolean) => {\n useStyleInsertionEffect(() => {\n if (enabled) inject(featuresCss, 'features');\n }, [enabled]);\n};\n","import type { Language, PreloadLanguage } from './types';\nimport { guessEmbeddedLanguages, isSpecialLang } from 'shiki/core';\nimport type {\n DynamicImportLanguageRegistration,\n Highlighter,\n HighlighterCore,\n LanguageInput,\n LanguageRegistration,\n} from 'shiki';\n\nexport const FALLBACK_LANGUAGE = 'plaintext';\n\nconst toArray = <T>(value?: T | T[]): T[] =>\n value == null ? [] : Array.isArray(value) ? value : [value];\n\n/** Deferred `LanguageInput` forms (getter, promise, module, array) that shiki resolves itself */\ntype DynamicLanguage = Exclude<LanguageInput, LanguageRegistration>;\n\nconst isDynamicLanguage = (\n lang: PreloadLanguage\n): lang is DynamicLanguage =>\n typeof lang === 'function' ||\n lang instanceof Promise ||\n Array.isArray(lang) ||\n (typeof lang === 'object' && lang != null && 'default' in lang);\n\nconst isRegistration = (\n lang: PreloadLanguage\n): lang is LanguageRegistration =>\n typeof lang === 'object' && lang != null && !isDynamicLanguage(lang);\n\n/** Dedupe identity: strings and registrations by value, dynamic inputs by reference */\nconst languageKey = (lang: PreloadLanguage): string | object | null => {\n if (lang == null) return null;\n if (typeof lang === 'string') return `s:${lang}`;\n if (isDynamicLanguage(lang)) return lang;\n return `o:${lang.name}::${lang.scopeName}`;\n};\n\nconst dedupeLanguages = (langs: PreloadLanguage[]): PreloadLanguage[] => {\n const seen = new Set<string | object>();\n return langs.filter((lang) => {\n const key = languageKey(lang);\n if (key == null || seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n};\n\n/**\n * A registration matches a query by name, scopeName (full or its last\n * segment, e.g. \"source.tsx\" -> \"tsx\"), aliases, or fileTypes.\n */\nconst registrationMatches = (\n candidate: LanguageRegistration,\n matches: (str: string | undefined) => boolean\n): boolean =>\n matches(candidate.name) ||\n matches(candidate.scopeName) ||\n matches(candidate.scopeName?.split('.').pop()) ||\n !!candidate.aliases?.some(matches) ||\n !!candidate.fileTypes?.some(matches);\n\ntype LanguageResult = {\n languageId: string;\n langsToLoad: PreloadLanguage[];\n};\n\n/**\n * Resolves the language prop to a shiki language id plus everything the\n * factory should load (primary, preloaded, and custom grammars, deduped).\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[],\n langAliases?: Record<string, string>,\n preloadLanguages?: PreloadLanguage | PreloadLanguage[]\n): LanguageResult => {\n const customLangs = toArray(customLanguages);\n const preloadLangs = toArray(preloadLanguages);\n\n const result = (\n languageId: string,\n primary?: Language\n ): LanguageResult => ({\n languageId,\n langsToLoad: dedupeLanguages([\n primary,\n ...preloadLangs,\n ...customLangs,\n ]),\n });\n\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return result(FALLBACK_LANGUAGE);\n }\n\n if (typeof lang === 'object') {\n return result(lang.name, lang);\n }\n\n const normalized = lang.trim();\n const query = normalized.toLowerCase();\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === query;\n\n // Custom registrations (from both customLanguages and preloadLanguages)\n const customMatch = [...customLangs, ...preloadLangs]\n .filter(isRegistration)\n .find((candidate) => registrationMatches(candidate, matches));\n if (customMatch) {\n return result(customMatch.name || normalized, customMatch);\n }\n\n const alias = Object.entries(langAliases ?? {}).find(([name]) =>\n matches(name)\n )?.[1];\n if (alias) {\n return result(alias, alias);\n }\n\n // Any other string passes through to the factory\n return result(normalized, normalized);\n};\n\n/**\n * Used in factories to check if language is supported.\n * Objects are validated as grammar registrations (name + scopeName);\n * dynamic imports pass through for shiki to resolve.\n */\nexport const isLoadableLanguage = <T extends string>(\n lang: PreloadLanguage,\n bundledLanguages: Record<T, DynamicImportLanguageRegistration>\n): lang is NonNullable<PreloadLanguage> => {\n if (lang == null) return false;\n if (typeof lang === 'string') return lang in bundledLanguages;\n if (isDynamicLanguage(lang)) return true;\n return (\n typeof lang.name === 'string' && typeof lang.scopeName === 'string'\n );\n};\n\n/**\n * Used in hook to resolve loaded language for highlighting.\n * Falls back to \"plaintext\" if not supported.\n */\nexport const resolveLoadedLanguage = (\n languageId: string,\n loadedLanguages: string[]\n): string =>\n isSpecialLang(languageId) || loadedLanguages.includes(languageId)\n ? languageId\n : FALLBACK_LANGUAGE;\n\nexport const getEmbeddedLanguages = (\n code: string,\n languageId: string,\n highlighter: Highlighter | HighlighterCore\n): LanguageInput[] => {\n const bundled: Record<string, LanguageInput> =\n highlighter.getBundledLanguages();\n return guessEmbeddedLanguages(code, languageId).flatMap(\n (language) => bundled[language] ?? []\n );\n};\n","import type { Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\n\nexport const DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\nexport type ResolvedTheme =\n | { isMulti: true; themes: Themes; themesToLoad: Theme[] }\n | { isMulti: false; theme: Theme; themesToLoad: Theme[] };\n\nconst isTextmateTheme = (\n value: unknown\n): value is ThemeRegistrationAny => {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Partial<ThemeRegistrationAny> & {\n settings?: unknown;\n };\n const hasTokens =\n Array.isArray(v.tokenColors) || Array.isArray(v.settings);\n const hasIdentity =\n typeof v.name === 'string' || typeof v.type === 'string';\n return hasTokens && hasIdentity;\n};\n\nexport function resolveTheme(themeInput: Theme | Themes): ResolvedTheme {\n if (\n typeof themeInput !== 'object' ||\n themeInput === null ||\n isTextmateTheme(themeInput)\n ) {\n return {\n isMulti: false,\n theme: themeInput,\n themesToLoad: [themeInput],\n };\n }\n\n const validEntries = Object.entries(themeInput).filter(\n ([key, value]) =>\n key.trim() !== '' &&\n ((typeof value === 'string' && value.trim() !== '') ||\n isTextmateTheme(value))\n );\n\n if (validEntries.length === 0) {\n console.warn(\n '[react-shiki] invalid multi-theme config, falling back to defaults'\n );\n return {\n isMulti: true,\n themes: DEFAULT_THEMES,\n themesToLoad: Object.values(DEFAULT_THEMES),\n };\n }\n\n if (validEntries.length !== Object.keys(themeInput).length) {\n console.warn(\n '[react-shiki] multi-theme config contained invalid entries; they were dropped'\n );\n }\n\n const themes = Object.fromEntries(validEntries) as Themes;\n return {\n isMulti: true,\n themes,\n themesToLoad: validEntries.map(([, value]) => value as Theme),\n };\n}\n","import type { Awaitable, RegexEngine } from 'shiki/core';\nimport type { EngineName } from './types';\n\nconst engines = new Map<EngineName, Promise<RegexEngine>>();\n\n// Dynamic imports keep unused engines out of the loaded graph: naming an\n// engine loads only that engine, and 'javascript' never fetches the wasm.\nconst loaders: Record<EngineName, () => Promise<RegexEngine>> = {\n javascript: () =>\n import('shiki/engine/javascript').then((m) =>\n m.createJavaScriptRegexEngine()\n ),\n oniguruma: () =>\n import('shiki/engine/oniguruma').then((m) =>\n m.createOnigurumaEngine(import('shiki/wasm'))\n ),\n};\n\n/**\n * Resolves a named engine to a lazily created, cached instance.\n * Engine instances and undefined pass through untouched.\n *\n * The cache makes named engines referentially stable across renders and\n * call sites, unlike inline `createJavaScriptRegexEngine()` calls.\n */\nexport const resolveEngine = (\n engine: EngineName | Awaitable<RegexEngine> | undefined\n): Awaitable<RegexEngine> | undefined => {\n if (typeof engine !== 'string') return engine;\n\n const load = loaders[engine];\n if (!load) {\n throw new Error(\n `[react-shiki] unknown engine '${engine}', expected 'javascript' or 'oniguruma'`\n );\n }\n\n const cached = engines.get(engine);\n if (cached) return cached;\n\n const instance = load();\n // Evict rejected loads (e.g. transient wasm fetch failure) so the\n // next highlight retries instead of reusing the cached rejection.\n instance.catch(() => engines.delete(engine));\n engines.set(engine, instance);\n return instance;\n};\n","import type { ShikiTransformer } from 'shiki/core';\n\nconst toLineNumberSet = (lineNumbers: readonly number[]) =>\n new Set(lineNumbers.filter(Number.isInteger));\n\n/**\n * Creates a transformer that enables line numbers display\n * @param startLine - The starting line number (defaults to 1)\n */\nexport function lineNumbersTransformer(startLine = 1): ShikiTransformer {\n return {\n name: 'react-shiki:line-numbers',\n code(node) {\n this.addClassToHast(node, 'rs-has-line-numbers');\n if (startLine !== 1) {\n const existing = node.properties?.style as string | undefined;\n node.properties = {\n ...node.properties,\n style: [existing, `--line-start: ${startLine}`]\n .filter(Boolean)\n .join('; '),\n };\n }\n },\n line(node) {\n this.addClassToHast(node, 'rs-line-number');\n return node;\n },\n };\n}\n\n/**\n * Creates a transformer that marks selected displayed line numbers.\n * @param lineNumbers - Displayed line numbers to highlight\n * @param startLine - The starting line number (defaults to 1)\n */\nexport function highlightedLinesTransformer(\n lineNumbers: readonly number[],\n startLine = 1\n): ShikiTransformer {\n const highlightedLines = toLineNumberSet(lineNumbers);\n\n return {\n name: 'react-shiki:highlight-lines',\n code(node) {\n this.addClassToHast(node, 'rs-has-highlighted-lines');\n },\n line(node, line) {\n const displayedLine = startLine + line - 1;\n\n if (highlightedLines.has(displayedLine)) {\n this.addClassToHast(node, 'rs-highlighted-line');\n }\n\n return node;\n },\n };\n}\n","import type {\n BundledTheme,\n CodeOptionsMultipleThemes,\n CodeOptionsSingleTheme,\n CodeToHastOptions,\n} from 'shiki';\n\nimport type {\n HighlighterOptions,\n HighlighterOptionsFor,\n OutputFormat,\n} from './types';\nimport type { ResolvedTheme } from './theme';\nimport {\n highlightedLinesTransformer,\n lineNumbersTransformer,\n} from './transformers';\n\nconst buildThemeOptions = (\n resolvedTheme: ResolvedTheme,\n defaultColor: HighlighterOptions['defaultColor'],\n cssVariablePrefix: HighlighterOptions['cssVariablePrefix']\n):\n | CodeOptionsMultipleThemes<BundledTheme>\n | CodeOptionsSingleTheme<BundledTheme> => {\n if (resolvedTheme.isMulti) {\n return {\n themes: resolvedTheme.themes,\n defaultColor,\n cssVariablePrefix,\n };\n }\n\n return {\n theme: resolvedTheme.theme,\n };\n};\n\nexport const buildShikiOptions = (\n languageId: string,\n resolvedTheme: ResolvedTheme,\n options: HighlighterOptionsFor<OutputFormat>\n): CodeToHastOptions => {\n const {\n delay,\n customLanguages,\n preloadLanguages,\n outputFormat,\n highlighter,\n langAlias,\n engine,\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n highlightLineNumbers,\n transformers: userTransformers,\n ...shikiPassthrough\n } = options;\n\n const transformers = [...(userTransformers || [])];\n if (showLineNumbers) {\n transformers.push(lineNumbersTransformer(startingLineNumber));\n }\n if (highlightLineNumbers?.length) {\n transformers.push(\n highlightedLinesTransformer(\n highlightLineNumbers,\n startingLineNumber\n )\n );\n }\n\n return {\n lang: languageId,\n ...buildThemeOptions(resolvedTheme, defaultColor, cssVariablePrefix),\n ...shikiPassthrough,\n transformers,\n };\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\nimport type { CodeToHastOptions } from 'shiki';\n\nimport type {\n HighlighterFactory,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightResult,\n HighlightResultMap,\n Language,\n OutputFormat,\n PreloadLanguage,\n Theme,\n Themes,\n TimeoutState,\n} from './types';\n\nimport { throttleHighlighting, useStableValue } from './utils';\nimport { useFeatureStyles } from './styles';\nimport {\n getEmbeddedLanguages,\n resolveLanguage,\n resolveLoadedLanguage,\n} from './language';\nimport { resolveTheme } from './theme';\nimport { resolveEngine } from './engine';\nimport { buildShikiOptions } from './options';\n\nexport async function highlight<F extends OutputFormat = 'react'>(\n code: string,\n resolved: {\n languageId: string;\n langsToLoad: PreloadLanguage[];\n themesToLoad: Theme[];\n shikiOptions: CodeToHastOptions;\n },\n opts: Pick<HighlighterOptions, 'highlighter' | 'engine'> & {\n outputFormat?: F;\n },\n factory: HighlighterFactory\n): Promise<HighlightResultMap[F]> {\n const { languageId, langsToLoad, themesToLoad, shikiOptions } =\n resolved;\n const format: OutputFormat = opts.outputFormat ?? 'react';\n\n const highlighter =\n opts.highlighter ??\n (await (async () => {\n const hl = await factory(\n langsToLoad,\n themesToLoad,\n resolveEngine(opts.engine)\n );\n await hl.loadLanguage(\n ...getEmbeddedLanguages(code, languageId, hl)\n );\n return hl;\n })());\n\n const language = resolveLoadedLanguage(\n languageId,\n highlighter.getLoadedLanguages()\n );\n const options = { ...shikiOptions, lang: language };\n\n const outputs: { [K in OutputFormat]: () => HighlightResultMap[K] } = {\n react: () =>\n toJsxRuntime(highlighter.codeToHast(code, options), {\n jsx,\n jsxs,\n Fragment,\n }),\n html: () => highlighter.codeToHtml(code, options),\n tokens: () => highlighter.codeToTokens(code, options),\n };\n\n // Untyped call sites can pass formats outside OutputFormat; degrade to\n // react rendering like the pre-tokens ternary did instead of throwing.\n return (outputs[format] ?? outputs.react)() as HighlightResultMap[F];\n}\n\nexport const useHighlight = <F extends OutputFormat = 'react'>(\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n options: HighlighterOptionsFor<F> = {},\n highlighterFactory: HighlighterFactory\n): HighlightResult<F> => {\n const [highlightedCode, setHighlightedCode] =\n useState<HighlightResult<F>>(null);\n\n // This hook owns the line-number/highlight output, so it also owns the\n // corresponding styles. The primitive boolean limits effect reruns.\n useFeatureStyles(\n Boolean(\n options.showLineNumbers || options.highlightLineNumbers?.length\n )\n );\n\n const stableLang = useStableValue(lang);\n const stableTheme = useStableValue(themeInput);\n const stableOpts = useStableValue(options);\n\n const resolved = useMemo(() => {\n const { languageId, langsToLoad } = resolveLanguage(\n stableLang,\n stableOpts.customLanguages,\n stableOpts.langAlias,\n stableOpts.preloadLanguages\n );\n\n const theme = resolveTheme(stableTheme);\n\n const shikiOptions = buildShikiOptions(languageId, theme, stableOpts);\n return {\n languageId,\n langsToLoad,\n themesToLoad: theme.themesToLoad,\n shikiOptions,\n };\n }, [stableLang, stableTheme, stableOpts]);\n\n const requestIdRef = useRef(0);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n // Factory is invoked imperatively; its identity is not semantic, so it must\n // not invalidate the effect on every render. See issue #161.\n const highlighterFactoryRef = useRef(highlighterFactory);\n highlighterFactoryRef.current = highlighterFactory;\n\n useEffect(() => {\n const requestId = ++requestIdRef.current;\n\n if (!resolved.languageId) return;\n\n const run = async () => {\n try {\n const result = await highlight<F>(\n code,\n resolved,\n stableOpts,\n highlighterFactoryRef.current\n );\n if (requestId === requestIdRef.current) {\n setHighlightedCode(result);\n }\n } catch (error) {\n console.error('[react-shiki] highlight failed', error);\n }\n };\n\n if (stableOpts.delay) {\n throttleHighlighting(run, timeoutControl, stableOpts.delay);\n } else {\n run();\n }\n\n return () => {\n clearTimeout(timeoutControl.current.timeoutId);\n };\n }, [code, resolved, stableOpts]);\n\n return highlightedCode;\n};\n","import { visit } from 'unist-util-visit';\nimport type { Root } from 'hast';\nimport type { Element } from './types';\n\n/**\n * Rehype plugin to add an 'inline' property to <code> elements\n * Sets 'inline' property to true if the <code> is not within a <pre> tag\n *\n * Pass this plugin to the `rehypePlugins` prop of react-markdown\n * You can then access `inline` as a prop in components passed to react-markdown\n *\n * @example\n * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />\n */\nexport function rehypeInlineCodeProperty() {\n return (tree: Root): undefined => {\n visit(tree, 'element', (node: Element, _index, parent) => {\n if (\n node.tagName === 'code' &&\n parent?.type === 'element' &&\n parent.tagName !== 'pre'\n ) {\n node.properties.inline = true;\n }\n });\n };\n}\n\n/**\n * Function to determine if code is inline based on the presence of line breaks\n *\n * @example\n * const isInline = node && isInlineCode(node: Element)\n */\nexport const isInlineCode = (node: Element): boolean => {\n const textContent = (node.children || [])\n .filter((child) => child.type === 'text')\n .map((child) => child.value)\n .join('');\n\n return !textContent.includes('\\n');\n};\n","import { clsx } from 'clsx';\n\nimport type {\n HighlighterOptions,\n Language,\n OutputFormat,\n Theme,\n Themes,\n UseShikiHighlighter,\n} from './types';\nimport { forwardRef } from 'react';\nimport { useComponentStyles } from './styles';\n\nlet warnedTokensOutputFormat = false;\n\n/**\n * The component's props exclude `outputFormat: 'tokens'`, but plain-JS\n * callers can still pass it. Tokens are not renderable React children,\n * so fall back to 'react' instead of crashing the render.\n */\nconst resolveOutputFormat = (\n outputFormat: HighlighterOptions['outputFormat']\n): HighlighterOptions['outputFormat'] => {\n if ((outputFormat as OutputFormat) !== 'tokens') return outputFormat;\n\n if (!warnedTokensOutputFormat) {\n warnedTokensOutputFormat = true;\n console.warn(\n \"[react-shiki] outputFormat 'tokens' is only supported by the useShikiHighlighter hook, falling back to 'react'\"\n );\n }\n return 'react';\n};\n\n/**\n * Props for the ShikiHighlighter component\n */\nexport interface ShikiHighlighterProps extends HighlighterOptions {\n /**\n * The programming language for syntax highlighting\n * Supports custom textmate grammar objects in addition to Shiki's bundled languages\n * @see https://shiki.style/languages\n */\n language: Language;\n\n /**\n * The code to be highlighted\n */\n children: string;\n\n /**\n * The color theme or themes for syntax highlighting\n * Supports single, dual, or multiple themes\n * Supports custom textmate theme objects in addition to Shiki's bundled themes\n *\n * @example\n * theme='github-dark' // single theme\n * theme={{ light: 'github-light', dark: 'github-dark' }} // multi-theme\n *\n * @see https://shiki.style/themes\n */\n theme: Theme | Themes;\n\n /**\n * Controls the application of default styles to the generated code blocks\n *\n * Default styles include padding, overflow handling, border radius, language label styling, and font settings\n * @default true\n */\n addDefaultStyles?: boolean;\n\n /**\n * Add custom inline styles to the generated code block\n */\n style?: React.CSSProperties;\n\n /**\n * Add custom inline styles to the language label\n */\n langStyle?: React.CSSProperties;\n\n /**\n * Add custom CSS class names to the generated code block\n */\n className?: string;\n\n /**\n * Add custom CSS class names to the language label\n */\n langClassName?: string;\n\n /**\n * Whether to show the language label\n * @default true\n */\n showLanguage?: boolean;\n\n /**\n * The HTML element that wraps the generated code block.\n * @default 'div'\n */\n as?: React.ElementType;\n}\n\n/**\n * Base ShikiHighlighter component factory.\n * This creates a component that uses the provided hook implementation.\n */\nexport const createShikiHighlighterComponent = (\n useShikiHighlighterImpl: UseShikiHighlighter\n) => {\n return forwardRef<HTMLElement, ShikiHighlighterProps>(\n (\n {\n language,\n theme,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n children: code,\n as: Element = 'div',\n outputFormat,\n ...options\n },\n ref\n ) => {\n // Kept inside the component so hook-only bundles can tree-shake it\n // together with the component CSS.\n useComponentStyles();\n\n const displayLanguageId =\n typeof language === 'object'\n ? language.name || null\n : language?.trim() || null;\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n {\n ...options,\n outputFormat: resolveOutputFormat(outputFormat),\n }\n );\n\n return (\n <Element\n ref={ref}\n data-testid=\"shiki-container\"\n data-slot=\"container\"\n className={clsx(\n 'rs-root',\n 'not-prose',\n addDefaultStyles && 'rs-default-styles',\n className\n )}\n style={style}\n >\n {showLanguage && displayLanguageId ? (\n <span\n data-slot=\"language-label\"\n className={clsx('rs-language-label', langClassName)}\n style={langStyle}\n >\n {displayLanguageId}\n </span>\n ) : null}\n {typeof highlightedCode === 'string' ? (\n <div\n data-slot=\"content\"\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n />\n ) : (\n highlightedCode\n )}\n </Element>\n );\n }\n );\n};\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { ReactElement } from "react";
2
- import { BundledHighlighterOptions, BundledLanguage, BundledTheme, CodeOptionsMultipleThemes, CodeToHastOptions, Highlighter, HighlighterCore, LanguageRegistration, SpecialLanguage, StringLiteralUnion, ThemeRegistrationAny } from "shiki";
3
-
2
+ import { Awaitable, BundledHighlighterOptions, BundledLanguage, BundledTheme, CodeOptionsMultipleThemes, CodeToHastOptions, Highlighter, HighlighterCore, LanguageInput, LanguageRegistration, RegexEngine, SpecialLanguage, StringLiteralUnion, ThemeRegistrationAny, TokensResult } from "shiki";
4
3
  //#region ../node_modules/.pnpm/@types+unist@3.0.3/node_modules/@types/unist/index.d.ts
5
4
  // ## Interfaces
6
5
  /**
@@ -317,6 +316,12 @@ type Element = Element$1;
317
316
  * @see https://shiki.style/languages
318
317
  */
319
318
  type Language = LanguageRegistration | StringLiteralUnion<BundledLanguage> | SpecialLanguage | undefined;
319
+ /**
320
+ * A `preloadLanguages` entry: a bundled language id, a custom grammar
321
+ * object, or a dynamic grammar import (shiki's `LanguageInput`), e.g.
322
+ * `() => import('../langs/mcfunction.json')`
323
+ */
324
+ type PreloadLanguage = Language | LanguageInput;
320
325
  type MultiThemeKey = 'dark' | 'light' | (string & {});
321
326
  /**
322
327
  * A Shiki BundledTheme or a custom textmate theme object
@@ -339,7 +344,11 @@ type Theme = ThemeRegistrationAny | StringLiteralUnion<BundledTheme>;
339
344
  *
340
345
  * @see https://shiki.style/guide/dual-themes
341
346
  */
342
- type Themes = { [Key in MultiThemeKey]: Theme };
347
+ type Themes = { [Key in MultiThemeKey]: Theme; };
348
+ /**
349
+ * Named RegExp engines that react-shiki creates and caches internally.
350
+ */
351
+ type EngineName = 'javascript' | 'oniguruma';
343
352
  /**
344
353
  * Configuration options specific to react-shiki
345
354
  */
@@ -349,6 +358,18 @@ interface ReactShikiOptions {
349
358
  * @default undefined (no throttling)
350
359
  */
351
360
  delay?: number;
361
+ /**
362
+ * RegExp engine for syntax highlighting.
363
+ * - 'oniguruma': compiled WebAssembly engine, maximum grammar support
364
+ * - 'javascript': pure-JS engine, no WASM fetch, faster startup
365
+ *
366
+ * Named engines are created lazily and cached by react-shiki, so they
367
+ * are safe to pass inline. A custom engine instance is also accepted;
368
+ * create it once at module scope, as a new instance on every render
369
+ * re-triggers highlighting.
370
+ * @default 'oniguruma'
371
+ */
372
+ engine?: EngineName | Awaitable<RegexEngine>;
352
373
  /**
353
374
  * Custom textmate grammars to be preloaded for highlighting.
354
375
  * @deprecated Use preloadLanguages instead.
@@ -357,12 +378,25 @@ interface ReactShikiOptions {
357
378
  /**
358
379
  * Preload custom grammars or bundled languages.
359
380
  * Supports custom textmate grammars, replaces deprecated `customLanguages`
381
+ *
382
+ * Full/web bundles also accept dynamic grammar imports, keeping custom
383
+ * grammars out of the main bundle. Define them at module scope, preferring
384
+ * getters: a fresh getter each render re-triggers highlighting, while
385
+ * swapping a bare promise at runtime is not detected.
386
+ *
387
+ * @example
388
+ * const preload = [() => import('../langs/mcfunction.json')];
360
389
  */
361
- preloadLanguages?: Language | Language[];
390
+ preloadLanguages?: PreloadLanguage | PreloadLanguage[];
362
391
  /**
363
392
  * Output format for the highlighted code.
364
393
  * - 'react': Returns React nodes (default, safer)
365
- * - 'html': Returns HTML string (~15-45% faster, requires dangerouslySetInnerHTML)
394
+ * - 'html': Returns HTML string (rendered via dangerouslySetInnerHTML)
395
+ *
396
+ * The hook additionally accepts 'tokens' (experimental) to return raw
397
+ * Shiki tokens for custom rendering. It is deliberately excluded here
398
+ * and only enters through the hook's generic signature, so option
399
+ * objects typed with this interface keep the classic return type.
366
400
  * @default 'react'
367
401
  */
368
402
  outputFormat?: 'react' | 'html';
@@ -404,20 +438,39 @@ interface ReactShikiOptions {
404
438
  * @default 1
405
439
  */
406
440
  startingLineNumber?: number;
441
+ /**
442
+ * Displayed line numbers to highlight
443
+ */
444
+ highlightLineNumbers?: number[];
407
445
  }
408
446
  /**
409
447
  * Configuration options for the syntax highlighter
410
448
  * Extends CodeToHastOptions to allow passing any Shiki options directly
411
449
  */
412
- interface HighlighterOptions extends ReactShikiOptions, Pick<CodeOptionsMultipleThemes<BundledTheme>, 'defaultColor' | 'cssVariablePrefix'>, Omit<CodeToHastOptions, 'lang' | 'theme' | 'themes'>, Pick<BundledHighlighterOptions<string, string>, 'langAlias' | 'engine'> {}
450
+ interface HighlighterOptions extends ReactShikiOptions, Pick<CodeOptionsMultipleThemes<BundledTheme>, 'defaultColor' | 'cssVariablePrefix'>, Omit<CodeToHastOptions, 'lang' | 'theme' | 'themes'>, Pick<BundledHighlighterOptions<string, string>, 'langAlias'> {}
413
451
  /**
414
- * State for the throttling logic
452
+ * Supported output formats and their corresponding result shapes.
415
453
  */
454
+ type OutputFormat = 'react' | 'html' | 'tokens';
455
+ interface HighlightResultMap {
456
+ react: ReactElement;
457
+ html: string;
458
+ tokens: TokensResult;
459
+ }
460
+ type HighlightResult<F extends OutputFormat = 'react'> = HighlightResultMap[F] | null;
416
461
  /**
417
- * Public API signature for the useShikiHighlighter hook.
418
- */
419
- type HighlightedCode = ReactElement | string | null;
420
- type UseShikiHighlighter = (code: string, lang: Language, themeInput: Theme | Themes, options?: HighlighterOptions) => HighlightedCode;
462
+ * Per-call options shape: every highlighter option except `outputFormat`,
463
+ * plus a narrowed `outputFormat?: F` so the return type can be inferred
464
+ * from the format the caller actually passes.
465
+ *
466
+ * This is the only place 'tokens' is accepted. Keeping it out of
467
+ * `HighlighterOptions` means values typed with that interface infer
468
+ * `F = 'react' | 'html'` and keep their pre-tokens return type.
469
+ */
470
+ type HighlighterOptionsFor<F extends OutputFormat> = Omit<HighlighterOptions, 'outputFormat'> & {
471
+ outputFormat?: F;
472
+ };
473
+ type UseShikiHighlighter = <F extends OutputFormat = 'react'>(code: string, lang: Language, themeInput: Theme | Themes, options?: HighlighterOptionsFor<F>) => HighlightResult<F>;
421
474
  //#endregion
422
475
  //#region src/lib/plugins.d.ts
423
476
  /**
@@ -494,16 +547,6 @@ interface ShikiHighlighterProps extends HighlighterOptions {
494
547
  * @default true
495
548
  */
496
549
  showLanguage?: boolean;
497
- /**
498
- * Whether to show line numbers
499
- * @default false
500
- */
501
- showLineNumbers?: boolean;
502
- /**
503
- * Starting line number (when showLineNumbers is true)
504
- * @default 1
505
- */
506
- startingLineNumber?: number;
507
550
  /**
508
551
  * The HTML element that wraps the generated code block.
509
552
  * @default 'div'
@@ -511,5 +554,5 @@ interface ShikiHighlighterProps extends HighlighterOptions {
511
554
  as?: React.ElementType;
512
555
  }
513
556
  //#endregion
514
- export { HighlighterOptions as a, Themes as c, Element as i, UseShikiHighlighter as l, isInlineCode as n, Language as o, rehypeInlineCodeProperty as r, Theme as s, ShikiHighlighterProps as t };
515
- //# sourceMappingURL=component-CjpuWrgO.d.mts.map
557
+ export { EngineName as a, HighlighterOptionsFor as c, PreloadLanguage as d, Theme as f, Element as i, Language as l, UseShikiHighlighter as m, isInlineCode as n, HighlightResult as o, Themes as p, rehypeInlineCodeProperty as r, HighlighterOptions as s, ShikiHighlighterProps as t, OutputFormat as u };
558
+ //# sourceMappingURL=component-DlK6KonE.d.mts.map
package/dist/core.d.mts CHANGED
@@ -1,9 +1,7 @@
1
- import { a as HighlighterOptions, c as Themes, i as Element, l as UseShikiHighlighter, n as isInlineCode, o as Language, r as rehypeInlineCodeProperty, s as Theme, t as ShikiHighlighterProps } from "./component-CjpuWrgO.mjs";
2
- import * as _$react from "react";
3
- import { LanguageRegistration, createHighlighterCore } from "shiki/core";
1
+ import { a as EngineName, c as HighlighterOptionsFor, d as PreloadLanguage, f as Theme, i as Element, l as Language, m as UseShikiHighlighter, n as isInlineCode, o as HighlightResult, p as Themes, r as rehypeInlineCodeProperty, s as HighlighterOptions, t as ShikiHighlighterProps, u as OutputFormat } from "./component-DlK6KonE.mjs";
2
+ import { LanguageRegistration, TokensResult, createHighlighterCore } from "shiki/core";
4
3
  import { createOnigurumaEngine } from "shiki/engine/oniguruma";
5
4
  import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/engine/javascript";
6
-
7
5
  //#region src/core.d.ts
8
6
  /**
9
7
  * Highlight code with shiki (core bundle)
@@ -12,7 +10,7 @@ import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/en
12
10
  * @param lang - Language (bundled or custom)
13
11
  * @param theme - Theme (bundled, multi-theme, or custom)
14
12
  * @param options - react-shiki options + shiki options
15
- * @returns Highlighted code as React elements or HTML string
13
+ * @returns Highlighted code as React elements (default), HTML string, or Shiki tokens, based on `outputFormat`
16
14
  *
17
15
  * @example
18
16
  * ```tsx
@@ -43,7 +41,7 @@ declare const useShikiHighlighter: UseShikiHighlighter;
43
41
  * ShikiHighlighter component using a custom highlighter.
44
42
  * Requires a highlighter to be provided.
45
43
  */
46
- declare const ShikiHighlighter: _$react.ForwardRefExoticComponent<ShikiHighlighterProps & _$react.RefAttributes<HTMLElement>>;
44
+ declare const ShikiHighlighter: import("react").ForwardRefExoticComponent<ShikiHighlighterProps & import("react").RefAttributes<HTMLElement>>;
47
45
  //#endregion
48
- export { type Element, type HighlighterOptions, type Language, type LanguageRegistration, ShikiHighlighter, ShikiHighlighter as default, type ShikiHighlighterProps, type Theme, type Themes, type UseShikiHighlighter, createHighlighterCore, createJavaScriptRawEngine, createJavaScriptRegexEngine, createOnigurumaEngine, isInlineCode, rehypeInlineCodeProperty, useShikiHighlighter };
46
+ export { type Element, type EngineName, type HighlightResult, type HighlighterOptions, type HighlighterOptionsFor, type Language, type LanguageRegistration, type OutputFormat, type PreloadLanguage, ShikiHighlighter, ShikiHighlighter as default, type ShikiHighlighterProps, type Theme, type Themes, type TokensResult, type UseShikiHighlighter, createHighlighterCore, createJavaScriptRawEngine, createJavaScriptRegexEngine, createOnigurumaEngine, isInlineCode, rehypeInlineCodeProperty, useShikiHighlighter };
49
47
  //# sourceMappingURL=core.d.mts.map
package/dist/core.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as useHighlight, n as isInlineCode, r as rehypeInlineCodeProperty, t as createShikiHighlighterComponent } from "./component-CYq7SnJ0.mjs";
1
+ import { i as useHighlight, n as isInlineCode, r as rehypeInlineCodeProperty, t as createShikiHighlighterComponent } from "./component-Cf8Topxy.mjs";
2
2
  import { createHighlighterCore } from "shiki/core";
3
3
  import { createOnigurumaEngine } from "shiki/engine/oniguruma";
4
4
  import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/engine/javascript";
@@ -20,7 +20,7 @@ function validateCoreHighlighter(highlighter) {
20
20
  * @param lang - Language (bundled or custom)
21
21
  * @param theme - Theme (bundled, multi-theme, or custom)
22
22
  * @param options - react-shiki options + shiki options
23
- * @returns Highlighted code as React elements or HTML string
23
+ * @returns Highlighted code as React elements (default), HTML string, or Shiki tokens, based on `outputFormat`
24
24
  *
25
25
  * @example
26
26
  * ```tsx
@@ -57,7 +57,7 @@ const useShikiHighlighter = (code, lang, themeInput, options = {}) => {
57
57
  * ShikiHighlighter component using a custom highlighter.
58
58
  * Requires a highlighter to be provided.
59
59
  */
60
- const ShikiHighlighter = createShikiHighlighterComponent(useShikiHighlighter);
60
+ const ShikiHighlighter = /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);
61
61
  //#endregion
62
62
  export { ShikiHighlighter, ShikiHighlighter as default, createHighlighterCore, createJavaScriptRawEngine, createJavaScriptRegexEngine, createOnigurumaEngine, isInlineCode, rehypeInlineCodeProperty, useShikiHighlighter };
63
63