@dr-ishaan/rehype-perfect-code-blocks 1.3.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/dist/astro.d.ts.map +1 -1
- package/dist/astro.js +17 -2
- package/dist/astro.js.map +1 -1
- package/dist/copy-script.d.ts +0 -0
- package/dist/copy-script.d.ts.map +0 -0
- package/dist/copy-script.js +0 -0
- package/dist/copy-script.js.map +0 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +0 -0
- package/dist/tokens.d.ts +62 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +168 -0
- package/dist/tokens.js.map +1 -0
- package/dist/transformer.d.ts.map +1 -1
- package/dist/transformer.js +5 -0
- package/dist/transformer.js.map +1 -1
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/astro.ts +20 -2
- package/src/copy-script.ts +0 -0
- package/src/index.ts +10 -1
- package/src/styles.css +0 -0
- package/src/tokens.ts +223 -0
- package/src/transformer.ts +5 -0
- package/src/types.ts +157 -0
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design-token bridge — derives 20+ --pcb-* variables from 5 core values.
|
|
3
|
+
*
|
|
4
|
+
* v2.0.0 feature. Uses `color-mix(in oklch, ...)` for automatic color
|
|
5
|
+
* derivation, which is supported in all modern browsers (Chrome 111+,
|
|
6
|
+
* Safari 16.4+, Firefox 113+). For older browsers, the fallback values
|
|
7
|
+
* from the Shiki theme (Pattern 2, v1.3.0) still apply on the <pre> element.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface DesignTokens {
|
|
11
|
+
bg?: string;
|
|
12
|
+
fg?: string;
|
|
13
|
+
border?: string;
|
|
14
|
+
radius?: string;
|
|
15
|
+
monoFont?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate a CSS style string containing all derived --pcb-* variables
|
|
20
|
+
* from the 5 core token values. Returns an empty string if no tokens
|
|
21
|
+
* are provided.
|
|
22
|
+
*
|
|
23
|
+
* The generated CSS is applied to `:where(.pcb)` so it has zero
|
|
24
|
+
* specificity — user CSS always wins.
|
|
25
|
+
*/
|
|
26
|
+
export function generateTokenStyles(tokens: DesignTokens, scope?: string): string {
|
|
27
|
+
if (!tokens || Object.keys(tokens).length === 0) return '';
|
|
28
|
+
|
|
29
|
+
const { bg, fg, border, radius, monoFont } = tokens;
|
|
30
|
+
if (!bg && !fg && !border && !radius && !monoFont) return '';
|
|
31
|
+
|
|
32
|
+
const scopePrefix = scope ? `${scope} ` : '';
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
|
|
35
|
+
// Direct mappings (1:1)
|
|
36
|
+
if (bg) lines.push(` --pcb-bg: ${bg};`);
|
|
37
|
+
if (fg) lines.push(` --pcb-fg: ${fg};`);
|
|
38
|
+
if (border) lines.push(` --pcb-border: ${border};`);
|
|
39
|
+
if (radius) lines.push(` --pcb-radius: ${radius};`);
|
|
40
|
+
if (monoFont) lines.push(` --pcb-font-mono: ${monoFont};`);
|
|
41
|
+
|
|
42
|
+
// Derived mappings (auto-computed from core values via color-mix)
|
|
43
|
+
// Only derive if BOTH bg and fg are available (needed for color-mix)
|
|
44
|
+
if (bg && fg) {
|
|
45
|
+
// Line numbers: muted (50% mix of fg over bg)
|
|
46
|
+
lines.push(` --pcb-text-muted: color-mix(in oklch, ${fg}, ${bg} 50%);`);
|
|
47
|
+
lines.push(` --pcb-ln-fg: color-mix(in oklch, ${fg}, ${bg} 50%);`);
|
|
48
|
+
|
|
49
|
+
// Header bar background: slightly different from body (5% fg over bg)
|
|
50
|
+
lines.push(` --pcb-bg-header: color-mix(in oklch, ${fg} 5%, ${bg});`);
|
|
51
|
+
|
|
52
|
+
// Header text: muted (30% fg mixed toward bg)
|
|
53
|
+
lines.push(` --pcb-text-bar: color-mix(in oklch, ${fg}, ${bg} 30%);`);
|
|
54
|
+
|
|
55
|
+
// Line highlight: subtle tint (12% fg over bg)
|
|
56
|
+
lines.push(` --pcb-line-highlight: color-mix(in oklch, ${fg} 12%, ${bg});`);
|
|
57
|
+
|
|
58
|
+
// Diff add: green tint (18% green over bg)
|
|
59
|
+
lines.push(` --pcb-line-add: color-mix(in oklch, #2ea043 18%, ${bg});`);
|
|
60
|
+
|
|
61
|
+
// Diff del: red tint (18% red over bg)
|
|
62
|
+
lines.push(` --pcb-line-del: color-mix(in oklch, #f85149 18%, ${bg});`);
|
|
63
|
+
|
|
64
|
+
// Focus: blue tint (18% blue over bg)
|
|
65
|
+
lines.push(` --pcb-line-focus: color-mix(in oklch, #58a6ff 18%, ${bg});`);
|
|
66
|
+
|
|
67
|
+
// Copy button hover background: subtle (8% fg over bg)
|
|
68
|
+
lines.push(` --pcb-copy-hover-bg: color-mix(in oklch, ${fg} 8%, ${bg});`);
|
|
69
|
+
|
|
70
|
+
// Gutter background: same as bg by default
|
|
71
|
+
lines.push(` --pcb-bg-gutter: ${bg};`);
|
|
72
|
+
|
|
73
|
+
// Caption background: slightly different (like header)
|
|
74
|
+
lines.push(` --pcb-caption-bg: color-mix(in oklch, ${fg} 5%, ${bg});`);
|
|
75
|
+
|
|
76
|
+
// Caption color: muted (like header text)
|
|
77
|
+
lines.push(` --pcb-caption-color: color-mix(in oklch, ${fg}, ${bg} 30%);`);
|
|
78
|
+
|
|
79
|
+
// Word highlight: gold tint (30% gold over bg)
|
|
80
|
+
lines.push(` --pcb-word-bg: color-mix(in oklch, #bb8009 30%, ${bg});`);
|
|
81
|
+
|
|
82
|
+
// Word highlight (id): blue tint (30% blue over bg)
|
|
83
|
+
lines.push(` --pcb-word-bg-id: color-mix(in oklch, #58a6ff 30%, ${bg});`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (lines.length === 0) return '';
|
|
87
|
+
|
|
88
|
+
return `:where(${scopePrefix}.pcb) {\n${lines.join('\n')}\n}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generate the dark-mode CSS selector based on the user's dark mode strategy.
|
|
93
|
+
*
|
|
94
|
+
* Returns the selector prefix that should be placed before `.pcb` in CSS rules.
|
|
95
|
+
* For 'media' strategy, returns empty string (the rules are wrapped in @media).
|
|
96
|
+
*/
|
|
97
|
+
export function generateDarkModeSelector(
|
|
98
|
+
darkMode?: {
|
|
99
|
+
strategy?: 'media' | 'attribute' | 'class' | 'custom';
|
|
100
|
+
attribute?: string;
|
|
101
|
+
attributeValue?: string;
|
|
102
|
+
class?: string;
|
|
103
|
+
customSelector?: string;
|
|
104
|
+
},
|
|
105
|
+
scope?: string
|
|
106
|
+
): { selector: string; mediaQuery: string | null } {
|
|
107
|
+
const scopePrefix = scope ? `${scope} ` : '';
|
|
108
|
+
|
|
109
|
+
if (!darkMode || darkMode.strategy === 'media' || !darkMode.strategy) {
|
|
110
|
+
// Default: prefers-color-scheme media query
|
|
111
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: dark' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (darkMode.strategy === 'attribute') {
|
|
115
|
+
const attr = darkMode.attribute ?? 'data-theme';
|
|
116
|
+
const val = darkMode.attributeValue ?? 'dark';
|
|
117
|
+
return {
|
|
118
|
+
selector: `html[${attr}="${val}"] ${scopePrefix}`.trim(),
|
|
119
|
+
mediaQuery: null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (darkMode.strategy === 'class') {
|
|
124
|
+
const cls = darkMode.class ?? 'dark';
|
|
125
|
+
return {
|
|
126
|
+
selector: `html.${cls} ${scopePrefix}`.trim(),
|
|
127
|
+
mediaQuery: null,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (darkMode.strategy === 'custom') {
|
|
132
|
+
const sel = darkMode.customSelector ?? ':root';
|
|
133
|
+
return {
|
|
134
|
+
selector: `${sel} ${scopePrefix}`.trim(),
|
|
135
|
+
mediaQuery: null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: dark' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Generate the light-mode CSS selector (the inverse of dark mode).
|
|
144
|
+
* When dark mode is NOT active, light mode defaults apply.
|
|
145
|
+
*/
|
|
146
|
+
export function generateLightModeSelector(
|
|
147
|
+
darkMode?: {
|
|
148
|
+
strategy?: 'media' | 'attribute' | 'class' | 'custom';
|
|
149
|
+
attribute?: string;
|
|
150
|
+
attributeValue?: string;
|
|
151
|
+
class?: string;
|
|
152
|
+
customSelector?: string;
|
|
153
|
+
},
|
|
154
|
+
scope?: string
|
|
155
|
+
): { selector: string; mediaQuery: string | null } {
|
|
156
|
+
const scopePrefix = scope ? `${scope} ` : '';
|
|
157
|
+
|
|
158
|
+
if (!darkMode || darkMode.strategy === 'media' || !darkMode.strategy) {
|
|
159
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: light' };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (darkMode.strategy === 'attribute') {
|
|
163
|
+
const attr = darkMode.attribute ?? 'data-theme';
|
|
164
|
+
const val = darkMode.attributeValue ?? 'dark';
|
|
165
|
+
// Light = when the attribute is NOT the dark value
|
|
166
|
+
return {
|
|
167
|
+
selector: `html:not([${attr}="${val}"]) ${scopePrefix}`.trim(),
|
|
168
|
+
mediaQuery: null,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (darkMode.strategy === 'class') {
|
|
173
|
+
const cls = darkMode.class ?? 'dark';
|
|
174
|
+
return {
|
|
175
|
+
selector: `html:not(.${cls}) ${scopePrefix}`.trim(),
|
|
176
|
+
mediaQuery: null,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (darkMode.strategy === 'custom') {
|
|
181
|
+
// For custom, light = when the custom selector does NOT match
|
|
182
|
+
// This is tricky — we can't easily negate an arbitrary selector.
|
|
183
|
+
// Fall back to media query for the light case.
|
|
184
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: light' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: light' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Apply a scope prefix to all CSS selectors in a stylesheet.
|
|
192
|
+
* This is a simple regex-based approach that handles the plugin's CSS
|
|
193
|
+
* structure. It prefixes every selector that starts with `.pcb` or
|
|
194
|
+
* `:where(.pcb` or `html` with the scope.
|
|
195
|
+
*/
|
|
196
|
+
export function applyScopeToCss(css: string, scope: string): string {
|
|
197
|
+
if (!scope) return css;
|
|
198
|
+
|
|
199
|
+
// Don't double-prefix if the scope is already present
|
|
200
|
+
if (css.includes(`${scope} .pcb`) || css.includes(`${scope}.pcb`)) return css;
|
|
201
|
+
|
|
202
|
+
// Prefix selectors that target .pcb or html.no-js
|
|
203
|
+
let result = css;
|
|
204
|
+
|
|
205
|
+
// :where(.pcb) → :where(scope .pcb)
|
|
206
|
+
result = result.replace(/:where\(\.pcb\)/g, `:where(${scope} .pcb)`);
|
|
207
|
+
|
|
208
|
+
// :where(html:not(...)) :where(.pcb:not(...)) → :where(scope html:not(...)) :where(scope .pcb:not(...))
|
|
209
|
+
// This is complex — for nested :where() with html, just prefix the whole compound
|
|
210
|
+
result = result.replace(/:where\(html/g, `:where(${scope} html`);
|
|
211
|
+
|
|
212
|
+
// .pcb pre → scope .pcb pre (the framework-reset overrides)
|
|
213
|
+
result = result.replace(/^(\.pcb\s)/gm, `${scope} $1`);
|
|
214
|
+
|
|
215
|
+
// .pcb__copy → scope .pcb__copy (note: .pcb__ starts with a dot, don't add another)
|
|
216
|
+
result = result.replace(/^(\.pcb__)/gm, `${scope} $1`);
|
|
217
|
+
|
|
218
|
+
// html.no-js .pcb__copy → scope html.no-js scope .pcb__copy
|
|
219
|
+
// (This is an edge case — the no-js rule needs both html and .pcb scoped)
|
|
220
|
+
result = result.replace(/html\.no-js\s+\.pcb/g, `${scope} html.no-js ${scope} .pcb`);
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
package/src/transformer.ts
CHANGED
|
@@ -242,6 +242,11 @@ export function rehypePerfectCodeBlocks(userOptions: PerfectCodeOptions = {}) {
|
|
|
242
242
|
preset: 'default',
|
|
243
243
|
injectStyles: true,
|
|
244
244
|
theme: 'auto',
|
|
245
|
+
cssInjection: 'inline' as const,
|
|
246
|
+
cssLayer: 'pcb',
|
|
247
|
+
tokens: undefined as unknown as NonNullable<PerfectCodeOptions['tokens']>,
|
|
248
|
+
darkMode: undefined as unknown as NonNullable<PerfectCodeOptions['darkMode']>,
|
|
249
|
+
scope: undefined as unknown as string,
|
|
245
250
|
inline: false,
|
|
246
251
|
...rest,
|
|
247
252
|
};
|
package/src/types.ts
CHANGED
|
@@ -334,6 +334,163 @@ export interface PerfectCodeOptions {
|
|
|
334
334
|
/** Manual theme override. Default: 'auto' (prefers-color-scheme) */
|
|
335
335
|
theme?: 'auto' | 'dark' | 'light';
|
|
336
336
|
|
|
337
|
+
/* ---------- v2.0.0: CSS Architecture ---------- */
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* CSS injection strategy. Controls how the plugin's stylesheet is delivered.
|
|
341
|
+
*
|
|
342
|
+
* - `'inline'` (default) — injects the full CSS in a `<style>` tag on every page.
|
|
343
|
+
* Simple, works everywhere, but the CSS is unlayered (can be overridden by
|
|
344
|
+
* framework resets on cascade ties).
|
|
345
|
+
*
|
|
346
|
+
* - `'layer'` — injects the CSS wrapped in `@layer <cssLayer> { ... }`.
|
|
347
|
+
* On sites using cascade layers (Tailwind v3+, daisyUI, etc.), this ensures
|
|
348
|
+
* the plugin's CSS sits in the correct layer and framework resets don't
|
|
349
|
+
* win on specificity ties. Requires `cssLayer` to be set.
|
|
350
|
+
*
|
|
351
|
+
* - `'import'` — does NOT inject CSS. The user imports it manually:
|
|
352
|
+
* `import '@dr-ishaan/rehype-perfect-code-blocks/styles.css'`
|
|
353
|
+
* Useful for sites that want full control over CSS loading (e.g., bundling
|
|
354
|
+
* with PostCSS, or importing into a specific `@layer` in their own CSS).
|
|
355
|
+
*
|
|
356
|
+
* Default: `'inline'` (backward-compatible with v1.x).
|
|
357
|
+
*/
|
|
358
|
+
cssInjection?: 'inline' | 'layer' | 'import';
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* The cascade layer name to use when `cssInjection: 'layer'`.
|
|
362
|
+
* The plugin's CSS will be wrapped in `@layer <cssLayer> { ... }`.
|
|
363
|
+
*
|
|
364
|
+
* Example: `cssLayer: 'components'` → all plugin CSS goes into
|
|
365
|
+
* `@layer components { ... }`, which the user can order above
|
|
366
|
+
* `@layer base` (framework resets) in their CSS:
|
|
367
|
+
*
|
|
368
|
+
* ```css
|
|
369
|
+
* @layer base, components, utilities;
|
|
370
|
+
* ```
|
|
371
|
+
*
|
|
372
|
+
* Default: `'pcb'`
|
|
373
|
+
*/
|
|
374
|
+
cssLayer?: string;
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Design-token bridge — provide 5 core values and the plugin auto-derives
|
|
378
|
+
* all 20+ `--pcb-*` variables. This is a shortcut for mapping the plugin
|
|
379
|
+
* to an external design system (Tailwind theme, daisyUI theme, CSS custom
|
|
380
|
+
* properties, etc.) without manually overriding every variable.
|
|
381
|
+
*
|
|
382
|
+
* Each value can be a CSS value string (e.g., `'#1a1b26'`) or a
|
|
383
|
+
* `var(--your-var)` reference.
|
|
384
|
+
*
|
|
385
|
+
* When set, the plugin generates a `<style>` block with the derived
|
|
386
|
+
* `--pcb-*` variables applied to `:where(.pcb)` (zero specificity, so
|
|
387
|
+
* user CSS still wins). If a specific `--pcb-*` variable is ALSO set
|
|
388
|
+
* in user CSS, the user's value wins (the token bridge is a default,
|
|
389
|
+
* not an override).
|
|
390
|
+
*
|
|
391
|
+
* Example:
|
|
392
|
+
* ```js
|
|
393
|
+
* perfectCode({
|
|
394
|
+
* tokens: {
|
|
395
|
+
* bg: 'var(--bg-subtle)',
|
|
396
|
+
* fg: 'var(--ink)',
|
|
397
|
+
* border: 'var(--rule)',
|
|
398
|
+
* radius: 'var(--radius-card)',
|
|
399
|
+
* monoFont: 'var(--font-mono)',
|
|
400
|
+
* },
|
|
401
|
+
* })
|
|
402
|
+
* ```
|
|
403
|
+
*
|
|
404
|
+
* The plugin auto-derives:
|
|
405
|
+
* - `--pcb-bg` = `bg`
|
|
406
|
+
* - `--pcb-fg` = `fg`
|
|
407
|
+
* - `--pcb-border` = `border`
|
|
408
|
+
* - `--pcb-radius` = `radius`
|
|
409
|
+
* - `--pcb-font-mono` = `monoFont`
|
|
410
|
+
* - `--pcb-ln-fg` = `color-mix(in oklch, fg, bg 50%)` (muted line numbers)
|
|
411
|
+
* - `--pcb-bg-header` = `color-mix(in oklch, fg 5%, bg)` (header slightly different)
|
|
412
|
+
* - `--pcb-text-bar` = `color-mix(in oklch, fg, bg 30%)` (header text muted)
|
|
413
|
+
* - `--pcb-line-highlight` = `color-mix(in oklch, fg 12%, bg)` (subtle highlight)
|
|
414
|
+
* - `--pcb-line-add` = `color-mix(in oklch, #2ea043 18%, bg)` (diff add)
|
|
415
|
+
* - `--pcb-line-del` = `color-mix(in oklch, #f85149 18%, bg)` (diff del)
|
|
416
|
+
* - `--pcb-line-focus` = `color-mix(in oklch, #58a6ff 18%, bg)` (focus)
|
|
417
|
+
* - `--pcb-copy-bg` (hover) = `color-mix(in oklch, fg 8%, bg)`
|
|
418
|
+
*
|
|
419
|
+
* Uses `color-mix(in oklch, ...)` which is supported in all modern browsers
|
|
420
|
+
* (Chrome 111+, Safari 16.4+, Firefox 113+). For older browsers, the
|
|
421
|
+
* fallback values from the Shiki theme (Pattern 2, v1.3.0) still apply.
|
|
422
|
+
*
|
|
423
|
+
* Default: `undefined` (no token bridge; use the plugin's built-in defaults)
|
|
424
|
+
*/
|
|
425
|
+
tokens?: {
|
|
426
|
+
/** Background color of the code block surface. */
|
|
427
|
+
bg?: string;
|
|
428
|
+
/** Foreground (text) color of the code. */
|
|
429
|
+
fg?: string;
|
|
430
|
+
/** Border color of the code block frame. */
|
|
431
|
+
border?: string;
|
|
432
|
+
/** Border radius of the code block. */
|
|
433
|
+
radius?: string;
|
|
434
|
+
/** Monospace font family for code text. */
|
|
435
|
+
monoFont?: string;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Dark mode strategy — controls how the plugin switches between light
|
|
440
|
+
* and dark themes.
|
|
441
|
+
*
|
|
442
|
+
* - `'media'` (default) — uses `@media (prefers-color-scheme: dark)`.
|
|
443
|
+
* Automatic, no configuration needed. Same as v1.x behavior.
|
|
444
|
+
*
|
|
445
|
+
* - `'attribute'` — switches based on an attribute on `<html>`.
|
|
446
|
+
* Requires `darkMode.attribute` and `darkMode.attributeValue`.
|
|
447
|
+
* Example: `darkMode: { strategy: 'attribute', attribute: 'data-theme', attributeValue: 'dark' }`
|
|
448
|
+
* → CSS: `html[data-theme="dark"] .pcb { ... }`
|
|
449
|
+
*
|
|
450
|
+
* - `'class'` — switches based on a class on `<html>`.
|
|
451
|
+
* Requires `darkMode.class`.
|
|
452
|
+
* Example: `darkMode: { strategy: 'class', class: 'dark' }`
|
|
453
|
+
* → CSS: `html.dark .pcb { ... }`
|
|
454
|
+
*
|
|
455
|
+
* - `'custom'` — uses a custom CSS selector.
|
|
456
|
+
* Requires `darkMode.customSelector`.
|
|
457
|
+
* Example: `darkMode: { strategy: 'custom', customSelector: ':root[data-mode="night"]' }`
|
|
458
|
+
* → CSS: `:root[data-mode="night"] .pcb { ... }`
|
|
459
|
+
*
|
|
460
|
+
* When using Shiki dual themes (`shiki.theme: { light, dark }`), the
|
|
461
|
+
* Shiki token CSS variables (`--shiki-light` / `--shiki-dark`) are also
|
|
462
|
+
* switched using the same strategy, not just `prefers-color-scheme`.
|
|
463
|
+
*
|
|
464
|
+
* Default: `'media'` (backward-compatible with v1.x)
|
|
465
|
+
*/
|
|
466
|
+
darkMode?: {
|
|
467
|
+
strategy: 'media' | 'attribute' | 'class' | 'custom';
|
|
468
|
+
/** For `strategy: 'attribute'` — the attribute name on `<html>`. Default: `'data-theme'` */
|
|
469
|
+
attribute?: string;
|
|
470
|
+
/** For `strategy: 'attribute'` — the attribute value that triggers dark mode. Default: `'dark'` */
|
|
471
|
+
attributeValue?: string;
|
|
472
|
+
/** For `strategy: 'class'` — the class name on `<html>` that triggers dark mode. Default: `'dark'` */
|
|
473
|
+
class?: string;
|
|
474
|
+
/** For `strategy: 'custom'` — the full CSS selector that triggers dark mode. */
|
|
475
|
+
customSelector?: string;
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* CSS containment scope — prefix all generated CSS selectors with this
|
|
480
|
+
* string. Useful when code blocks appear in specific contexts (e.g., only
|
|
481
|
+
* inside `.prose` or `article`) and you don't want the plugin's CSS to
|
|
482
|
+
* affect code blocks elsewhere on the page.
|
|
483
|
+
*
|
|
484
|
+
* Example: `scope: '.prose'` → all selectors become `.prose .pcb { ... }`,
|
|
485
|
+
* `.prose .pcb__header { ... }`, etc.
|
|
486
|
+
*
|
|
487
|
+
* The scope is applied to ALL generated CSS, including the framework-reset
|
|
488
|
+
* overrides and the dark-mode selectors.
|
|
489
|
+
*
|
|
490
|
+
* Default: `undefined` (no scoping; CSS applies everywhere)
|
|
491
|
+
*/
|
|
492
|
+
scope?: string;
|
|
493
|
+
|
|
337
494
|
/* ---------- Inline code (legacy cosmetic option) ---------- */
|
|
338
495
|
/** Also style inline `code` cosmetically (no tokenization). Default: false */
|
|
339
496
|
inline?: boolean;
|