Skip to content

Commit 2cd48c1

Browse files
authored
feat(component): default ShikiHighlighter container to div (#135)
* feat(component): default ShikiHighlighter container to div
1 parent 9eeecf6 commit 2cd48c1

4 files changed

Lines changed: 36 additions & 25 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-shiki": patch
3+
---
4+
5+
Change default `ShikiHighlighter` container element from `pre` to `div`

package/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ The `ShikiHighlighter` component offers minimal built-in styling and customizati
234234
| ------------------ | --------- | ------- | ---------------------------------------------------------- |
235235
| `showLanguage` | `boolean` | `true` | Displays language label in top-right corner |
236236
| `addDefaultStyles` | `boolean` | `true` | Adds minimal default styling to the highlighted code block |
237-
| `as` | `string` | `'pre'` | Component's Root HTML element |
237+
| `as` | `string` | `'div'` | Component's Root HTML element |
238238
| `className` | `string` | - | Custom class name for the code block |
239239
| `langClassName` | `string` | - | Class name for styling the language label |
240240
| `style` | `object` | - | Inline style object for the code block |

package/src/lib/component.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export interface ShikiHighlighterProps extends HighlighterOptions {
8888

8989
/**
9090
* The HTML element that wraps the generated code block.
91-
* @default 'pre'
91+
* @default 'div'
9292
*/
9393
as?: React.ElementType;
9494
}
@@ -118,7 +118,7 @@ export const createShikiHighlighterComponent = (
118118
showLineNumbers = false,
119119
startingLineNumber = 1,
120120
children: code,
121-
as: Element = 'pre',
121+
as: Element = 'div',
122122
customLanguages,
123123
preloadLanguages,
124124
...shikiOptions
@@ -150,32 +150,37 @@ export const createShikiHighlighterComponent = (
150150
options
151151
);
152152

153-
const isHtmlOutput = typeof highlightedCode === 'string';
154-
155153
return (
156154
<Element
157155
ref={ref}
158156
data-testid="shiki-container"
157+
data-slot="container"
159158
className={clsx(
160159
'rs-root',
161160
'not-prose',
162161
addDefaultStyles && 'rs-default-styles',
163162
className
164163
)}
165164
style={style}
166-
id="shiki-container"
167165
>
168166
{showLanguage && displayLanguageId ? (
169167
<span
170-
className={clsx('rs-language-label', langClassName)}
171-
style={langStyle}
172168
id="language-label"
169+
data-slot="language-label"
170+
className={clsx(
171+
'rs-language-label',
172+
langClassName
173+
)}
174+
style={langStyle}
173175
>
174176
{displayLanguageId}
175177
</span>
176178
) : null}
177-
{isHtmlOutput ? (
178-
<div dangerouslySetInnerHTML={{ __html: highlightedCode }} />
179+
{typeof highlightedCode === 'string' ? (
180+
<div
181+
data-slot="content"
182+
dangerouslySetInnerHTML={{ __html: highlightedCode }}
183+
/>
179184
) : (
180185
highlightedCode
181186
)}

package/tests/component.test.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ const codeSample = 'console.log("Hello World");';
1111
// Test utilities
1212
const getContainer = (container: HTMLElement) =>
1313
container.querySelector(
14-
'[data-testid="shiki-container"]'
14+
'[data-slot="container"]'
1515
) as HTMLElement | null;
1616

1717
const getLanguageLabel = (container: HTMLElement | null) =>
18-
container?.querySelector('#language-label') as HTMLElement | null;
18+
container?.querySelector(
19+
'[data-slot="language-label"]'
20+
) as HTMLElement | null;
1921

2022
describe('ShikiHighlighter Component', () => {
2123
describe('Component-specific Props', () => {
22-
test('renders with default pre element', async () => {
24+
test('renders with default div element', async () => {
2325
const { container } = render(
2426
<ShikiHighlighter language="javascript" theme="github-light">
2527
{codeSample}
@@ -29,7 +31,10 @@ describe('ShikiHighlighter Component', () => {
2931
await waitFor(() => {
3032
const containerElement = getContainer(container);
3133
expect(containerElement).toBeInTheDocument();
32-
expect(containerElement?.tagName.toLowerCase()).toBe('pre');
34+
expect(containerElement?.tagName.toLowerCase()).toBe('div');
35+
expect(containerElement).toHaveClass('rs-root');
36+
expect(containerElement).toHaveClass('rs-default-styles');
37+
expect(containerElement).not.toHaveClass('relative');
3338
});
3439
});
3540

@@ -227,13 +232,13 @@ describe('ShikiHighlighter Component', () => {
227232
);
228233

229234
await waitFor(() => {
230-
const shikiContainer =
231-
container.querySelector('#shiki-container');
235+
const shikiContainer = getContainer(container);
232236
expect(shikiContainer).toBeInTheDocument();
233237

234238
// Should have a div with dangerouslySetInnerHTML
235239
const innerDiv = shikiContainer?.querySelector(':scope > div');
236240
expect(innerDiv).toBeInTheDocument();
241+
expect(innerDiv?.getAttribute('data-slot')).toBe('content');
237242

238243
// Should still render highlighted code
239244
expect(container.querySelector('pre')).toBeInTheDocument();
@@ -268,7 +273,7 @@ describe('ShikiHighlighter Component', () => {
268273
);
269274
};
270275

271-
test('forwards ref to the default container element (pre)', async () => {
276+
test('forwards ref to the default container element (div)', async () => {
272277
let refCurrent: HTMLElement | null = null;
273278

274279
render(
@@ -281,10 +286,8 @@ describe('ShikiHighlighter Component', () => {
281286

282287
await waitFor(() => {
283288
expect(refCurrent).not.toBeNull();
284-
expect(refCurrent?.tagName.toLowerCase()).toBe('pre');
285-
expect(refCurrent?.getAttribute('data-testid')).toBe(
286-
'shiki-container'
287-
);
289+
expect(refCurrent?.tagName.toLowerCase()).toBe('div');
290+
expect(refCurrent?.getAttribute('data-slot')).toBe('container');
288291
});
289292
});
290293

@@ -303,9 +306,7 @@ describe('ShikiHighlighter Component', () => {
303306
await waitFor(() => {
304307
expect(refCurrent).not.toBeNull();
305308
expect(refCurrent?.tagName.toLowerCase()).toBe('div');
306-
expect(refCurrent?.getAttribute('data-testid')).toBe(
307-
'shiki-container'
308-
);
309+
expect(refCurrent?.getAttribute('data-slot')).toBe('container');
309310
});
310311
});
311312

@@ -324,7 +325,7 @@ describe('ShikiHighlighter Component', () => {
324325
expect(refCurrent).not.toBeNull();
325326
expect(typeof refCurrent?.focus).toBe('function');
326327
expect(typeof refCurrent?.getBoundingClientRect).toBe('function');
327-
expect(refCurrent?.tagName.toLowerCase()).toBe('pre');
328+
expect(refCurrent?.tagName.toLowerCase()).toBe('div');
328329
});
329330
});
330331
});

0 commit comments

Comments
 (0)