react-shiki 0.7.0 → 0.7.2

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
@@ -23,6 +23,7 @@ A performant client-side syntax highlighting component and hook for React, built
23
23
  - [Custom Languages](#custom-languages)
24
24
  - [Preloading Custom Languages](#preloading-custom-languages)
25
25
  - [Custom Transformers](#custom-transformers)
26
+ - [Line Numbers](#line-numbers)
26
27
  - [Integration](#integration)
27
28
  - [Integration with react-markdown](#integration-with-react-markdown)
28
29
  - [Handling Inline Code](#handling-inline-code)
@@ -44,6 +45,7 @@ A performant client-side syntax highlighting component and hook for React, built
44
45
  - 🖥️ `ShikiHighlighter` component displays a language label for each code block
45
46
  when `showLanguage` is set to `true` (default)
46
47
  - 🎨 Customizable styling of generated code blocks and language labels
48
+ - 📏 Optional line numbers with customizable starting number and styling
47
49
 
48
50
  ## Installation
49
51
 
@@ -155,6 +157,9 @@ See [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for more i
155
157
  | `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom textmate theme object |
156
158
  | `delay` | `number` | `0` | Delay between highlights (in milliseconds) |
157
159
  | `customLanguages` | `array` | `[]` | Array of custom languages to preload |
160
+ | `langAlias` | `object` | `{}` | Map of language aliases |
161
+ | `showLineNumbers` | `boolean` | `false` | Display line numbers alongside code |
162
+ | `startingLineNumber` | `number` | `1` | Starting line number when line numbers are enabled |
158
163
  | `transformers` | `array` | `[]` | Custom Shiki transformers for modifying the highlighting output |
159
164
  | `cssVariablePrefix` | `string` | `'--shiki'` | Prefix for CSS variables storing theme colors |
160
165
  | `defaultColor` | `string \| false` | `'light'` | Default theme mode when using multiple themes, can also disable default theme |
@@ -221,12 +226,12 @@ Custom themes can be passed as a TextMate theme in JavaScript object. For exampl
221
226
  ```tsx
222
227
  import tokyoNight from "../styles/tokyo-night.json";
223
228
 
224
- // Using the component
229
+ // Component
225
230
  <ShikiHighlighter language="tsx" theme={tokyoNight}>
226
231
  {code.trim()}
227
232
  </ShikiHighlighter>
228
233
 
229
- // Using the hook
234
+ // Hook
230
235
  const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
231
236
  ```
232
237
 
@@ -237,12 +242,12 @@ Custom languages should be passed as a TextMate grammar in JavaScript object. Fo
237
242
  ```tsx
238
243
  import mcfunction from "../langs/mcfunction.tmLanguage.json";
239
244
 
240
- // Using the component
245
+ // Component
241
246
  <ShikiHighlighter language={mcfunction} theme="github-dark">
242
247
  {code.trim()}
243
248
  </ShikiHighlighter>
244
249
 
245
- // Using the hook
250
+ // Hook
246
251
  const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
247
252
  ```
248
253
 
@@ -254,7 +259,7 @@ For dynamic highlighting scenarios where language selection happens at runtime:
254
259
  import mcfunction from "../langs/mcfunction.tmLanguage.json";
255
260
  import bosque from "../langs/bosque.tmLanguage.json";
256
261
 
257
- // With the component
262
+ // Component
258
263
  <ShikiHighlighter
259
264
  language="typescript"
260
265
  theme="github-dark"
@@ -263,28 +268,113 @@ import bosque from "../langs/bosque.tmLanguage.json";
263
268
  {code.trim()}
264
269
  </ShikiHighlighter>
265
270
 
266
- // With the hook
271
+ // Hook
267
272
  const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
268
273
  customLanguages: [mcfunction, bosque],
269
274
  });
270
275
  ```
271
276
 
277
+ ### Language Aliases
278
+
279
+ You can define custom aliases for languages using the `langAlias` option. This is useful when you want to use alternative names for languages:
280
+
281
+ ```tsx
282
+ // Component
283
+ <ShikiHighlighter
284
+ language="indents"
285
+ theme="github-dark"
286
+ langAlias={{ indents: "python" }}
287
+ >
288
+ {code.trim()}
289
+ </ShikiHighlighter>
290
+
291
+ // Hook
292
+ const highlightedCode = useShikiHighlighter(code, "indents", "github-dark", {
293
+ langAlias: { indents: "python" },
294
+ });
295
+ ```
296
+
272
297
  ### Custom Transformers
273
298
 
274
299
  ```tsx
275
300
  import { customTransformer } from "../utils/shikiTransformers";
276
301
 
277
- // Using the component
302
+ // Component
278
303
  <ShikiHighlighter language="tsx" transformers={[customTransformer]}>
279
304
  {code.trim()}
280
305
  </ShikiHighlighter>
281
306
 
282
- // Using the hook
307
+ // Hook
283
308
  const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
284
309
  transformers: [customTransformer],
285
310
  });
286
311
  ```
287
312
 
313
+ ### Line Numbers
314
+
315
+ Display line numbers alongside your code, these are CSS-based
316
+ and can be customized with CSS variables:
317
+
318
+ ```tsx
319
+ // Component
320
+ <ShikiHighlighter
321
+ language="javascript"
322
+ theme="github-dark"
323
+ showLineNumbers,
324
+ startingLineNumber={0} // default is 1
325
+ >
326
+ {code}
327
+ </ShikiHighlighter>
328
+
329
+ <ShikiHighlighter
330
+ language="python"
331
+ theme="github-dark"
332
+ showLineNumbers
333
+ startingLineNumber={0}
334
+ >
335
+ {code}
336
+ </ShikiHighlighter>
337
+
338
+ // Hook (import 'react-shiki/css' for line numbers to work)
339
+ const highlightedCode = useShikiHighlighter(code, "javascript", "github-dark", {
340
+ showLineNumbers: true,
341
+ startingLineNumber: 0,
342
+ });
343
+ ```
344
+
345
+ > [!NOTE]
346
+ > When using the hook with line numbers, import the CSS file for the line numbers to work:
347
+ > ```tsx
348
+ > import 'react-shiki/css';
349
+ > ```
350
+ > Or provide your own CSS counter implementation and styles for `.line-numbers` (line `span`) and `.has-line-numbers` (container `code` element)
351
+
352
+ Available CSS variables for customization:
353
+ ```css
354
+ --line-numbers-foreground: rgba(107, 114, 128, 0.5);
355
+ --line-numbers-width: 2ch;
356
+ --line-numbers-padding-left: 0ch;
357
+ --line-numbers-padding-right: 2ch;
358
+ --line-numbers-font-size: inherit;
359
+ --line-numbers-font-weight: inherit;
360
+ --line-numbers-opacity: 1;
361
+ ```
362
+
363
+ You can customize them in your own CSS or by using the style prop on the component:
364
+ ```tsx
365
+ <ShikiHighlighter
366
+ language="javascript"
367
+ theme="github-dark"
368
+ showLineNumbers
369
+ style={{
370
+ '--line-numbers-foreground': '#60a5fa',
371
+ '--line-numbers-width': '3ch'
372
+ }}
373
+ >
374
+ {code}
375
+ </ShikiHighlighter>
376
+ ```
377
+
288
378
  ## Integration
289
379
 
290
380
  ### Integration with react-markdown
@@ -35,7 +35,7 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
35
35
  import { toJsxRuntime } from "hast-util-to-jsx-runtime";
36
36
 
37
37
  // src/lib/resolvers.ts
38
- var resolveLanguage = (lang, customLanguages) => {
38
+ var resolveLanguage = (lang, customLanguages, langAliases) => {
39
39
  const normalizedCustomLangs = customLanguages ? Array.isArray(customLanguages) ? customLanguages : [customLanguages] : [];
40
40
  if (lang == null || typeof lang === "string" && !lang.trim()) {
41
41
  return {
@@ -63,6 +63,13 @@ var resolveLanguage = (lang, customLanguages) => {
63
63
  langsToLoad: customMatch
64
64
  };
65
65
  }
66
+ if (langAliases?.[lang]) {
67
+ return {
68
+ languageId: langAliases[lang],
69
+ displayLanguageId: lang,
70
+ langsToLoad: langAliases[lang]
71
+ };
72
+ }
66
73
  return {
67
74
  languageId: lang,
68
75
  displayLanguageId: lang,
@@ -94,6 +101,28 @@ function resolveTheme(themeInput) {
94
101
  };
95
102
  }
96
103
 
104
+ // src/lib/transformers.ts
105
+ function lineNumbersTransformer(startLine = 1) {
106
+ return {
107
+ name: "react-shiki:line-numbers",
108
+ code(node) {
109
+ this.addClassToHast(node, "has-line-numbers");
110
+ if (startLine !== 1) {
111
+ const existingStyle = node.properties?.style || "";
112
+ const newStyle = existingStyle ? `${existingStyle}; --line-start: ${startLine}` : `--line-start: ${startLine}`;
113
+ node.properties = {
114
+ ...node.properties,
115
+ style: newStyle
116
+ };
117
+ }
118
+ },
119
+ line(node) {
120
+ this.addClassToHast(node, "line-numbers");
121
+ return node;
122
+ }
123
+ };
124
+ }
125
+
97
126
  // src/lib/hook.ts
98
127
  var DEFAULT_THEMES = {
99
128
  light: "github-light",
@@ -123,8 +152,12 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
123
152
  const [stableTheme, themeRev] = useStableOptions(themeInput);
124
153
  const [stableOpts, optsRev] = useStableOptions(options);
125
154
  const { languageId, langsToLoad } = useMemo(
126
- () => resolveLanguage(stableLang, stableOpts.customLanguages),
127
- [stableLang, stableOpts.customLanguages]
155
+ () => resolveLanguage(
156
+ stableLang,
157
+ stableOpts.customLanguages,
158
+ stableOpts.langAlias
159
+ ),
160
+ [stableLang, stableOpts.customLanguages, stableOpts.langAlias]
128
161
  );
129
162
  const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } = useMemo(() => resolveTheme(stableTheme), [stableTheme]);
130
163
  const timeoutControl = useRef({
@@ -132,8 +165,14 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
132
165
  timeoutId: void 0
133
166
  });
134
167
  const shikiOptions = useMemo(() => {
135
- const { defaultColor, cssVariablePrefix, ...restOptions } = stableOpts;
136
168
  const languageOption = { lang: languageId };
169
+ const {
170
+ defaultColor,
171
+ cssVariablePrefix,
172
+ showLineNumbers,
173
+ startingLineNumber,
174
+ ...restOptions
175
+ } = stableOpts;
137
176
  const themeOptions = isMultiTheme ? {
138
177
  themes: multiTheme || DEFAULT_THEMES,
139
178
  defaultColor,
@@ -141,7 +180,16 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
141
180
  } : {
142
181
  theme: singleTheme || DEFAULT_THEMES.dark
143
182
  };
144
- return { ...languageOption, ...themeOptions, ...restOptions };
183
+ const transformers = restOptions.transformers || [];
184
+ if (showLineNumbers) {
185
+ transformers.push(lineNumbersTransformer(startingLineNumber));
186
+ }
187
+ return {
188
+ ...languageOption,
189
+ ...themeOptions,
190
+ ...restOptions,
191
+ transformers
192
+ };
145
193
  }, [languageId, themeId, langRev, themeRev, optsRev]);
146
194
  useEffect(() => {
147
195
  let isMounted = true;
@@ -203,7 +251,7 @@ function styleInject(css, { insertAt } = {}) {
203
251
  }
204
252
 
205
253
  // 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");
254
+ 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.line-numbers::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(--line-numbers-width, 2ch);\n padding-left: var(--line-numbers-padding-left, 2ch);\n padding-right: var(--line-numbers-padding-right, 2ch);\n color: var(--line-numbers-foreground, rgba(107, 114, 128, 0.6));\n font-size: var(--line-numbers-font-size, inherit);\n font-weight: var(--line-numbers-font-weight, inherit);\n line-height: var(--line-numbers-line-height, inherit);\n font-family: var(--line-numbers-font-family, inherit);\n opacity: var(--line-numbers-opacity, 1);\n user-select: none;\n pointer-events: none;\n}\n.has-line-numbers {\n counter-reset: line-number calc(var(--line-start, 1) - 1);\n --line-numbers-foreground: rgba(107, 114, 128, 0.5);\n --line-numbers-width: 2ch;\n --line-numbers-padding-left: 0ch;\n --line-numbers-padding-right: 2ch;\n --line-numbers-font-size: inherit;\n --line-numbers-font-weight: inherit;\n --line-numbers-line-height: inherit;\n --line-numbers-font-family: inherit;\n --line-numbers-opacity: 1;\n}\n");
207
255
 
208
256
  // src/lib/component.tsx
209
257
  import { clsx } from "clsx";
@@ -222,6 +270,8 @@ var createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
222
270
  className,
223
271
  langClassName,
224
272
  showLanguage = true,
273
+ showLineNumbers = false,
274
+ startingLineNumber = 1,
225
275
  children: code,
226
276
  as: Element = "pre",
227
277
  customLanguages,
@@ -233,6 +283,8 @@ var createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
233
283
  customLanguages,
234
284
  defaultColor,
235
285
  cssVariablePrefix,
286
+ showLineNumbers,
287
+ startingLineNumber,
236
288
  ...shikiOptions
237
289
  };
238
290
  const { displayLanguageId } = resolveLanguage(
@@ -280,4 +332,4 @@ export {
280
332
  useShikiHighlighter,
281
333
  createShikiHighlighterComponent
282
334
  };
283
- //# sourceMappingURL=chunk-XAMWR6XF.js.map
335
+ //# sourceMappingURL=chunk-SDEA5YEL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/lib/hook.ts","../src/lib/resolvers.ts","../src/lib/transformers.ts","#style-inject:#style-inject","../src/lib/styles.css","../src/lib/component.tsx"],"sourcesContent":["import { visit } from 'unist-util-visit';\n\nimport type { TimeoutState, 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: any): undefined => {\n visit(tree, 'element', (node: Element, _index, parent: Element) => {\n if (node.tagName === 'code' && parent.tagName !== 'pre') {\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\n/**\n * Optionally throttles rapid sequential highlighting operations\n * Exported for testing in __tests__/throttleHighlighting.test.ts\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 const now = Date.now();\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(0, timeoutControl.current.nextAllowedTime - now);\n timeoutControl.current.timeoutId = setTimeout(() => {\n performHighlight().catch(console.error);\n timeoutControl.current.nextAllowedTime = now + throttleMs;\n }, delay);\n};\n","import {\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport { dequal } from 'dequal/lite';\n\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\n\nimport type {\n CodeToHastOptions,\n CodeOptionsSingleTheme,\n CodeOptionsMultipleThemes,\n Highlighter,\n HighlighterCore,\n} from 'shiki';\n\nimport type { ShikiLanguageRegistration } from './extended-types';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState,\n Themes,\n} from './types';\n\nimport { throttleHighlighting } from './utils';\nimport { resolveLanguage, resolveTheme } from './resolvers';\nimport { lineNumbersTransformer } from './transformers';\n\nconst DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\n/**\n * Returns a deep-stable reference and a version counter that only changes when content changes.\n * Includes optimizations for primitive values and reference equality.\n */\nconst useStableOptions = <T>(value: T) => {\n const ref = useRef(value);\n const revision = useRef(0);\n\n // Fast-path for primitive values\n if (typeof value !== 'object' || value === null) {\n if (value !== ref.current) {\n ref.current = value;\n revision.current += 1;\n }\n return [value, revision.current] as const;\n }\n\n // Reference equality check before expensive deep comparison\n if (value !== ref.current) {\n if (!dequal(value, ref.current)) {\n ref.current = value;\n revision.current += 1;\n }\n }\n\n return [ref.current, revision.current] as const;\n};\n\n/**\n * Base hook for syntax highlighting using Shiki.\n * This is the core implementation used by all entry points.\n *\n * @param code - The code to highlight\n * @param lang - Language for highlighting\n * @param themeInput - Theme or themes to use\n * @param options - Highlighting options\n * @param createHighlighter - Factory function to create highlighter (internal use)\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n createHighlighter: (\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n ) => Promise<Highlighter | HighlighterCore>,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] =\n useState<ReactNode | null>(null);\n\n // Stabilize options, language and theme inputs to prevent unnecessary\n // re-renders or recalculations when object references change\n const [stableLang, langRev] = useStableOptions(lang);\n const [stableTheme, themeRev] = useStableOptions(themeInput);\n const [stableOpts, optsRev] = useStableOptions(options);\n\n const { languageId, langsToLoad } = useMemo(\n () =>\n resolveLanguage(\n stableLang,\n stableOpts.customLanguages,\n stableOpts.langAlias\n ),\n [stableLang, stableOpts.customLanguages, stableOpts.langAlias]\n );\n\n const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } =\n useMemo(() => resolveTheme(stableTheme), [stableTheme]);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n const shikiOptions = useMemo<CodeToHastOptions>(() => {\n const languageOption = { lang: languageId };\n const {\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n ...restOptions\n } = stableOpts;\n\n const themeOptions = isMultiTheme\n ? ({\n themes: multiTheme || DEFAULT_THEMES,\n defaultColor,\n cssVariablePrefix,\n } as CodeOptionsMultipleThemes)\n : ({\n theme: singleTheme || DEFAULT_THEMES.dark,\n } as CodeOptionsSingleTheme);\n\n const transformers = restOptions.transformers || [];\n if (showLineNumbers) {\n transformers.push(lineNumbersTransformer(startingLineNumber));\n }\n\n return {\n ...languageOption,\n ...themeOptions,\n ...restOptions,\n transformers,\n };\n }, [languageId, themeId, langRev, themeRev, optsRev]);\n\n useEffect(() => {\n let isMounted = true;\n\n const highlightCode = async () => {\n if (!languageId) return;\n\n const highlighter = stableOpts.highlighter\n ? stableOpts.highlighter\n : await createHighlighter(\n langsToLoad as ShikiLanguageRegistration,\n themesToLoad\n );\n\n const loadedLanguages = highlighter.getLoadedLanguages();\n const langToUse = loadedLanguages.includes(languageId)\n ? languageId\n : 'plaintext';\n const finalOptions = { ...shikiOptions, lang: langToUse };\n\n const hast = highlighter.codeToHast(code, finalOptions);\n\n if (isMounted) {\n setHighlightedCode(toJsxRuntime(hast, { jsx, jsxs, Fragment }));\n }\n };\n\n const { delay } = stableOpts;\n\n if (delay) {\n throttleHighlighting(highlightCode, timeoutControl, delay);\n } else {\n highlightCode().catch(console.error);\n }\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutControl.current.timeoutId);\n };\n }, [\n code,\n shikiOptions,\n stableOpts.delay,\n stableOpts.highlighter,\n langsToLoad,\n themesToLoad,\n ]);\n\n return highlightedCode;\n};\n","import type { Language, Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\nimport type { LanguageRegistration } from './extended-types';\n\n/**\n * Resolved languages and metadata\n */\ntype LanguageResult = {\n languageId: string;\n displayLanguageId: string | null;\n langsToLoad: Language;\n};\n\n/**\n * Resolves the language input to standardized IDs and objects for Shiki and UI display\n * @param lang The language input from props\n * @param customLanguages An array of custom textmate grammar objects or a single grammar object\n * @returns A LanguageResult object containing:\n * - languageId: The resolved language ID\n * - displayLanguageId: The display language ID\n * - langToLoad: The language object or string id to load\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[],\n langAliases?: Record<string, string>\n): LanguageResult => {\n const normalizedCustomLangs = customLanguages\n ? Array.isArray(customLanguages)\n ? customLanguages\n : [customLanguages]\n : [];\n\n // Language is null or empty string\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return {\n languageId: 'plaintext',\n displayLanguageId: 'plaintext',\n langsToLoad: undefined,\n };\n }\n\n // Language is custom\n if (typeof lang === 'object') {\n return {\n languageId: lang.name,\n displayLanguageId: lang.name || null,\n langsToLoad: lang,\n };\n }\n\n // Language is string\n const lowerLang = lang.toLowerCase();\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === lowerLang;\n\n // Check if the string identifies a provided custom language\n const customMatch = normalizedCustomLangs.find(\n (cl) =>\n matches(cl.name) ||\n matches(cl.scopeName) ||\n matches(cl.scopeName?.split('.').pop()) ||\n cl.aliases?.some(matches) ||\n cl.fileTypes?.some(matches)\n );\n\n if (customMatch) {\n return {\n languageId: customMatch.name || lang,\n displayLanguageId: lang,\n langsToLoad: customMatch,\n };\n }\n\n // Check if language is aliased\n if (langAliases?.[lang]) {\n return {\n languageId: langAliases[lang],\n displayLanguageId: lang,\n langsToLoad: langAliases[lang],\n };\n }\n\n // For any other string, pass it through,\n // fallback is handled in highlighter factories\n return {\n languageId: lang,\n displayLanguageId: lang,\n langsToLoad: lang,\n };\n};\n\n/**\n * Resolved themes and metadata\n */\ninterface ThemeResult {\n isMultiTheme: boolean;\n themeId: Theme;\n multiTheme?: Themes | ThemeRegistrationAny | null;\n singleTheme?: Theme | undefined;\n themesToLoad: Theme[];\n}\n\n/**\n * Determines theme configuration and returns the resolved theme with metadata\n * @param themeInput - The theme input, either as a string name or theme object\n * @returns Object containing:\n * - isMultiTheme: If theme input is a multi-theme configuration\n * - themeId: Theme reference identifier\n * - multiTheme: The multi-theme config if it exists\n * - singleTheme: The single theme if it exists\n * - themesToLoad: The themes to load when creating the highlighter\n */\nexport function resolveTheme(themeInput: Theme | Themes): ThemeResult {\n const isTextmateTheme =\n typeof themeInput === 'object' &&\n 'tokenColors' in themeInput &&\n Array.isArray(themeInput.tokenColors);\n\n // Assume non textmate objects are multi theme configs\n const isMultiThemeConfig =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme;\n\n const validMultiThemeObj =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme &&\n Object.entries(themeInput).some(\n ([key, value]) =>\n key &&\n value &&\n key.trim() !== '' &&\n value !== '' &&\n (typeof value === 'string' || isTextmateTheme)\n );\n\n if (isMultiThemeConfig) {\n const themeId = validMultiThemeObj\n ? `multi-${Object.values(themeInput)\n .map(\n (theme) =>\n (typeof theme === 'string' ? theme : theme?.name) ||\n 'custom'\n )\n .sort()\n .join('-')}`\n : 'multi-default';\n\n // If config is invalid, return null to handle fallback in `buildShikiOptions()`\n return {\n isMultiTheme: true,\n themeId,\n multiTheme: validMultiThemeObj ? themeInput : null,\n themesToLoad: validMultiThemeObj ? Object.values(themeInput) : [],\n };\n }\n\n return {\n isMultiTheme: false,\n themeId:\n typeof themeInput === 'string'\n ? themeInput\n : themeInput?.name || 'custom',\n singleTheme: themeInput,\n themesToLoad: [themeInput],\n };\n}\n","import type { ShikiTransformer } from 'shiki/core';\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, 'has-line-numbers');\n if (startLine !== 1) {\n const existingStyle = (node.properties?.style as string) || '';\n const newStyle = existingStyle\n ? `${existingStyle}; --line-start: ${startLine}`\n : `--line-start: ${startLine}`;\n node.properties = {\n ...node.properties,\n style: newStyle,\n };\n }\n },\n line(node) {\n this.addClassToHast(node, 'line-numbers');\n return node;\n },\n };\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';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.line-numbers::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(--line-numbers-width, 2ch);\\n padding-left: var(--line-numbers-padding-left, 2ch);\\n padding-right: var(--line-numbers-padding-right, 2ch);\\n color: var(--line-numbers-foreground, rgba(107, 114, 128, 0.6));\\n font-size: var(--line-numbers-font-size, inherit);\\n font-weight: var(--line-numbers-font-weight, inherit);\\n line-height: var(--line-numbers-line-height, inherit);\\n font-family: var(--line-numbers-font-family, inherit);\\n opacity: var(--line-numbers-opacity, 1);\\n user-select: none;\\n pointer-events: none;\\n}\\n.has-line-numbers {\\n counter-reset: line-number calc(var(--line-start, 1) - 1);\\n --line-numbers-foreground: rgba(107, 114, 128, 0.5);\\n --line-numbers-width: 2ch;\\n --line-numbers-padding-left: 0ch;\\n --line-numbers-padding-right: 2ch;\\n --line-numbers-font-size: inherit;\\n --line-numbers-font-weight: inherit;\\n --line-numbers-line-height: inherit;\\n --line-numbers-font-family: inherit;\\n --line-numbers-opacity: 1;\\n}\\n\")","import './styles.css';\nimport { clsx } from 'clsx';\nimport { resolveLanguage } from './resolvers';\n\nimport type {\n HighlighterOptions,\n Language,\n Theme,\n Themes,\n} from './types';\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 * Whether to show line numbers\n * @default false\n */\n showLineNumbers?: boolean;\n\n /**\n * Starting line number (when showLineNumbers is true)\n * @default 1\n */\n startingLineNumber?: number;\n\n /**\n * The HTML element that wraps the generated code block.\n * @default 'pre'\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: (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n options?: HighlighterOptions\n ) => React.ReactNode\n) => {\n return ({\n language,\n theme,\n delay,\n transformers,\n defaultColor,\n cssVariablePrefix,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n showLineNumbers = false,\n startingLineNumber = 1,\n children: code,\n as: Element = 'pre',\n customLanguages,\n ...shikiOptions\n }: ShikiHighlighterProps): React.ReactElement => {\n const options: HighlighterOptions = {\n delay,\n transformers,\n customLanguages,\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n ...shikiOptions,\n };\n\n // Use resolveLanguage to get displayLanguageId directly\n const { displayLanguageId } = resolveLanguage(\n language,\n customLanguages\n );\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n options\n );\n\n return (\n <Element\n data-testid=\"shiki-container\"\n className={clsx(\n 'relative',\n 'not-prose',\n addDefaultStyles && 'defaultStyles',\n className\n )}\n style={style}\n id=\"shiki-container\"\n >\n {showLanguage && displayLanguageId ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id=\"language-label\"\n >\n {displayLanguageId}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n };\n};\n"],"mappings":"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","names":["jsx","jsxs"]}
@@ -1,4 +1,4 @@
1
- import { Highlighter, HighlighterCore, CodeOptionsMultipleThemes, BundledTheme, CodeToHastOptions, StringLiteralUnion, BundledLanguage, SpecialLanguage, ThemeRegistrationAny } from 'shiki';
1
+ import { Highlighter, HighlighterCore, CodeOptionsMultipleThemes, BundledTheme, CodeToHastOptions, BundledHighlighterOptions, StringLiteralUnion, BundledLanguage, SpecialLanguage, ThemeRegistrationAny } from 'shiki';
2
2
  import { ReactNode } from 'react';
3
3
  import { LanguageRegistration as LanguageRegistration$1 } from 'shiki/core';
4
4
  import { Element as Element$1 } from 'hast';
@@ -113,12 +113,22 @@ interface ReactShikiOptions {
113
113
  * });
114
114
  */
115
115
  highlighter?: Highlighter | HighlighterCore;
116
+ /**
117
+ * Whether to show line numbers
118
+ * @default false
119
+ */
120
+ showLineNumbers?: boolean;
121
+ /**
122
+ * Starting line number (when showLineNumbers is true)
123
+ * @default 1
124
+ */
125
+ startingLineNumber?: number;
116
126
  }
117
127
  /**
118
128
  * Configuration options for the syntax highlighter
119
129
  * Extends CodeToHastOptions to allow passing any Shiki options directly
120
130
  */
121
- interface HighlighterOptions extends ReactShikiOptions, Pick<CodeOptionsMultipleThemes<BundledTheme>, 'defaultColor' | 'cssVariablePrefix'>, Omit<CodeToHastOptions, 'lang' | 'theme' | 'themes'> {
131
+ interface HighlighterOptions extends ReactShikiOptions, Pick<CodeOptionsMultipleThemes<BundledTheme>, 'defaultColor' | 'cssVariablePrefix'>, Omit<CodeToHastOptions, 'lang' | 'theme' | 'themes'>, Pick<BundledHighlighterOptions<string, string>, 'langAlias'> {
122
132
  }
123
133
  /**
124
134
  * Public API signature for the useShikiHighlighter hook.
@@ -199,6 +209,16 @@ interface ShikiHighlighterProps extends HighlighterOptions {
199
209
  * @default true
200
210
  */
201
211
  showLanguage?: boolean;
212
+ /**
213
+ * Whether to show line numbers
214
+ * @default false
215
+ */
216
+ showLineNumbers?: boolean;
217
+ /**
218
+ * Starting line number (when showLineNumbers is true)
219
+ * @default 1
220
+ */
221
+ startingLineNumber?: number;
202
222
  /**
203
223
  * The HTML element that wraps the generated code block.
204
224
  * @default 'pre'
package/dist/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-D73l5tW-.js';
2
- export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-D73l5tW-.js';
1
+ import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
2
+ export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
3
3
  export { createHighlighterCore } from 'shiki/core';
4
4
  export { createOnigurumaEngine } from 'shiki/engine/oniguruma';
5
5
  export { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
@@ -33,6 +33,6 @@ declare const useShikiHighlighter: UseShikiHighlighter;
33
33
  * ShikiHighlighter component using a custom highlighter.
34
34
  * Requires a highlighter to be provided.
35
35
  */
36
- declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
36
+ declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, showLineNumbers, startingLineNumber, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
37
37
 
38
38
  export { ShikiHighlighter, ShikiHighlighterProps, UseShikiHighlighter, ShikiHighlighter as default, useShikiHighlighter };
package/dist/core.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  isInlineCode,
4
4
  rehypeInlineCodeProperty,
5
5
  useShikiHighlighter
6
- } from "./chunk-XAMWR6XF.js";
6
+ } from "./chunk-SDEA5YEL.js";
7
7
 
8
8
  // src/bundles/core.ts
9
9
  function validateCoreHighlighter(highlighter) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-D73l5tW-.js';
2
- export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-D73l5tW-.js';
1
+ import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
2
+ export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
3
3
  import 'shiki';
4
4
  import 'react';
5
5
  import 'shiki/core';
@@ -20,6 +20,6 @@ declare const useShikiHighlighter: UseShikiHighlighter;
20
20
  * ShikiHighlighter component using the full bundle.
21
21
  * Includes all languages and themes for maximum compatibility.
22
22
  */
23
- declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
23
+ declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, showLineNumbers, startingLineNumber, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
24
24
 
25
25
  export { ShikiHighlighter, ShikiHighlighterProps, UseShikiHighlighter, ShikiHighlighter as default, useShikiHighlighter };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  isInlineCode,
4
4
  rehypeInlineCodeProperty,
5
5
  useShikiHighlighter
6
- } from "./chunk-XAMWR6XF.js";
6
+ } from "./chunk-SDEA5YEL.js";
7
7
 
8
8
  // src/bundles/full.ts
9
9
  import { getSingletonHighlighter } from "shiki";
package/dist/web.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-D73l5tW-.js';
2
- export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-D73l5tW-.js';
1
+ import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
2
+ export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
3
3
  import 'shiki';
4
4
  import 'react';
5
5
  import 'shiki/core';
@@ -20,6 +20,6 @@ declare const useShikiHighlighter: UseShikiHighlighter;
20
20
  * ShikiHighlighter component using the web bundle.
21
21
  * Includes web-focused languages for balanced size and functionality.
22
22
  */
23
- declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
23
+ declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, showLineNumbers, startingLineNumber, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
24
24
 
25
25
  export { ShikiHighlighter, ShikiHighlighterProps, UseShikiHighlighter, ShikiHighlighter as default, useShikiHighlighter };
package/dist/web.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  isInlineCode,
4
4
  rehypeInlineCodeProperty,
5
5
  useShikiHighlighter
6
- } from "./chunk-XAMWR6XF.js";
6
+ } from "./chunk-SDEA5YEL.js";
7
7
 
8
8
  // src/bundles/web.ts
9
9
  import {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-shiki",
3
3
  "description": "Syntax highlighter component for react using shiki",
4
- "version": "0.7.0",
4
+ "version": "0.7.2",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Bassim Shahidy",
@@ -27,7 +27,8 @@
27
27
  "main": "./dist/index.js",
28
28
  "types": "./dist/index.d.ts",
29
29
  "files": [
30
- "dist"
30
+ "dist",
31
+ "src/lib/styles.css"
31
32
  ],
32
33
  "exports": {
33
34
  ".": {
@@ -41,7 +42,8 @@
41
42
  "./core": {
42
43
  "types": "./dist/core.d.ts",
43
44
  "default": "./dist/core.js"
44
- }
45
+ },
46
+ "./css": "./src/lib/styles.css"
45
47
  },
46
48
  "peerDependencies": {
47
49
  "react": ">= 16.8.0",
@@ -0,0 +1,55 @@
1
+ .relative {
2
+ position: relative;
3
+ }
4
+
5
+ .defaultStyles pre {
6
+ overflow: auto;
7
+ border-radius: 0.5rem;
8
+ padding-left: 1.5rem;
9
+ padding-right: 1.5rem;
10
+ padding-top: 1.25rem;
11
+ padding-bottom: 1.25rem;
12
+ }
13
+
14
+ .languageLabel {
15
+ position: absolute;
16
+ right: 0.75rem;
17
+ top: 0.5rem;
18
+ font-family: monospace;
19
+ font-size: 0.75rem;
20
+ letter-spacing: -0.05em;
21
+ color: rgba(107, 114, 128, 0.85);
22
+ }
23
+
24
+ .line-numbers::before {
25
+ counter-increment: line-number;
26
+ content: counter(line-number);
27
+ display: inline-flex;
28
+ justify-content: flex-end;
29
+ align-items: flex-start;
30
+ box-sizing: content-box;
31
+ min-width: var(--line-numbers-width, 2ch);
32
+ padding-left: var(--line-numbers-padding-left, 2ch);
33
+ padding-right: var(--line-numbers-padding-right, 2ch);
34
+ color: var(--line-numbers-foreground, rgba(107, 114, 128, 0.6));
35
+ font-size: var(--line-numbers-font-size, inherit);
36
+ font-weight: var(--line-numbers-font-weight, inherit);
37
+ line-height: var(--line-numbers-line-height, inherit);
38
+ font-family: var(--line-numbers-font-family, inherit);
39
+ opacity: var(--line-numbers-opacity, 1);
40
+ user-select: none;
41
+ pointer-events: none;
42
+ }
43
+
44
+ .has-line-numbers {
45
+ counter-reset: line-number calc(var(--line-start, 1) - 1);
46
+ --line-numbers-foreground: rgba(107, 114, 128, 0.5);
47
+ --line-numbers-width: 2ch;
48
+ --line-numbers-padding-left: 0ch;
49
+ --line-numbers-padding-right: 2ch;
50
+ --line-numbers-font-size: inherit;
51
+ --line-numbers-font-weight: inherit;
52
+ --line-numbers-line-height: inherit;
53
+ --line-numbers-font-family: inherit;
54
+ --line-numbers-opacity: 1;
55
+ }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/lib/hook.ts","../src/lib/resolvers.ts","#style-inject:#style-inject","../src/lib/styles.css","../src/lib/component.tsx"],"sourcesContent":["import { visit } from 'unist-util-visit';\n\nimport type { TimeoutState, 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: any): undefined => {\n visit(tree, 'element', (node: Element, _index, parent: Element) => {\n if (node.tagName === 'code' && parent.tagName !== 'pre') {\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\n/**\n * Optionally throttles rapid sequential highlighting operations\n * Exported for testing in __tests__/throttleHighlighting.test.ts\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 const now = Date.now();\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(0, timeoutControl.current.nextAllowedTime - now);\n timeoutControl.current.timeoutId = setTimeout(() => {\n performHighlight().catch(console.error);\n timeoutControl.current.nextAllowedTime = now + throttleMs;\n }, delay);\n};\n","import {\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport { dequal } from 'dequal/lite';\n\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\n\nimport type {\n CodeToHastOptions,\n CodeOptionsSingleTheme,\n CodeOptionsMultipleThemes,\n Highlighter,\n HighlighterCore,\n} from 'shiki';\n\nimport type { ShikiLanguageRegistration } from './extended-types';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState,\n Themes,\n} from './types';\n\nimport { throttleHighlighting } from './utils';\nimport { resolveLanguage, resolveTheme } from './resolvers';\n\nconst DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\n/**\n * Returns a deep-stable reference and a version counter that only changes when content changes.\n * Includes optimizations for primitive values and reference equality.\n */\nconst useStableOptions = <T>(value: T) => {\n const ref = useRef(value);\n const revision = useRef(0);\n\n // Fast-path for primitive values\n if (typeof value !== 'object' || value === null) {\n if (value !== ref.current) {\n ref.current = value;\n revision.current += 1;\n }\n return [value, revision.current] as const;\n }\n\n // Reference equality check before expensive deep comparison\n if (value !== ref.current) {\n if (!dequal(value, ref.current)) {\n ref.current = value;\n revision.current += 1;\n }\n }\n\n return [ref.current, revision.current] as const;\n};\n\n/**\n * Base hook for syntax highlighting using Shiki.\n * This is the core implementation used by all entry points.\n *\n * @param code - The code to highlight\n * @param lang - Language for highlighting\n * @param themeInput - Theme or themes to use\n * @param options - Highlighting options\n * @param createHighlighter - Factory function to create highlighter (internal use)\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n createHighlighter: (\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n ) => Promise<Highlighter | HighlighterCore>,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] =\n useState<ReactNode | null>(null);\n\n // Stabilize options, language and theme inputs to prevent unnecessary\n // re-renders or recalculations when object references change\n const [stableLang, langRev] = useStableOptions(lang);\n const [stableTheme, themeRev] = useStableOptions(themeInput);\n const [stableOpts, optsRev] = useStableOptions(options);\n\n const { languageId, langsToLoad } = useMemo(\n () => resolveLanguage(stableLang, stableOpts.customLanguages),\n [stableLang, stableOpts.customLanguages]\n );\n\n const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } =\n useMemo(() => resolveTheme(stableTheme), [stableTheme]);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n const shikiOptions = useMemo<CodeToHastOptions>(() => {\n const { defaultColor, cssVariablePrefix, ...restOptions } =\n stableOpts;\n const languageOption = { lang: languageId };\n\n const themeOptions = isMultiTheme\n ? ({\n themes: multiTheme || DEFAULT_THEMES,\n defaultColor,\n cssVariablePrefix,\n } as CodeOptionsMultipleThemes)\n : ({\n theme: singleTheme || DEFAULT_THEMES.dark,\n } as CodeOptionsSingleTheme);\n\n return { ...languageOption, ...themeOptions, ...restOptions };\n }, [languageId, themeId, langRev, themeRev, optsRev]);\n\n useEffect(() => {\n let isMounted = true;\n\n const highlightCode = async () => {\n if (!languageId) return;\n\n // Use provided custom highlighter or create one using the factory\n const highlighter = stableOpts.highlighter\n ? stableOpts.highlighter\n : await createHighlighter(\n langsToLoad as ShikiLanguageRegistration,\n themesToLoad\n );\n\n // Check if language is loaded, fallback to plaintext if not\n const loadedLanguages = highlighter.getLoadedLanguages();\n const langToUse = loadedLanguages.includes(languageId) ? languageId : 'plaintext';\n const finalOptions = { ...shikiOptions, lang: langToUse };\n \n const hast = highlighter.codeToHast(code, finalOptions);\n\n if (isMounted) {\n setHighlightedCode(toJsxRuntime(hast, { jsx, jsxs, Fragment }));\n }\n };\n\n const { delay } = stableOpts;\n\n if (delay) {\n throttleHighlighting(highlightCode, timeoutControl, delay);\n } else {\n highlightCode().catch(console.error);\n }\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutControl.current.timeoutId);\n };\n }, [\n code,\n shikiOptions,\n stableOpts.delay,\n stableOpts.highlighter,\n langsToLoad,\n themesToLoad,\n ]);\n\n return highlightedCode;\n};\n","import type { Language, Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\nimport type { LanguageRegistration } from './extended-types';\n\n/**\n * Resolved languages and metadata\n */\ntype LanguageResult = {\n languageId: string;\n displayLanguageId: string | null;\n langsToLoad: Language;\n};\n\n/**\n * Resolves the language input to standardized IDs and objects for Shiki and UI display\n * @param lang The language input from props\n * @param customLanguages An array of custom textmate grammar objects or a single grammar object\n * @returns A LanguageResult object containing:\n * - languageId: The resolved language ID\n * - displayLanguageId: The display language ID\n * - langToLoad: The language object or string id to load\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[]\n): LanguageResult => {\n const normalizedCustomLangs = customLanguages\n ? Array.isArray(customLanguages)\n ? customLanguages\n : [customLanguages]\n : [];\n\n // Language is null or empty string\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return {\n languageId: 'plaintext',\n displayLanguageId: 'plaintext',\n langsToLoad: undefined,\n };\n }\n\n // Language is custom\n if (typeof lang === 'object') {\n return {\n languageId: lang.name,\n displayLanguageId: lang.name || null,\n langsToLoad: lang,\n };\n }\n\n // Language is string\n const lowerLang = lang.toLowerCase();\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === lowerLang;\n\n // Check if the string identifies a provided custom language\n const customMatch = normalizedCustomLangs.find(\n (cl) =>\n matches(cl.name) ||\n matches(cl.scopeName) ||\n matches(cl.scopeName?.split('.').pop()) ||\n cl.aliases?.some(matches) ||\n cl.fileTypes?.some(matches)\n );\n\n if (customMatch) {\n return {\n languageId: customMatch.name || lang,\n displayLanguageId: lang,\n langsToLoad: customMatch,\n };\n }\n\n // For any other string, pass it through, \n // fallback is handled in highlighter factories\n return {\n languageId: lang,\n displayLanguageId: lang,\n langsToLoad: lang,\n };\n};\n\n/**\n * Resolved themes and metadata\n */\ninterface ThemeResult {\n isMultiTheme: boolean;\n themeId: Theme;\n multiTheme?: Themes | ThemeRegistrationAny | null;\n singleTheme?: Theme | undefined;\n themesToLoad: Theme[];\n}\n\n/**\n * Determines theme configuration and returns the resolved theme with metadata\n * @param themeInput - The theme input, either as a string name or theme object\n * @returns Object containing:\n * - isMultiTheme: If theme input is a multi-theme configuration\n * - themeId: Theme reference identifier\n * - multiTheme: The multi-theme config if it exists\n * - singleTheme: The single theme if it exists\n * - themesToLoad: The themes to load when creating the highlighter\n */\nexport function resolveTheme(themeInput: Theme | Themes): ThemeResult {\n const isTextmateTheme =\n typeof themeInput === 'object' &&\n 'tokenColors' in themeInput &&\n Array.isArray(themeInput.tokenColors);\n\n // Assume non textmate objects are multi theme configs\n const isMultiThemeConfig =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme;\n\n const validMultiThemeObj =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme &&\n Object.entries(themeInput).some(\n ([key, value]) =>\n key &&\n value &&\n key.trim() !== '' &&\n value !== '' &&\n (typeof value === 'string' || isTextmateTheme)\n );\n\n if (isMultiThemeConfig) {\n const themeId = validMultiThemeObj\n ? `multi-${Object.values(themeInput)\n .map(\n (theme) =>\n (typeof theme === 'string' ? theme : theme?.name) ||\n 'custom'\n )\n .sort()\n .join('-')}`\n : 'multi-default';\n\n // If config is invalid, return null to handle fallback in `buildShikiOptions()`\n return {\n isMultiTheme: true,\n themeId,\n multiTheme: validMultiThemeObj ? themeInput : null,\n themesToLoad: validMultiThemeObj ? Object.values(themeInput) : [],\n };\n }\n\n return {\n isMultiTheme: false,\n themeId:\n typeof themeInput === 'string'\n ? themeInput\n : themeInput?.name || 'custom',\n singleTheme: themeInput,\n themesToLoad: [themeInput],\n };\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';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\")","import './styles.css';\nimport { clsx } from 'clsx';\nimport { resolveLanguage } from './resolvers';\n\nimport type {\n HighlighterOptions,\n Language,\n Theme,\n Themes,\n} from './types';\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 'pre'\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: (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n options?: HighlighterOptions\n ) => React.ReactNode\n) => {\n return ({\n language,\n theme,\n delay,\n transformers,\n defaultColor,\n cssVariablePrefix,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n children: code,\n as: Element = 'pre',\n customLanguages,\n ...shikiOptions\n }: ShikiHighlighterProps): React.ReactElement => {\n const options: HighlighterOptions = {\n delay,\n transformers,\n customLanguages,\n defaultColor,\n cssVariablePrefix,\n ...shikiOptions,\n };\n\n // Use resolveLanguage to get displayLanguageId directly\n const { displayLanguageId } = resolveLanguage(\n language,\n customLanguages\n );\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n options\n );\n\n return (\n <Element\n data-testid=\"shiki-container\"\n className={clsx(\n 'relative',\n 'not-prose',\n addDefaultStyles && 'defaultStyles',\n className\n )}\n style={style}\n id=\"shiki-container\"\n >\n {showLanguage && displayLanguageId ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id=\"language-label\"\n >\n {displayLanguageId}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n };\n};\n"],"mappings":"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","names":["jsx","jsxs"]}