@particle-academy/react-fancy 6.4.0 → 6.6.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 +29 -0
- package/dist/CodeBlock.types-BFMFB4X2.d.cts +53 -0
- package/dist/CodeBlock.types-BFMFB4X2.d.ts +53 -0
- package/dist/chunk-2X54BVXG.js +58 -0
- package/dist/chunk-2X54BVXG.js.map +1 -0
- package/dist/chunk-5WXY3Q6X.cjs +63 -0
- package/dist/chunk-5WXY3Q6X.cjs.map +1 -0
- package/dist/{chunk-AHZIT3CM.js → chunk-MI57NAKF.js} +24 -22
- package/dist/chunk-MI57NAKF.js.map +1 -0
- package/dist/{chunk-VAVS6WRZ.cjs → chunk-PM5I5ZHK.cjs} +23 -21
- package/dist/chunk-PM5I5ZHK.cjs.map +1 -0
- package/dist/{chunk-P24A2BIR.js → chunk-TFX4LL7V.js} +17 -12
- package/dist/chunk-TFX4LL7V.js.map +1 -0
- package/dist/{chunk-VQLPAYQF.cjs → chunk-THMFLRDW.cjs} +4 -4
- package/dist/{chunk-VQLPAYQF.cjs.map → chunk-THMFLRDW.cjs.map} +1 -1
- package/dist/{chunk-UMDOSJUH.js → chunk-VSTUGU7W.js} +3 -3
- package/dist/{chunk-UMDOSJUH.js.map → chunk-VSTUGU7W.js.map} +1 -1
- package/dist/{chunk-NIAG6OF6.cjs → chunk-YQZB5Z7C.cjs} +16 -11
- package/dist/chunk-YQZB5Z7C.cjs.map +1 -0
- package/dist/code-block.cjs +3 -3
- package/dist/code-block.d.cts +3 -45
- package/dist/code-block.d.ts +3 -45
- package/dist/code-block.js +2 -2
- package/dist/code-view.cjs +3 -3
- package/dist/code-view.d.cts +9 -6
- package/dist/code-view.d.ts +9 -6
- package/dist/code-view.js +2 -2
- package/dist/editor.cjs +5 -5
- package/dist/editor.js +3 -3
- package/dist/index.cjs +8 -8
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -4
- package/dist/styles.css +20 -0
- package/dist/styles.css.map +1 -1
- package/docs/CodeBlock.md +30 -5
- package/docs/CodeView.md +28 -8
- package/package.json +2 -2
- package/dist/chunk-6BB32POL.js +0 -29
- package/dist/chunk-6BB32POL.js.map +0 -1
- package/dist/chunk-AHZIT3CM.js.map +0 -1
- package/dist/chunk-HZUKPYR3.cjs +0 -31
- package/dist/chunk-HZUKPYR3.cjs.map +0 -1
- package/dist/chunk-NIAG6OF6.cjs.map +0 -1
- package/dist/chunk-P24A2BIR.js.map +0 -1
- package/dist/chunk-VAVS6WRZ.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [6.6.0] - 2026-11-02
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **`CodeBlock` and `CodeView` no longer flash light on a dark site.** They picked one palette with a client-only hook, so server-rendered HTML always carried the light colours until hydration. Now the root carries BOTH palettes as CSS variables (`--fc-<slot>-l` / `--fc-<slot>-d`), every colour reads `var(--fc-<slot>, var(--fc-<slot>-l))`, and CSS switches to dark: the root's `dark:[--fc-<slot>:var(--fc-<slot>-d)]` classes (compiled by your Tailwind from the `@source`d dist, so they follow your `dark` variant, class or media) and a zero-specificity `styles.css` rule under `.dark` / `[data-theme="dark"]` for hosts that do not scan the package. Server and client markup are now identical in both themes. Verified in a real browser: dark under `.dark`, under `[data-theme="dark"]`, with only the Tailwind CSS, and with only `styles.css`; light with no CSS at all.
|
|
19
|
+
- **PHP snippets without `<?php` are highlighted.** `<CodeBlock language="php" code={'use Prism\\Prism\\Facades\\Prism;'} />` rendered as one plain span, because PHP files are inline HTML until their open tag. Both components now tokenize with `fancy-file-commons`' snippet mode: no open tag means PHP, and a `?>` switches to HTML. Code that starts with `<?php` highlights exactly as before. Reported by Prism.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Token spans read CSS variables instead of carrying hex colours** (`style="color:var(--fc-keyword,var(--fc-keyword-l))"`), and the root's `background-color` / `color` / `border-color` are `var()` references too. **Consumer action:** none for rendering. If a test asserted a literal colour on a `CodeBlock` / `CodeView` element (e.g. `style.backgroundColor === "rgb(24, 24, 27)"` under `.dark`), assert the `--fc-*` variables instead; jsdom does not resolve `var()`. To recolour a slot, set its variables on the component: `style={{ "--fc-keyword-l": "...", "--fc-keyword-d": "..." }}`.
|
|
24
|
+
- **The internal `useIsDark` hook is removed.** It was never exported. Nothing in either component reads `matchMedia` or the document's class any more.
|
|
25
|
+
- **Requires `@particle-academy/fancy-file-commons` `>=0.5`** (still `<2.0`), the first release with `highlightCodeWithVariables`, `themeVariables` and `tokenize`'s `snippet` option. npm installs it for you. **Consumer action:** none. *(Release step, not yet applied: raise the `dependencies` range from `>=0.4` together with the lockfile once 0.5.0 is on the registry.)*
|
|
26
|
+
|
|
27
|
+
## [6.5.0] - 2026-11-02
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`CodeBlock` and `CodeView` highlight ten languages, not just HTML.** TypeScript (`ts`, `tsx`), JavaScript (`js`, `jsx`), JSON (`json`), PHP (`php`), Python (`py`), Go (`go`), Markdown (`md`), shell (`sh`, `bash`, `zsh`, `shell`), dotenv (`env`, `dotenv`) and HTML (`html`, `htm`, `xml`, `xhtml`), case-insensitively, through `@particle-academy/fancy-file-commons`' new `tokenize(code, language)` lookup. The grammars are the ones `fancy-code`'s `CodeEditor` uses, now shared from commons, so a snippet highlights the same way in both. Unknown languages still render as plain text. A new `CodeLanguage` type lists the known ids and still accepts any string.
|
|
32
|
+
- **No new colours.** Both components keep the existing light/dark palette; shell flags and dotenv keys use the `attribute` colour, shell command words `function`, and `$VAR` / `${VAR}` `variable`.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **A `CodeBlock` or `CodeView` whose `language` is now recognised gains colour spans** where it used to render plain text: `bash`, `json`, `env`, `typescript`, `markdown` and the rest above. The text, its escaping and the copy behaviour are unchanged; only `<span style="color:...">` wrappers are added. **Consumer action:** none, unless a test asserted that such a block had no spans. The `Editor`'s Source view is unchanged: it passes `"html"` or `"plaintext"`.
|
|
37
|
+
- **The `@particle-academy/fancy-file-commons` range floor rose from `>=0.2` to `>=0.4`** (still `<2.0`): 0.4 is the first release with these grammars and `tokenize`. npm installs it for you. **Consumer action:** none.
|
|
38
|
+
|
|
39
|
+
### Security
|
|
40
|
+
|
|
41
|
+
- **The escaping guarantee is now tested in every language.** The `<script>` / `<img onerror>` payload test runs across all ten grammars plus plain text, in `CodeBlock` with and without line numbers and in `CodeView`; the code always renders as text and nothing executes.
|
|
42
|
+
|
|
14
43
|
## [6.4.0] - 2026-11-01
|
|
15
44
|
|
|
16
45
|
### Added
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Language ids `CodeBlock` and `CodeView` highlight (aliases included). Any
|
|
5
|
+
* other string is accepted and renders as plain text.
|
|
6
|
+
*/
|
|
7
|
+
type CodeLanguage = "typescript" | "ts" | "tsx" | "javascript" | "js" | "jsx" | "json" | "php" | "python" | "py" | "go" | "markdown" | "md" | "shell" | "sh" | "bash" | "zsh" | "dotenv" | "env" | "html" | "htm" | "xml" | "xhtml" | "plaintext" | (string & {});
|
|
8
|
+
/** Words on the copy button, for translation. */
|
|
9
|
+
interface CodeBlockCopyLabels {
|
|
10
|
+
/** Idle label (aria-label and tooltip). @default "Copy code" */
|
|
11
|
+
copy?: string;
|
|
12
|
+
/** Shown, visibly and to screen readers, after a successful copy. @default "Copied" */
|
|
13
|
+
copied?: string;
|
|
14
|
+
/** Shown after a copy that could not be written. @default "Copy failed" */
|
|
15
|
+
failed?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Reported through `onCopyCode` after every copy attempt. */
|
|
18
|
+
interface CodeBlockCopyEvent {
|
|
19
|
+
/** Exactly the `code` prop: the raw source, never the highlighted HTML. */
|
|
20
|
+
text: string;
|
|
21
|
+
/** Whether the clipboard accepted it. */
|
|
22
|
+
ok: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> {
|
|
25
|
+
/** The source to show. Rendered as text; never parsed as HTML. */
|
|
26
|
+
code: string;
|
|
27
|
+
/**
|
|
28
|
+
* Language id or alias, case-insensitive. Highlighted: TypeScript (`ts`,
|
|
29
|
+
* `tsx`), JavaScript (`js`, `jsx`), JSON (`json`), PHP (`php`), Python
|
|
30
|
+
* (`py`), Go (`go`), Markdown (`md`), shell (`sh`, `bash`, `zsh`, `shell`),
|
|
31
|
+
* dotenv (`env`, `dotenv`) and HTML (`html`, `htm`, `xml`, `xhtml`), via
|
|
32
|
+
* `fancy-file-commons`' `tokenize`. Any other value renders as plain text.
|
|
33
|
+
* Reflected as `data-language` either way.
|
|
34
|
+
* @default "plaintext"
|
|
35
|
+
*/
|
|
36
|
+
language?: CodeLanguage;
|
|
37
|
+
/** Show a copy button. It copies `code` exactly. @default true */
|
|
38
|
+
copyable?: boolean;
|
|
39
|
+
/** Header above the code: a filename, or a short label like "Terminal". */
|
|
40
|
+
title?: ReactNode;
|
|
41
|
+
/** Number each line in a gutter that is not part of the code text. @default false */
|
|
42
|
+
showLineNumbers?: boolean;
|
|
43
|
+
/** Wrap long lines instead of scrolling horizontally. @default false */
|
|
44
|
+
wrap?: boolean;
|
|
45
|
+
/** Words on the copy button. */
|
|
46
|
+
copyLabels?: CodeBlockCopyLabels;
|
|
47
|
+
/** Called after every copy attempt, successful or not. */
|
|
48
|
+
onCopyCode?: (event: CodeBlockCopyEvent) => void;
|
|
49
|
+
/** Additional classes on the root element. */
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type { CodeBlockCopyEvent as C, CodeBlockCopyLabels as a, CodeBlockProps as b, CodeLanguage as c };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Language ids `CodeBlock` and `CodeView` highlight (aliases included). Any
|
|
5
|
+
* other string is accepted and renders as plain text.
|
|
6
|
+
*/
|
|
7
|
+
type CodeLanguage = "typescript" | "ts" | "tsx" | "javascript" | "js" | "jsx" | "json" | "php" | "python" | "py" | "go" | "markdown" | "md" | "shell" | "sh" | "bash" | "zsh" | "dotenv" | "env" | "html" | "htm" | "xml" | "xhtml" | "plaintext" | (string & {});
|
|
8
|
+
/** Words on the copy button, for translation. */
|
|
9
|
+
interface CodeBlockCopyLabels {
|
|
10
|
+
/** Idle label (aria-label and tooltip). @default "Copy code" */
|
|
11
|
+
copy?: string;
|
|
12
|
+
/** Shown, visibly and to screen readers, after a successful copy. @default "Copied" */
|
|
13
|
+
copied?: string;
|
|
14
|
+
/** Shown after a copy that could not be written. @default "Copy failed" */
|
|
15
|
+
failed?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Reported through `onCopyCode` after every copy attempt. */
|
|
18
|
+
interface CodeBlockCopyEvent {
|
|
19
|
+
/** Exactly the `code` prop: the raw source, never the highlighted HTML. */
|
|
20
|
+
text: string;
|
|
21
|
+
/** Whether the clipboard accepted it. */
|
|
22
|
+
ok: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> {
|
|
25
|
+
/** The source to show. Rendered as text; never parsed as HTML. */
|
|
26
|
+
code: string;
|
|
27
|
+
/**
|
|
28
|
+
* Language id or alias, case-insensitive. Highlighted: TypeScript (`ts`,
|
|
29
|
+
* `tsx`), JavaScript (`js`, `jsx`), JSON (`json`), PHP (`php`), Python
|
|
30
|
+
* (`py`), Go (`go`), Markdown (`md`), shell (`sh`, `bash`, `zsh`, `shell`),
|
|
31
|
+
* dotenv (`env`, `dotenv`) and HTML (`html`, `htm`, `xml`, `xhtml`), via
|
|
32
|
+
* `fancy-file-commons`' `tokenize`. Any other value renders as plain text.
|
|
33
|
+
* Reflected as `data-language` either way.
|
|
34
|
+
* @default "plaintext"
|
|
35
|
+
*/
|
|
36
|
+
language?: CodeLanguage;
|
|
37
|
+
/** Show a copy button. It copies `code` exactly. @default true */
|
|
38
|
+
copyable?: boolean;
|
|
39
|
+
/** Header above the code: a filename, or a short label like "Terminal". */
|
|
40
|
+
title?: ReactNode;
|
|
41
|
+
/** Number each line in a gutter that is not part of the code text. @default false */
|
|
42
|
+
showLineNumbers?: boolean;
|
|
43
|
+
/** Wrap long lines instead of scrolling horizontally. @default false */
|
|
44
|
+
wrap?: boolean;
|
|
45
|
+
/** Words on the copy button. */
|
|
46
|
+
copyLabels?: CodeBlockCopyLabels;
|
|
47
|
+
/** Called after every copy attempt, successful or not. */
|
|
48
|
+
onCopyCode?: (event: CodeBlockCopyEvent) => void;
|
|
49
|
+
/** Additional classes on the root element. */
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type { CodeBlockCopyEvent as C, CodeBlockCopyLabels as a, CodeBlockProps as b, CodeLanguage as c };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { themeVariables, LIGHT_COLORS, DARK_COLORS, themeColorVar } from '@particle-academy/fancy-file-commons';
|
|
2
|
+
|
|
3
|
+
// src/components/CodeView/code-palette.ts
|
|
4
|
+
var CODE_PALETTE_KEYS = [
|
|
5
|
+
"background",
|
|
6
|
+
"foreground",
|
|
7
|
+
"gutterBackground",
|
|
8
|
+
"gutterForeground",
|
|
9
|
+
"gutterBorder",
|
|
10
|
+
"cursorColor",
|
|
11
|
+
"keyword",
|
|
12
|
+
"string",
|
|
13
|
+
"comment",
|
|
14
|
+
"number",
|
|
15
|
+
"operator",
|
|
16
|
+
"function",
|
|
17
|
+
"type",
|
|
18
|
+
"tag",
|
|
19
|
+
"attribute",
|
|
20
|
+
"attributeValue",
|
|
21
|
+
"punctuation",
|
|
22
|
+
"variable"
|
|
23
|
+
];
|
|
24
|
+
var CODE_PALETTE_DARK_CLASS = [
|
|
25
|
+
"dark:[--fc-background:var(--fc-background-d)]",
|
|
26
|
+
"dark:[--fc-foreground:var(--fc-foreground-d)]",
|
|
27
|
+
"dark:[--fc-gutterBackground:var(--fc-gutterBackground-d)]",
|
|
28
|
+
"dark:[--fc-gutterForeground:var(--fc-gutterForeground-d)]",
|
|
29
|
+
"dark:[--fc-gutterBorder:var(--fc-gutterBorder-d)]",
|
|
30
|
+
"dark:[--fc-cursorColor:var(--fc-cursorColor-d)]",
|
|
31
|
+
"dark:[--fc-keyword:var(--fc-keyword-d)]",
|
|
32
|
+
"dark:[--fc-string:var(--fc-string-d)]",
|
|
33
|
+
"dark:[--fc-comment:var(--fc-comment-d)]",
|
|
34
|
+
"dark:[--fc-number:var(--fc-number-d)]",
|
|
35
|
+
"dark:[--fc-operator:var(--fc-operator-d)]",
|
|
36
|
+
"dark:[--fc-function:var(--fc-function-d)]",
|
|
37
|
+
"dark:[--fc-type:var(--fc-type-d)]",
|
|
38
|
+
"dark:[--fc-tag:var(--fc-tag-d)]",
|
|
39
|
+
"dark:[--fc-attribute:var(--fc-attribute-d)]",
|
|
40
|
+
"dark:[--fc-attributeValue:var(--fc-attributeValue-d)]",
|
|
41
|
+
"dark:[--fc-punctuation:var(--fc-punctuation-d)]",
|
|
42
|
+
"dark:[--fc-variable:var(--fc-variable-d)]"
|
|
43
|
+
].join(" ");
|
|
44
|
+
var CODE_PALETTE_ATTR = "data-react-fancy-code-palette";
|
|
45
|
+
var ALL_VARIABLES = themeVariables(LIGHT_COLORS, DARK_COLORS);
|
|
46
|
+
var CODE_PALETTE_STYLE = Object.fromEntries(
|
|
47
|
+
CODE_PALETTE_KEYS.flatMap((key) => [
|
|
48
|
+
[`--fc-${key}-l`, ALL_VARIABLES[`--fc-${key}-l`]],
|
|
49
|
+
[`--fc-${key}-d`, ALL_VARIABLES[`--fc-${key}-d`]]
|
|
50
|
+
])
|
|
51
|
+
);
|
|
52
|
+
function paletteColor(key) {
|
|
53
|
+
return themeColorVar(key);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { CODE_PALETTE_ATTR, CODE_PALETTE_DARK_CLASS, CODE_PALETTE_STYLE, paletteColor };
|
|
57
|
+
//# sourceMappingURL=chunk-2X54BVXG.js.map
|
|
58
|
+
//# sourceMappingURL=chunk-2X54BVXG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/CodeView/code-palette.ts"],"names":[],"mappings":";;;AA+BO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,YAAA;AAAA,EACA,YAAA;AAAA,EACA,kBAAA;AAAA,EACA,kBAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA;AAKO,IAAM,uBAAA,GAA0B;AAAA,EACrC,+CAAA;AAAA,EACA,+CAAA;AAAA,EACA,2DAAA;AAAA,EACA,2DAAA;AAAA,EACA,mDAAA;AAAA,EACA,iDAAA;AAAA,EACA,yCAAA;AAAA,EACA,uCAAA;AAAA,EACA,yCAAA;AAAA,EACA,uCAAA;AAAA,EACA,2CAAA;AAAA,EACA,2CAAA;AAAA,EACA,mCAAA;AAAA,EACA,iCAAA;AAAA,EACA,6CAAA;AAAA,EACA,uDAAA;AAAA,EACA,iDAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,GAAG;AAGH,IAAM,iBAAA,GAAoB;AAEjC,IAAM,aAAA,GAAgB,cAAA,CAAe,YAAA,EAAc,WAAW,CAAA;AAGvD,IAAM,qBAAoC,MAAA,CAAO,WAAA;AAAA,EACtD,iBAAA,CAAkB,OAAA,CAAQ,CAAC,GAAA,KAAQ;AAAA,IACjC,CAAC,QAAQ,GAAG,CAAA,EAAA,CAAA,EAAM,cAAc,CAAA,KAAA,EAAQ,GAAG,IAAI,CAAC,CAAA;AAAA,IAChD,CAAC,QAAQ,GAAG,CAAA,EAAA,CAAA,EAAM,cAAc,CAAA,KAAA,EAAQ,GAAG,IAAI,CAAC;AAAA,GACjD;AACH;AAGO,SAAS,aAAa,GAAA,EAA6B;AACxD,EAAA,OAAO,cAAc,GAAG,CAAA;AAC1B","file":"chunk-2X54BVXG.js","sourcesContent":["import type { CSSProperties } from \"react\";\nimport {\n themeColorVar,\n themeVariables,\n LIGHT_COLORS,\n DARK_COLORS,\n type ThemeColorKey,\n} from \"@particle-academy/fancy-file-commons\";\n\n// ── Both palettes, switched by CSS ──────────────────────────────────────────\n// CodeBlock and CodeView used to pick ONE palette with a client-only hook, so\n// server-rendered HTML always carried the light colours and a dark site flashed\n// light until hydration. Now the markup is the same in either theme:\n//\n// 1. the root declares `--fc-<key>-l` and `--fc-<key>-d` (both palettes, via\n// fancy-file-commons' `themeVariables`, which allow-lists every value);\n// 2. everything coloured reads `var(--fc-<key>,var(--fc-<key>-l))`, so it is\n// light unless `--fc-<key>` is set;\n// 3. `--fc-<key>` is set to the dark value by EITHER\n// - the `dark:` classes below, compiled by the consumer's Tailwind from\n// react-fancy's dist, so they follow the consumer's own `dark` variant\n// (class strategy, `dark-variant.css`, or the default media query), or\n// - the zero-specificity rule in `styles.css` keyed on\n// `[data-react-fancy-code-palette]` under `.dark` / `[data-theme=\"dark\"]`,\n// for a host whose Tailwind does not scan this package.\n// Together they cover what the old hook followed: `.dark` OR the media\n// query (the latter through Tailwind's default `dark` variant).\n//\n// The class strings MUST stay literal: Tailwind finds them by scanning dist.\n\n/** The palette slots CodeBlock and CodeView paint. */\nexport const CODE_PALETTE_KEYS = [\n \"background\",\n \"foreground\",\n \"gutterBackground\",\n \"gutterForeground\",\n \"gutterBorder\",\n \"cursorColor\",\n \"keyword\",\n \"string\",\n \"comment\",\n \"number\",\n \"operator\",\n \"function\",\n \"type\",\n \"tag\",\n \"attribute\",\n \"attributeValue\",\n \"punctuation\",\n \"variable\",\n] as const satisfies readonly ThemeColorKey[];\n\nexport type CodePaletteKey = (typeof CODE_PALETTE_KEYS)[number];\n\n/** One `dark:` class per key in `CODE_PALETTE_KEYS`; a test keeps them in step. */\nexport const CODE_PALETTE_DARK_CLASS = [\n \"dark:[--fc-background:var(--fc-background-d)]\",\n \"dark:[--fc-foreground:var(--fc-foreground-d)]\",\n \"dark:[--fc-gutterBackground:var(--fc-gutterBackground-d)]\",\n \"dark:[--fc-gutterForeground:var(--fc-gutterForeground-d)]\",\n \"dark:[--fc-gutterBorder:var(--fc-gutterBorder-d)]\",\n \"dark:[--fc-cursorColor:var(--fc-cursorColor-d)]\",\n \"dark:[--fc-keyword:var(--fc-keyword-d)]\",\n \"dark:[--fc-string:var(--fc-string-d)]\",\n \"dark:[--fc-comment:var(--fc-comment-d)]\",\n \"dark:[--fc-number:var(--fc-number-d)]\",\n \"dark:[--fc-operator:var(--fc-operator-d)]\",\n \"dark:[--fc-function:var(--fc-function-d)]\",\n \"dark:[--fc-type:var(--fc-type-d)]\",\n \"dark:[--fc-tag:var(--fc-tag-d)]\",\n \"dark:[--fc-attribute:var(--fc-attribute-d)]\",\n \"dark:[--fc-attributeValue:var(--fc-attributeValue-d)]\",\n \"dark:[--fc-punctuation:var(--fc-punctuation-d)]\",\n \"dark:[--fc-variable:var(--fc-variable-d)]\",\n].join(\" \");\n\n/** Marks the element that declares the palette; styles.css keys its dark rule on it. */\nexport const CODE_PALETTE_ATTR = \"data-react-fancy-code-palette\";\n\nconst ALL_VARIABLES = themeVariables(LIGHT_COLORS, DARK_COLORS);\n\n/** Both palettes as custom properties, for the root's `style`. */\nexport const CODE_PALETTE_STYLE: CSSProperties = Object.fromEntries(\n CODE_PALETTE_KEYS.flatMap((key) => [\n [`--fc-${key}-l`, ALL_VARIABLES[`--fc-${key}-l`]],\n [`--fc-${key}-d`, ALL_VARIABLES[`--fc-${key}-d`]],\n ]),\n) as CSSProperties;\n\n/** The CSS value for palette slot `key`: dark under the dark switch, light otherwise. */\nexport function paletteColor(key: CodePaletteKey): string {\n return themeColorVar(key);\n}\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fancyFileCommons = require('@particle-academy/fancy-file-commons');
|
|
4
|
+
|
|
5
|
+
// src/components/CodeView/code-palette.ts
|
|
6
|
+
var CODE_PALETTE_KEYS = [
|
|
7
|
+
"background",
|
|
8
|
+
"foreground",
|
|
9
|
+
"gutterBackground",
|
|
10
|
+
"gutterForeground",
|
|
11
|
+
"gutterBorder",
|
|
12
|
+
"cursorColor",
|
|
13
|
+
"keyword",
|
|
14
|
+
"string",
|
|
15
|
+
"comment",
|
|
16
|
+
"number",
|
|
17
|
+
"operator",
|
|
18
|
+
"function",
|
|
19
|
+
"type",
|
|
20
|
+
"tag",
|
|
21
|
+
"attribute",
|
|
22
|
+
"attributeValue",
|
|
23
|
+
"punctuation",
|
|
24
|
+
"variable"
|
|
25
|
+
];
|
|
26
|
+
var CODE_PALETTE_DARK_CLASS = [
|
|
27
|
+
"dark:[--fc-background:var(--fc-background-d)]",
|
|
28
|
+
"dark:[--fc-foreground:var(--fc-foreground-d)]",
|
|
29
|
+
"dark:[--fc-gutterBackground:var(--fc-gutterBackground-d)]",
|
|
30
|
+
"dark:[--fc-gutterForeground:var(--fc-gutterForeground-d)]",
|
|
31
|
+
"dark:[--fc-gutterBorder:var(--fc-gutterBorder-d)]",
|
|
32
|
+
"dark:[--fc-cursorColor:var(--fc-cursorColor-d)]",
|
|
33
|
+
"dark:[--fc-keyword:var(--fc-keyword-d)]",
|
|
34
|
+
"dark:[--fc-string:var(--fc-string-d)]",
|
|
35
|
+
"dark:[--fc-comment:var(--fc-comment-d)]",
|
|
36
|
+
"dark:[--fc-number:var(--fc-number-d)]",
|
|
37
|
+
"dark:[--fc-operator:var(--fc-operator-d)]",
|
|
38
|
+
"dark:[--fc-function:var(--fc-function-d)]",
|
|
39
|
+
"dark:[--fc-type:var(--fc-type-d)]",
|
|
40
|
+
"dark:[--fc-tag:var(--fc-tag-d)]",
|
|
41
|
+
"dark:[--fc-attribute:var(--fc-attribute-d)]",
|
|
42
|
+
"dark:[--fc-attributeValue:var(--fc-attributeValue-d)]",
|
|
43
|
+
"dark:[--fc-punctuation:var(--fc-punctuation-d)]",
|
|
44
|
+
"dark:[--fc-variable:var(--fc-variable-d)]"
|
|
45
|
+
].join(" ");
|
|
46
|
+
var CODE_PALETTE_ATTR = "data-react-fancy-code-palette";
|
|
47
|
+
var ALL_VARIABLES = fancyFileCommons.themeVariables(fancyFileCommons.LIGHT_COLORS, fancyFileCommons.DARK_COLORS);
|
|
48
|
+
var CODE_PALETTE_STYLE = Object.fromEntries(
|
|
49
|
+
CODE_PALETTE_KEYS.flatMap((key) => [
|
|
50
|
+
[`--fc-${key}-l`, ALL_VARIABLES[`--fc-${key}-l`]],
|
|
51
|
+
[`--fc-${key}-d`, ALL_VARIABLES[`--fc-${key}-d`]]
|
|
52
|
+
])
|
|
53
|
+
);
|
|
54
|
+
function paletteColor(key) {
|
|
55
|
+
return fancyFileCommons.themeColorVar(key);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.CODE_PALETTE_ATTR = CODE_PALETTE_ATTR;
|
|
59
|
+
exports.CODE_PALETTE_DARK_CLASS = CODE_PALETTE_DARK_CLASS;
|
|
60
|
+
exports.CODE_PALETTE_STYLE = CODE_PALETTE_STYLE;
|
|
61
|
+
exports.paletteColor = paletteColor;
|
|
62
|
+
//# sourceMappingURL=chunk-5WXY3Q6X.cjs.map
|
|
63
|
+
//# sourceMappingURL=chunk-5WXY3Q6X.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/CodeView/code-palette.ts"],"names":["themeVariables","LIGHT_COLORS","DARK_COLORS","themeColorVar"],"mappings":";;;;;AA+BO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,YAAA;AAAA,EACA,YAAA;AAAA,EACA,kBAAA;AAAA,EACA,kBAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA;AAKO,IAAM,uBAAA,GAA0B;AAAA,EACrC,+CAAA;AAAA,EACA,+CAAA;AAAA,EACA,2DAAA;AAAA,EACA,2DAAA;AAAA,EACA,mDAAA;AAAA,EACA,iDAAA;AAAA,EACA,yCAAA;AAAA,EACA,uCAAA;AAAA,EACA,yCAAA;AAAA,EACA,uCAAA;AAAA,EACA,2CAAA;AAAA,EACA,2CAAA;AAAA,EACA,mCAAA;AAAA,EACA,iCAAA;AAAA,EACA,6CAAA;AAAA,EACA,uDAAA;AAAA,EACA,iDAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,GAAG;AAGH,IAAM,iBAAA,GAAoB;AAEjC,IAAM,aAAA,GAAgBA,+BAAA,CAAeC,6BAAA,EAAcC,4BAAW,CAAA;AAGvD,IAAM,qBAAoC,MAAA,CAAO,WAAA;AAAA,EACtD,iBAAA,CAAkB,OAAA,CAAQ,CAAC,GAAA,KAAQ;AAAA,IACjC,CAAC,QAAQ,GAAG,CAAA,EAAA,CAAA,EAAM,cAAc,CAAA,KAAA,EAAQ,GAAG,IAAI,CAAC,CAAA;AAAA,IAChD,CAAC,QAAQ,GAAG,CAAA,EAAA,CAAA,EAAM,cAAc,CAAA,KAAA,EAAQ,GAAG,IAAI,CAAC;AAAA,GACjD;AACH;AAGO,SAAS,aAAa,GAAA,EAA6B;AACxD,EAAA,OAAOC,+BAAc,GAAG,CAAA;AAC1B","file":"chunk-5WXY3Q6X.cjs","sourcesContent":["import type { CSSProperties } from \"react\";\nimport {\n themeColorVar,\n themeVariables,\n LIGHT_COLORS,\n DARK_COLORS,\n type ThemeColorKey,\n} from \"@particle-academy/fancy-file-commons\";\n\n// ── Both palettes, switched by CSS ──────────────────────────────────────────\n// CodeBlock and CodeView used to pick ONE palette with a client-only hook, so\n// server-rendered HTML always carried the light colours and a dark site flashed\n// light until hydration. Now the markup is the same in either theme:\n//\n// 1. the root declares `--fc-<key>-l` and `--fc-<key>-d` (both palettes, via\n// fancy-file-commons' `themeVariables`, which allow-lists every value);\n// 2. everything coloured reads `var(--fc-<key>,var(--fc-<key>-l))`, so it is\n// light unless `--fc-<key>` is set;\n// 3. `--fc-<key>` is set to the dark value by EITHER\n// - the `dark:` classes below, compiled by the consumer's Tailwind from\n// react-fancy's dist, so they follow the consumer's own `dark` variant\n// (class strategy, `dark-variant.css`, or the default media query), or\n// - the zero-specificity rule in `styles.css` keyed on\n// `[data-react-fancy-code-palette]` under `.dark` / `[data-theme=\"dark\"]`,\n// for a host whose Tailwind does not scan this package.\n// Together they cover what the old hook followed: `.dark` OR the media\n// query (the latter through Tailwind's default `dark` variant).\n//\n// The class strings MUST stay literal: Tailwind finds them by scanning dist.\n\n/** The palette slots CodeBlock and CodeView paint. */\nexport const CODE_PALETTE_KEYS = [\n \"background\",\n \"foreground\",\n \"gutterBackground\",\n \"gutterForeground\",\n \"gutterBorder\",\n \"cursorColor\",\n \"keyword\",\n \"string\",\n \"comment\",\n \"number\",\n \"operator\",\n \"function\",\n \"type\",\n \"tag\",\n \"attribute\",\n \"attributeValue\",\n \"punctuation\",\n \"variable\",\n] as const satisfies readonly ThemeColorKey[];\n\nexport type CodePaletteKey = (typeof CODE_PALETTE_KEYS)[number];\n\n/** One `dark:` class per key in `CODE_PALETTE_KEYS`; a test keeps them in step. */\nexport const CODE_PALETTE_DARK_CLASS = [\n \"dark:[--fc-background:var(--fc-background-d)]\",\n \"dark:[--fc-foreground:var(--fc-foreground-d)]\",\n \"dark:[--fc-gutterBackground:var(--fc-gutterBackground-d)]\",\n \"dark:[--fc-gutterForeground:var(--fc-gutterForeground-d)]\",\n \"dark:[--fc-gutterBorder:var(--fc-gutterBorder-d)]\",\n \"dark:[--fc-cursorColor:var(--fc-cursorColor-d)]\",\n \"dark:[--fc-keyword:var(--fc-keyword-d)]\",\n \"dark:[--fc-string:var(--fc-string-d)]\",\n \"dark:[--fc-comment:var(--fc-comment-d)]\",\n \"dark:[--fc-number:var(--fc-number-d)]\",\n \"dark:[--fc-operator:var(--fc-operator-d)]\",\n \"dark:[--fc-function:var(--fc-function-d)]\",\n \"dark:[--fc-type:var(--fc-type-d)]\",\n \"dark:[--fc-tag:var(--fc-tag-d)]\",\n \"dark:[--fc-attribute:var(--fc-attribute-d)]\",\n \"dark:[--fc-attributeValue:var(--fc-attributeValue-d)]\",\n \"dark:[--fc-punctuation:var(--fc-punctuation-d)]\",\n \"dark:[--fc-variable:var(--fc-variable-d)]\",\n].join(\" \");\n\n/** Marks the element that declares the palette; styles.css keys its dark rule on it. */\nexport const CODE_PALETTE_ATTR = \"data-react-fancy-code-palette\";\n\nconst ALL_VARIABLES = themeVariables(LIGHT_COLORS, DARK_COLORS);\n\n/** Both palettes as custom properties, for the root's `style`. */\nexport const CODE_PALETTE_STYLE: CSSProperties = Object.fromEntries(\n CODE_PALETTE_KEYS.flatMap((key) => [\n [`--fc-${key}-l`, ALL_VARIABLES[`--fc-${key}-l`]],\n [`--fc-${key}-d`, ALL_VARIABLES[`--fc-${key}-d`]],\n ]),\n) as CSSProperties;\n\n/** The CSS value for palette slot `key`: dark under the dark switch, light otherwise. */\nexport function paletteColor(key: CodePaletteKey): string {\n return themeColorVar(key);\n}\n"]}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { runWriter, defaultClipboardWriter } from './chunk-J2BWXCES.js';
|
|
2
2
|
import { visuallyHidden } from './chunk-I3LAFKJU.js';
|
|
3
|
-
import {
|
|
3
|
+
import { paletteColor, CODE_PALETTE_STYLE, CODE_PALETTE_DARK_CLASS, CODE_PALETTE_ATTR } from './chunk-2X54BVXG.js';
|
|
4
4
|
import { Button } from './chunk-4GPH7HOR.js';
|
|
5
5
|
import { cn } from './chunk-MMW2JGKZ.js';
|
|
6
6
|
import { forwardRef, useMemo, useState, useRef, useEffect, useCallback } from 'react';
|
|
7
|
-
import {
|
|
7
|
+
import { highlightCodeWithVariables, tokenize } from '@particle-academy/fancy-file-commons';
|
|
8
8
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
10
|
var FEEDBACK_MS = 2e3;
|
|
11
|
-
var HTML_ALIASES = /* @__PURE__ */ new Set(["html", "htm", "xml", "xhtml"]);
|
|
12
11
|
function tokensFor(code, language) {
|
|
13
|
-
return
|
|
12
|
+
return tokenize(code, language, { snippet: true }) ?? [];
|
|
14
13
|
}
|
|
15
|
-
function highlightLines(code, tokens
|
|
14
|
+
function highlightLines(code, tokens) {
|
|
16
15
|
const out = [];
|
|
17
16
|
let start = 0;
|
|
18
17
|
let ti = 0;
|
|
@@ -25,7 +24,7 @@ function highlightLines(code, tokens, colors) {
|
|
|
25
24
|
const e = Math.min(tokens[j].end, end) - start;
|
|
26
25
|
if (e > s) clipped.push({ type: tokens[j].type, start: s, end: e });
|
|
27
26
|
}
|
|
28
|
-
out.push(
|
|
27
|
+
out.push(highlightCodeWithVariables(line, clipped));
|
|
29
28
|
start = end + 1;
|
|
30
29
|
}
|
|
31
30
|
return out;
|
|
@@ -44,19 +43,17 @@ var CodeBlock = forwardRef(function CodeBlock2({
|
|
|
44
43
|
style,
|
|
45
44
|
...rest
|
|
46
45
|
}, ref) {
|
|
47
|
-
const isDark = useIsDark();
|
|
48
|
-
const colors = isDark ? DARK_COLORS : LIGHT_COLORS;
|
|
49
46
|
const tokens = useMemo(() => tokensFor(code, language), [code, language]);
|
|
50
47
|
const html = useMemo(
|
|
51
|
-
() => showLineNumbers ? null :
|
|
52
|
-
[code, tokens,
|
|
48
|
+
() => showLineNumbers ? null : highlightCodeWithVariables(code, tokens),
|
|
49
|
+
[code, tokens, showLineNumbers]
|
|
53
50
|
);
|
|
54
51
|
const lines = useMemo(() => {
|
|
55
52
|
if (!showLineNumbers) return null;
|
|
56
|
-
const all = highlightLines(code, tokens
|
|
53
|
+
const all = highlightLines(code, tokens);
|
|
57
54
|
if (all.length > 1 && code.endsWith("\n")) all.pop();
|
|
58
55
|
return all;
|
|
59
|
-
}, [code, tokens,
|
|
56
|
+
}, [code, tokens, showLineNumbers]);
|
|
60
57
|
const [copyState, setCopyState] = useState("idle");
|
|
61
58
|
const timer = useRef(void 0);
|
|
62
59
|
useEffect(() => () => clearTimeout(timer.current), []);
|
|
@@ -93,7 +90,7 @@ var CodeBlock = forwardRef(function CodeBlock2({
|
|
|
93
90
|
className: cn(
|
|
94
91
|
floating && "opacity-80 group-hover:opacity-100 focus-visible:opacity-100"
|
|
95
92
|
),
|
|
96
|
-
style: floating ? { backgroundColor:
|
|
93
|
+
style: floating ? { backgroundColor: paletteColor("background") } : void 0,
|
|
97
94
|
children: copyState !== "idle" ? label : hasHeader ? "Copy" : void 0
|
|
98
95
|
}
|
|
99
96
|
) : null;
|
|
@@ -105,14 +102,19 @@ var CodeBlock = forwardRef(function CodeBlock2({
|
|
|
105
102
|
"data-react-fancy-code-block": "",
|
|
106
103
|
"data-language": language,
|
|
107
104
|
"data-wrap": wrap ? "true" : "false",
|
|
105
|
+
...{ [CODE_PALETTE_ATTR]: "" },
|
|
108
106
|
className: cn(
|
|
109
107
|
"group relative overflow-hidden rounded-lg border",
|
|
108
|
+
CODE_PALETTE_DARK_CLASS,
|
|
110
109
|
className
|
|
111
110
|
),
|
|
112
111
|
style: {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
// Both palettes; the dark one applies under the consumer's `dark`
|
|
113
|
+
// variant or a `.dark` / `[data-theme="dark"]` ancestor (code-palette).
|
|
114
|
+
...CODE_PALETTE_STYLE,
|
|
115
|
+
backgroundColor: paletteColor("background"),
|
|
116
|
+
color: paletteColor("foreground"),
|
|
117
|
+
borderColor: paletteColor("gutterBorder"),
|
|
116
118
|
// `relative` inline too: the floating button and the live region are
|
|
117
119
|
// absolute, and must not escape a host whose Tailwind lacks the class.
|
|
118
120
|
position: "relative",
|
|
@@ -126,9 +128,9 @@ var CodeBlock = forwardRef(function CodeBlock2({
|
|
|
126
128
|
"data-react-fancy-code-block-header": "",
|
|
127
129
|
className: "flex items-center justify-between gap-2 border-b py-1 pl-3 pr-1 text-xs",
|
|
128
130
|
style: {
|
|
129
|
-
backgroundColor:
|
|
130
|
-
color:
|
|
131
|
-
borderColor:
|
|
131
|
+
backgroundColor: paletteColor("gutterBackground"),
|
|
132
|
+
color: paletteColor("gutterForeground"),
|
|
133
|
+
borderColor: paletteColor("gutterBorder")
|
|
132
134
|
},
|
|
133
135
|
children: [
|
|
134
136
|
/* @__PURE__ */ jsx("span", { "data-react-fancy-code-block-title": "", className: "min-w-0 truncate font-mono", children: title }),
|
|
@@ -164,7 +166,7 @@ var CodeBlock = forwardRef(function CodeBlock2({
|
|
|
164
166
|
width: gutterWidth,
|
|
165
167
|
marginRight: "1.5ch",
|
|
166
168
|
textAlign: "right",
|
|
167
|
-
color:
|
|
169
|
+
color: paletteColor("gutterForeground"),
|
|
168
170
|
userSelect: "none"
|
|
169
171
|
},
|
|
170
172
|
children: i + 1
|
|
@@ -200,5 +202,5 @@ var CodeBlock = forwardRef(function CodeBlock2({
|
|
|
200
202
|
CodeBlock.displayName = "CodeBlock";
|
|
201
203
|
|
|
202
204
|
export { CodeBlock };
|
|
203
|
-
//# sourceMappingURL=chunk-
|
|
204
|
-
//# sourceMappingURL=chunk-
|
|
205
|
+
//# sourceMappingURL=chunk-MI57NAKF.js.map
|
|
206
|
+
//# sourceMappingURL=chunk-MI57NAKF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/CodeBlock/CodeBlock.tsx"],"names":["CodeBlock"],"mappings":"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","file":"chunk-MI57NAKF.js","sourcesContent":["import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n highlightCodeWithVariables,\n tokenize,\n type Token,\n} from \"@particle-academy/fancy-file-commons\";\nimport { cn } from \"../../utils/cn\";\nimport { visuallyHidden } from \"../../utils/visually-hidden\";\nimport { Button } from \"../Button/Button\";\nimport { defaultClipboardWriter, runWriter } from \"../ContentRenderer/copy\";\nimport {\n CODE_PALETTE_ATTR,\n CODE_PALETTE_DARK_CLASS,\n CODE_PALETTE_STYLE,\n paletteColor,\n} from \"../CodeView/code-palette\";\nimport type { CodeBlockProps } from \"./CodeBlock.types\";\n\n/** How long \"Copied\" / \"Copy failed\" stays before the button resets. */\nconst FEEDBACK_MS = 2000;\n\n/**\n * Tokens for any language fancy-file-commons can highlight (aliases included,\n * case-insensitive); none for anything else, which renders as plain text.\n * `snippet: true` because a code block shows documentation snippets: PHP\n * without an opening `<?php` is read as PHP, not inline HTML. `tokenize`\n * returns sorted, non-overlapping, in-bounds tokens, which `highlightLines`\n * relies on.\n */\nfunction tokensFor(code: string, language: string): Token[] {\n return tokenize(code, language, { snippet: true }) ?? [];\n}\n\n/**\n * Highlight one line at a time, clipping each token to the line it falls on, so\n * a token that spans lines (an HTML comment) keeps its colour on every line.\n * `highlightCodeWithVariables` escapes every slice it emits, highlighted or not,\n * and writes no colour value: spans read the palette variables on the root.\n */\nfunction highlightLines(code: string, tokens: Token[]): string[] {\n const out: string[] = [];\n let start = 0;\n let ti = 0;\n for (const line of code.split(\"\\n\")) {\n const end = start + line.length;\n while (ti < tokens.length && tokens[ti].end <= start) ti++;\n const clipped: Token[] = [];\n for (let j = ti; j < tokens.length && tokens[j].start < end; j++) {\n const s = Math.max(tokens[j].start, start) - start;\n const e = Math.min(tokens[j].end, end) - start;\n if (e > s) clipped.push({ type: tokens[j].type, start: s, end: e });\n }\n out.push(highlightCodeWithVariables(line, clipped));\n start = end + 1;\n }\n return out;\n}\n\ntype CopyState = \"idle\" | \"copied\" | \"failed\";\n\n// Matches CodeView's text metrics so the two sit together.\nconst TEXT_METRICS = \"m-0 p-3 font-mono text-xs leading-relaxed\";\n\n/**\n * A display-only code block: `<pre><code>`, syntax-highlighted where a grammar\n * exists, with an optional copy button. It contains no textarea, input, select\n * or contenteditable element, so it is safe on pages that must not accept\n * input. For an editable or selectable source surface, use `CodeView`.\n */\nexport const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(function CodeBlock(\n {\n code,\n language = \"plaintext\",\n copyable = true,\n title,\n showLineNumbers = false,\n wrap = false,\n copyLabels,\n onCopyCode,\n className,\n style,\n ...rest\n },\n ref,\n) {\n const tokens = useMemo(() => tokensFor(code, language), [code, language]);\n const html = useMemo(\n () => (showLineNumbers ? null : highlightCodeWithVariables(code, tokens)),\n [code, tokens, showLineNumbers],\n );\n const lines = useMemo(() => {\n if (!showLineNumbers) return null;\n const all = highlightLines(code, tokens);\n // A trailing newline ends the last line; it does not start a new one.\n if (all.length > 1 && code.endsWith(\"\\n\")) all.pop();\n return all;\n }, [code, tokens, showLineNumbers]);\n\n const [copyState, setCopyState] = useState<CopyState>(\"idle\");\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n useEffect(() => () => clearTimeout(timer.current), []);\n\n // Latest props at click time, without re-creating the handler.\n const latest = useRef({ code, onCopyCode });\n latest.current = { code, onCopyCode };\n\n const onCopyClick = useCallback(async () => {\n const { code: text, onCopyCode: report } = latest.current;\n // Raw `code`, never the highlighted HTML. `defaultClipboardWriter` throws\n // when `navigator.clipboard` is absent (SSR, insecure context), and\n // `runWriter` turns a throw, rejection or hang into `false`.\n const ok = await runWriter(defaultClipboardWriter, { kind: \"code\", text, html: \"\" });\n setCopyState(ok ? \"copied\" : \"failed\");\n clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopyState(\"idle\"), FEEDBACK_MS);\n report?.({ text, ok });\n }, []);\n\n const labels = {\n copy: copyLabels?.copy ?? \"Copy code\",\n copied: copyLabels?.copied ?? \"Copied\",\n failed: copyLabels?.failed ?? \"Copy failed\",\n };\n const label = copyState === \"copied\" ? labels.copied : copyState === \"failed\" ? labels.failed : labels.copy;\n const hasHeader = title != null && title !== false;\n const floating = copyable && !hasHeader;\n\n const copyButton = copyable ? (\n <Button\n type=\"button\"\n size=\"xs\"\n variant=\"ghost\"\n icon={copyState === \"copied\" ? \"check\" : copyState === \"failed\" ? \"x\" : \"copy\"}\n aria-label={label}\n title={label}\n data-react-fancy-code-block-copy=\"\"\n data-copy-state={copyState}\n onClick={onCopyClick}\n className={cn(\n floating && \"opacity-80 group-hover:opacity-100 focus-visible:opacity-100\",\n )}\n style={floating ? { backgroundColor: paletteColor(\"background\") } : undefined}\n >\n {/* Visible feedback; idle is icon-only when floating over the code. */}\n {copyState !== \"idle\" ? label : hasHeader ? \"Copy\" : undefined}\n </Button>\n ) : null;\n\n const gutterWidth = lines ? `${String(lines.length).length}ch` : undefined;\n\n return (\n <div\n ref={ref}\n data-react-fancy-code-block=\"\"\n data-language={language}\n data-wrap={wrap ? \"true\" : \"false\"}\n {...{ [CODE_PALETTE_ATTR]: \"\" }}\n className={cn(\n \"group relative overflow-hidden rounded-lg border\",\n CODE_PALETTE_DARK_CLASS,\n className,\n )}\n style={{\n // Both palettes; the dark one applies under the consumer's `dark`\n // variant or a `.dark` / `[data-theme=\"dark\"]` ancestor (code-palette).\n ...CODE_PALETTE_STYLE,\n backgroundColor: paletteColor(\"background\"),\n color: paletteColor(\"foreground\"),\n borderColor: paletteColor(\"gutterBorder\"),\n // `relative` inline too: the floating button and the live region are\n // absolute, and must not escape a host whose Tailwind lacks the class.\n position: \"relative\",\n ...style,\n }}\n {...rest}\n >\n {hasHeader && (\n <div\n data-react-fancy-code-block-header=\"\"\n className=\"flex items-center justify-between gap-2 border-b py-1 pl-3 pr-1 text-xs\"\n style={{\n backgroundColor: paletteColor(\"gutterBackground\"),\n color: paletteColor(\"gutterForeground\"),\n borderColor: paletteColor(\"gutterBorder\"),\n }}\n >\n <span data-react-fancy-code-block-title=\"\" className=\"min-w-0 truncate font-mono\">\n {title}\n </span>\n {copyButton}\n </div>\n )}\n\n <pre\n data-react-fancy-code-block-pre=\"\"\n className={cn(TEXT_METRICS, \"border-none\", wrap ? \"overflow-x-hidden\" : \"overflow-x-auto\", floating && \"pr-12\")}\n style={{\n whiteSpace: wrap ? \"pre-wrap\" : \"pre\",\n overflowWrap: wrap ? \"break-word\" : \"normal\",\n backgroundColor: \"transparent\",\n color: \"inherit\",\n }}\n >\n {lines ? (\n <code\n data-react-fancy-code-block-code=\"\"\n style={{ display: wrap ? \"block\" : \"inline-block\", minWidth: \"100%\" }}\n >\n {lines.map((lineHtml, i) => (\n <span key={i} data-react-fancy-code-block-line={i + 1} style={{ display: \"flex\" }}>\n <span\n data-react-fancy-code-block-line-number=\"\"\n aria-hidden=\"true\"\n className=\"select-none\"\n style={{\n flex: \"none\",\n width: gutterWidth,\n marginRight: \"1.5ch\",\n textAlign: \"right\",\n color: paletteColor(\"gutterForeground\"),\n userSelect: \"none\",\n }}\n >\n {i + 1}\n </span>\n <span\n data-react-fancy-code-block-line-content=\"\"\n style={{ flex: \"1 1 auto\", minWidth: 0 }}\n dangerouslySetInnerHTML={{ __html: lineHtml }}\n />\n </span>\n ))}\n </code>\n ) : (\n <code data-react-fancy-code-block-code=\"\" dangerouslySetInnerHTML={{ __html: html ?? \"\" }} />\n )}\n </pre>\n\n {floating && (\n <div\n data-react-fancy-code-block-actions=\"\"\n className=\"absolute right-2 top-2\"\n style={{ position: \"absolute\", right: \"0.5rem\", top: \"0.5rem\" }}\n >\n {copyButton}\n </div>\n )}\n\n {copyable && (\n <span role=\"status\" aria-live=\"polite\" className=\"sr-only\" style={visuallyHidden}>\n {copyState === \"idle\" ? \"\" : label}\n </span>\n )}\n </div>\n );\n});\n\nCodeBlock.displayName = \"CodeBlock\";\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var chunk7ZCNZUZK_cjs = require('./chunk-7ZCNZUZK.cjs');
|
|
4
4
|
var chunk2C64OVOZ_cjs = require('./chunk-2C64OVOZ.cjs');
|
|
5
|
-
var
|
|
5
|
+
var chunk5WXY3Q6X_cjs = require('./chunk-5WXY3Q6X.cjs');
|
|
6
6
|
var chunkNZHVDWOM_cjs = require('./chunk-NZHVDWOM.cjs');
|
|
7
7
|
var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
|
|
8
8
|
var react = require('react');
|
|
@@ -10,11 +10,10 @@ var fancyFileCommons = require('@particle-academy/fancy-file-commons');
|
|
|
10
10
|
var jsxRuntime = require('react/jsx-runtime');
|
|
11
11
|
|
|
12
12
|
var FEEDBACK_MS = 2e3;
|
|
13
|
-
var HTML_ALIASES = /* @__PURE__ */ new Set(["html", "htm", "xml", "xhtml"]);
|
|
14
13
|
function tokensFor(code, language) {
|
|
15
|
-
return
|
|
14
|
+
return fancyFileCommons.tokenize(code, language, { snippet: true }) ?? [];
|
|
16
15
|
}
|
|
17
|
-
function highlightLines(code, tokens
|
|
16
|
+
function highlightLines(code, tokens) {
|
|
18
17
|
const out = [];
|
|
19
18
|
let start = 0;
|
|
20
19
|
let ti = 0;
|
|
@@ -27,7 +26,7 @@ function highlightLines(code, tokens, colors) {
|
|
|
27
26
|
const e = Math.min(tokens[j].end, end) - start;
|
|
28
27
|
if (e > s) clipped.push({ type: tokens[j].type, start: s, end: e });
|
|
29
28
|
}
|
|
30
|
-
out.push(fancyFileCommons.
|
|
29
|
+
out.push(fancyFileCommons.highlightCodeWithVariables(line, clipped));
|
|
31
30
|
start = end + 1;
|
|
32
31
|
}
|
|
33
32
|
return out;
|
|
@@ -46,19 +45,17 @@ var CodeBlock = react.forwardRef(function CodeBlock2({
|
|
|
46
45
|
style,
|
|
47
46
|
...rest
|
|
48
47
|
}, ref) {
|
|
49
|
-
const isDark = chunkHZUKPYR3_cjs.useIsDark();
|
|
50
|
-
const colors = isDark ? fancyFileCommons.DARK_COLORS : fancyFileCommons.LIGHT_COLORS;
|
|
51
48
|
const tokens = react.useMemo(() => tokensFor(code, language), [code, language]);
|
|
52
49
|
const html = react.useMemo(
|
|
53
|
-
() => showLineNumbers ? null : fancyFileCommons.
|
|
54
|
-
[code, tokens,
|
|
50
|
+
() => showLineNumbers ? null : fancyFileCommons.highlightCodeWithVariables(code, tokens),
|
|
51
|
+
[code, tokens, showLineNumbers]
|
|
55
52
|
);
|
|
56
53
|
const lines = react.useMemo(() => {
|
|
57
54
|
if (!showLineNumbers) return null;
|
|
58
|
-
const all = highlightLines(code, tokens
|
|
55
|
+
const all = highlightLines(code, tokens);
|
|
59
56
|
if (all.length > 1 && code.endsWith("\n")) all.pop();
|
|
60
57
|
return all;
|
|
61
|
-
}, [code, tokens,
|
|
58
|
+
}, [code, tokens, showLineNumbers]);
|
|
62
59
|
const [copyState, setCopyState] = react.useState("idle");
|
|
63
60
|
const timer = react.useRef(void 0);
|
|
64
61
|
react.useEffect(() => () => clearTimeout(timer.current), []);
|
|
@@ -95,7 +92,7 @@ var CodeBlock = react.forwardRef(function CodeBlock2({
|
|
|
95
92
|
className: chunkZU5Q77OT_cjs.cn(
|
|
96
93
|
floating && "opacity-80 group-hover:opacity-100 focus-visible:opacity-100"
|
|
97
94
|
),
|
|
98
|
-
style: floating ? { backgroundColor:
|
|
95
|
+
style: floating ? { backgroundColor: chunk5WXY3Q6X_cjs.paletteColor("background") } : void 0,
|
|
99
96
|
children: copyState !== "idle" ? label : hasHeader ? "Copy" : void 0
|
|
100
97
|
}
|
|
101
98
|
) : null;
|
|
@@ -107,14 +104,19 @@ var CodeBlock = react.forwardRef(function CodeBlock2({
|
|
|
107
104
|
"data-react-fancy-code-block": "",
|
|
108
105
|
"data-language": language,
|
|
109
106
|
"data-wrap": wrap ? "true" : "false",
|
|
107
|
+
...{ [chunk5WXY3Q6X_cjs.CODE_PALETTE_ATTR]: "" },
|
|
110
108
|
className: chunkZU5Q77OT_cjs.cn(
|
|
111
109
|
"group relative overflow-hidden rounded-lg border",
|
|
110
|
+
chunk5WXY3Q6X_cjs.CODE_PALETTE_DARK_CLASS,
|
|
112
111
|
className
|
|
113
112
|
),
|
|
114
113
|
style: {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
// Both palettes; the dark one applies under the consumer's `dark`
|
|
115
|
+
// variant or a `.dark` / `[data-theme="dark"]` ancestor (code-palette).
|
|
116
|
+
...chunk5WXY3Q6X_cjs.CODE_PALETTE_STYLE,
|
|
117
|
+
backgroundColor: chunk5WXY3Q6X_cjs.paletteColor("background"),
|
|
118
|
+
color: chunk5WXY3Q6X_cjs.paletteColor("foreground"),
|
|
119
|
+
borderColor: chunk5WXY3Q6X_cjs.paletteColor("gutterBorder"),
|
|
118
120
|
// `relative` inline too: the floating button and the live region are
|
|
119
121
|
// absolute, and must not escape a host whose Tailwind lacks the class.
|
|
120
122
|
position: "relative",
|
|
@@ -128,9 +130,9 @@ var CodeBlock = react.forwardRef(function CodeBlock2({
|
|
|
128
130
|
"data-react-fancy-code-block-header": "",
|
|
129
131
|
className: "flex items-center justify-between gap-2 border-b py-1 pl-3 pr-1 text-xs",
|
|
130
132
|
style: {
|
|
131
|
-
backgroundColor:
|
|
132
|
-
color:
|
|
133
|
-
borderColor:
|
|
133
|
+
backgroundColor: chunk5WXY3Q6X_cjs.paletteColor("gutterBackground"),
|
|
134
|
+
color: chunk5WXY3Q6X_cjs.paletteColor("gutterForeground"),
|
|
135
|
+
borderColor: chunk5WXY3Q6X_cjs.paletteColor("gutterBorder")
|
|
134
136
|
},
|
|
135
137
|
children: [
|
|
136
138
|
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-react-fancy-code-block-title": "", className: "min-w-0 truncate font-mono", children: title }),
|
|
@@ -166,7 +168,7 @@ var CodeBlock = react.forwardRef(function CodeBlock2({
|
|
|
166
168
|
width: gutterWidth,
|
|
167
169
|
marginRight: "1.5ch",
|
|
168
170
|
textAlign: "right",
|
|
169
|
-
color:
|
|
171
|
+
color: chunk5WXY3Q6X_cjs.paletteColor("gutterForeground"),
|
|
170
172
|
userSelect: "none"
|
|
171
173
|
},
|
|
172
174
|
children: i + 1
|
|
@@ -202,5 +204,5 @@ var CodeBlock = react.forwardRef(function CodeBlock2({
|
|
|
202
204
|
CodeBlock.displayName = "CodeBlock";
|
|
203
205
|
|
|
204
206
|
exports.CodeBlock = CodeBlock;
|
|
205
|
-
//# sourceMappingURL=chunk-
|
|
206
|
-
//# sourceMappingURL=chunk-
|
|
207
|
+
//# sourceMappingURL=chunk-PM5I5ZHK.cjs.map
|
|
208
|
+
//# sourceMappingURL=chunk-PM5I5ZHK.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/CodeBlock/CodeBlock.tsx"],"names":["tokenize","highlightCodeWithVariables","forwardRef","CodeBlock","useMemo","useState","useRef","useEffect","useCallback","runWriter","defaultClipboardWriter","jsx","Button","cn","paletteColor","jsxs","CODE_PALETTE_ATTR","CODE_PALETTE_DARK_CLASS","CODE_PALETTE_STYLE","visuallyHidden"],"mappings":"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","file":"chunk-PM5I5ZHK.cjs","sourcesContent":["import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n highlightCodeWithVariables,\n tokenize,\n type Token,\n} from \"@particle-academy/fancy-file-commons\";\nimport { cn } from \"../../utils/cn\";\nimport { visuallyHidden } from \"../../utils/visually-hidden\";\nimport { Button } from \"../Button/Button\";\nimport { defaultClipboardWriter, runWriter } from \"../ContentRenderer/copy\";\nimport {\n CODE_PALETTE_ATTR,\n CODE_PALETTE_DARK_CLASS,\n CODE_PALETTE_STYLE,\n paletteColor,\n} from \"../CodeView/code-palette\";\nimport type { CodeBlockProps } from \"./CodeBlock.types\";\n\n/** How long \"Copied\" / \"Copy failed\" stays before the button resets. */\nconst FEEDBACK_MS = 2000;\n\n/**\n * Tokens for any language fancy-file-commons can highlight (aliases included,\n * case-insensitive); none for anything else, which renders as plain text.\n * `snippet: true` because a code block shows documentation snippets: PHP\n * without an opening `<?php` is read as PHP, not inline HTML. `tokenize`\n * returns sorted, non-overlapping, in-bounds tokens, which `highlightLines`\n * relies on.\n */\nfunction tokensFor(code: string, language: string): Token[] {\n return tokenize(code, language, { snippet: true }) ?? [];\n}\n\n/**\n * Highlight one line at a time, clipping each token to the line it falls on, so\n * a token that spans lines (an HTML comment) keeps its colour on every line.\n * `highlightCodeWithVariables` escapes every slice it emits, highlighted or not,\n * and writes no colour value: spans read the palette variables on the root.\n */\nfunction highlightLines(code: string, tokens: Token[]): string[] {\n const out: string[] = [];\n let start = 0;\n let ti = 0;\n for (const line of code.split(\"\\n\")) {\n const end = start + line.length;\n while (ti < tokens.length && tokens[ti].end <= start) ti++;\n const clipped: Token[] = [];\n for (let j = ti; j < tokens.length && tokens[j].start < end; j++) {\n const s = Math.max(tokens[j].start, start) - start;\n const e = Math.min(tokens[j].end, end) - start;\n if (e > s) clipped.push({ type: tokens[j].type, start: s, end: e });\n }\n out.push(highlightCodeWithVariables(line, clipped));\n start = end + 1;\n }\n return out;\n}\n\ntype CopyState = \"idle\" | \"copied\" | \"failed\";\n\n// Matches CodeView's text metrics so the two sit together.\nconst TEXT_METRICS = \"m-0 p-3 font-mono text-xs leading-relaxed\";\n\n/**\n * A display-only code block: `<pre><code>`, syntax-highlighted where a grammar\n * exists, with an optional copy button. It contains no textarea, input, select\n * or contenteditable element, so it is safe on pages that must not accept\n * input. For an editable or selectable source surface, use `CodeView`.\n */\nexport const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(function CodeBlock(\n {\n code,\n language = \"plaintext\",\n copyable = true,\n title,\n showLineNumbers = false,\n wrap = false,\n copyLabels,\n onCopyCode,\n className,\n style,\n ...rest\n },\n ref,\n) {\n const tokens = useMemo(() => tokensFor(code, language), [code, language]);\n const html = useMemo(\n () => (showLineNumbers ? null : highlightCodeWithVariables(code, tokens)),\n [code, tokens, showLineNumbers],\n );\n const lines = useMemo(() => {\n if (!showLineNumbers) return null;\n const all = highlightLines(code, tokens);\n // A trailing newline ends the last line; it does not start a new one.\n if (all.length > 1 && code.endsWith(\"\\n\")) all.pop();\n return all;\n }, [code, tokens, showLineNumbers]);\n\n const [copyState, setCopyState] = useState<CopyState>(\"idle\");\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n useEffect(() => () => clearTimeout(timer.current), []);\n\n // Latest props at click time, without re-creating the handler.\n const latest = useRef({ code, onCopyCode });\n latest.current = { code, onCopyCode };\n\n const onCopyClick = useCallback(async () => {\n const { code: text, onCopyCode: report } = latest.current;\n // Raw `code`, never the highlighted HTML. `defaultClipboardWriter` throws\n // when `navigator.clipboard` is absent (SSR, insecure context), and\n // `runWriter` turns a throw, rejection or hang into `false`.\n const ok = await runWriter(defaultClipboardWriter, { kind: \"code\", text, html: \"\" });\n setCopyState(ok ? \"copied\" : \"failed\");\n clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopyState(\"idle\"), FEEDBACK_MS);\n report?.({ text, ok });\n }, []);\n\n const labels = {\n copy: copyLabels?.copy ?? \"Copy code\",\n copied: copyLabels?.copied ?? \"Copied\",\n failed: copyLabels?.failed ?? \"Copy failed\",\n };\n const label = copyState === \"copied\" ? labels.copied : copyState === \"failed\" ? labels.failed : labels.copy;\n const hasHeader = title != null && title !== false;\n const floating = copyable && !hasHeader;\n\n const copyButton = copyable ? (\n <Button\n type=\"button\"\n size=\"xs\"\n variant=\"ghost\"\n icon={copyState === \"copied\" ? \"check\" : copyState === \"failed\" ? \"x\" : \"copy\"}\n aria-label={label}\n title={label}\n data-react-fancy-code-block-copy=\"\"\n data-copy-state={copyState}\n onClick={onCopyClick}\n className={cn(\n floating && \"opacity-80 group-hover:opacity-100 focus-visible:opacity-100\",\n )}\n style={floating ? { backgroundColor: paletteColor(\"background\") } : undefined}\n >\n {/* Visible feedback; idle is icon-only when floating over the code. */}\n {copyState !== \"idle\" ? label : hasHeader ? \"Copy\" : undefined}\n </Button>\n ) : null;\n\n const gutterWidth = lines ? `${String(lines.length).length}ch` : undefined;\n\n return (\n <div\n ref={ref}\n data-react-fancy-code-block=\"\"\n data-language={language}\n data-wrap={wrap ? \"true\" : \"false\"}\n {...{ [CODE_PALETTE_ATTR]: \"\" }}\n className={cn(\n \"group relative overflow-hidden rounded-lg border\",\n CODE_PALETTE_DARK_CLASS,\n className,\n )}\n style={{\n // Both palettes; the dark one applies under the consumer's `dark`\n // variant or a `.dark` / `[data-theme=\"dark\"]` ancestor (code-palette).\n ...CODE_PALETTE_STYLE,\n backgroundColor: paletteColor(\"background\"),\n color: paletteColor(\"foreground\"),\n borderColor: paletteColor(\"gutterBorder\"),\n // `relative` inline too: the floating button and the live region are\n // absolute, and must not escape a host whose Tailwind lacks the class.\n position: \"relative\",\n ...style,\n }}\n {...rest}\n >\n {hasHeader && (\n <div\n data-react-fancy-code-block-header=\"\"\n className=\"flex items-center justify-between gap-2 border-b py-1 pl-3 pr-1 text-xs\"\n style={{\n backgroundColor: paletteColor(\"gutterBackground\"),\n color: paletteColor(\"gutterForeground\"),\n borderColor: paletteColor(\"gutterBorder\"),\n }}\n >\n <span data-react-fancy-code-block-title=\"\" className=\"min-w-0 truncate font-mono\">\n {title}\n </span>\n {copyButton}\n </div>\n )}\n\n <pre\n data-react-fancy-code-block-pre=\"\"\n className={cn(TEXT_METRICS, \"border-none\", wrap ? \"overflow-x-hidden\" : \"overflow-x-auto\", floating && \"pr-12\")}\n style={{\n whiteSpace: wrap ? \"pre-wrap\" : \"pre\",\n overflowWrap: wrap ? \"break-word\" : \"normal\",\n backgroundColor: \"transparent\",\n color: \"inherit\",\n }}\n >\n {lines ? (\n <code\n data-react-fancy-code-block-code=\"\"\n style={{ display: wrap ? \"block\" : \"inline-block\", minWidth: \"100%\" }}\n >\n {lines.map((lineHtml, i) => (\n <span key={i} data-react-fancy-code-block-line={i + 1} style={{ display: \"flex\" }}>\n <span\n data-react-fancy-code-block-line-number=\"\"\n aria-hidden=\"true\"\n className=\"select-none\"\n style={{\n flex: \"none\",\n width: gutterWidth,\n marginRight: \"1.5ch\",\n textAlign: \"right\",\n color: paletteColor(\"gutterForeground\"),\n userSelect: \"none\",\n }}\n >\n {i + 1}\n </span>\n <span\n data-react-fancy-code-block-line-content=\"\"\n style={{ flex: \"1 1 auto\", minWidth: 0 }}\n dangerouslySetInnerHTML={{ __html: lineHtml }}\n />\n </span>\n ))}\n </code>\n ) : (\n <code data-react-fancy-code-block-code=\"\" dangerouslySetInnerHTML={{ __html: html ?? \"\" }} />\n )}\n </pre>\n\n {floating && (\n <div\n data-react-fancy-code-block-actions=\"\"\n className=\"absolute right-2 top-2\"\n style={{ position: \"absolute\", right: \"0.5rem\", top: \"0.5rem\" }}\n >\n {copyButton}\n </div>\n )}\n\n {copyable && (\n <span role=\"status\" aria-live=\"polite\" className=\"sr-only\" style={visuallyHidden}>\n {copyState === \"idle\" ? \"\" : label}\n </span>\n )}\n </div>\n );\n});\n\nCodeBlock.displayName = \"CodeBlock\";\n"]}
|