@particle-academy/react-fancy 6.5.0 → 6.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/{chunk-L5AI4MGV.cjs → chunk-25HRO7HQ.cjs} +4 -4
  3. package/dist/{chunk-L5AI4MGV.cjs.map → chunk-25HRO7HQ.cjs.map} +1 -1
  4. package/dist/{chunk-MF2DCMOA.cjs → chunk-2TLAZ5MX.cjs} +7 -5
  5. package/dist/chunk-2TLAZ5MX.cjs.map +1 -0
  6. package/dist/chunk-2X54BVXG.js +58 -0
  7. package/dist/chunk-2X54BVXG.js.map +1 -0
  8. package/dist/{chunk-ARNFT23K.js → chunk-5IXOQGPX.js} +7 -5
  9. package/dist/chunk-5IXOQGPX.js.map +1 -0
  10. package/dist/chunk-5WXY3Q6X.cjs +63 -0
  11. package/dist/chunk-5WXY3Q6X.cjs.map +1 -0
  12. package/dist/{chunk-YRI6OFLY.js → chunk-MI57NAKF.js} +24 -21
  13. package/dist/chunk-MI57NAKF.js.map +1 -0
  14. package/dist/{chunk-QH4YSG7C.cjs → chunk-PM5I5ZHK.cjs} +23 -20
  15. package/dist/chunk-PM5I5ZHK.cjs.map +1 -0
  16. package/dist/{chunk-PVHZO4T3.js → chunk-TFX4LL7V.js} +17 -12
  17. package/dist/chunk-TFX4LL7V.js.map +1 -0
  18. package/dist/{chunk-RE4BYTVM.cjs → chunk-THMFLRDW.cjs} +4 -4
  19. package/dist/{chunk-RE4BYTVM.cjs.map → chunk-THMFLRDW.cjs.map} +1 -1
  20. package/dist/{chunk-YYNUTUJO.js → chunk-VSTUGU7W.js} +3 -3
  21. package/dist/{chunk-YYNUTUJO.js.map → chunk-VSTUGU7W.js.map} +1 -1
  22. package/dist/{chunk-TQL3QW7F.cjs → chunk-YQZB5Z7C.cjs} +16 -11
  23. package/dist/chunk-YQZB5Z7C.cjs.map +1 -0
  24. package/dist/{chunk-LYG2NLSV.js → chunk-ZPBKRJMH.js} +3 -3
  25. package/dist/{chunk-LYG2NLSV.js.map → chunk-ZPBKRJMH.js.map} +1 -1
  26. package/dist/code-block.cjs +3 -3
  27. package/dist/code-block.js +2 -2
  28. package/dist/code-view.cjs +3 -3
  29. package/dist/code-view.js +2 -2
  30. package/dist/editor.cjs +5 -5
  31. package/dist/editor.js +3 -3
  32. package/dist/index.cjs +13 -13
  33. package/dist/index.js +6 -6
  34. package/dist/styles.css +20 -0
  35. package/dist/styles.css.map +1 -1
  36. package/dist/tree-nav.cjs +4 -4
  37. package/dist/tree-nav.js +2 -2
  38. package/dist/virtual-list.cjs +2 -2
  39. package/dist/virtual-list.js +1 -1
  40. package/docs/CodeBlock.md +11 -3
  41. package/docs/CodeView.md +7 -3
  42. package/package.json +2 -2
  43. package/dist/chunk-6BB32POL.js +0 -29
  44. package/dist/chunk-6BB32POL.js.map +0 -1
  45. package/dist/chunk-ARNFT23K.js.map +0 -1
  46. package/dist/chunk-HZUKPYR3.cjs +0 -31
  47. package/dist/chunk-HZUKPYR3.cjs.map +0 -1
  48. package/dist/chunk-MF2DCMOA.cjs.map +0 -1
  49. package/dist/chunk-PVHZO4T3.js.map +0 -1
  50. package/dist/chunk-QH4YSG7C.cjs.map +0 -1
  51. package/dist/chunk-TQL3QW7F.cjs.map +0 -1
  52. package/dist/chunk-YRI6OFLY.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/CodeView/CodeView.tsx"],"names":["useRef","useIsDark","DARK_COLORS","LIGHT_COLORS","useMemo","tokenize","highlightCode","useEffect","jsx","cn","jsxs"],"mappings":"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","file":"chunk-TQL3QW7F.cjs","sourcesContent":["import { useEffect, useMemo, useRef } from \"react\";\nimport {\n highlightCode,\n tokenize,\n LIGHT_COLORS,\n DARK_COLORS,\n type ThemeColors,\n} from \"@particle-academy/fancy-file-commons\";\nimport { cn } from \"../../utils/cn\";\nimport { useIsDark } from \"./use-is-dark\";\nimport type { CodeViewProps } from \"./CodeView.types\";\n\n// Shared text metrics — the overlay and the textarea MUST match exactly (font,\n// size, line-height, padding, wrapping) so the highlighted glyphs sit under the\n// caret. Keep this string identical on both layers.\nconst TEXT_METRICS = \"m-0 p-3 font-mono text-xs leading-relaxed\";\n\n/**\n * A lightweight, syntax-highlighted source view built on the pure highlight\n * primitives in `@particle-academy/fancy-file-commons` — a highlight overlay\n * with a transparent, auto-growing textarea on top; the container scrolls both\n * together. Fills its parent's height (`h-full`) and grows with content down to\n * `minHeight`. Highlights every language fancy-file-commons ships (see\n * `CodeViewProps.language`); anything else renders as plain text.\n */\nexport function CodeView({\n value,\n onChange,\n language = \"plaintext\",\n readOnly = false,\n placeholder,\n minHeight = 120,\n maxHeight,\n className,\n}: CodeViewProps) {\n const textareaRef = useRef<HTMLTextAreaElement | null>(null);\n const isDark = useIsDark();\n const colors: ThemeColors = isDark ? DARK_COLORS : LIGHT_COLORS;\n\n const highlighted = useMemo(() => {\n const tokens = tokenize(value, language) ?? [];\n return highlightCode(value, tokens, colors);\n }, [value, language, colors]);\n\n // Grow the textarea to its content height so it never scrolls internally —\n // the outer container owns scrolling and moves overlay + textarea in lockstep.\n useEffect(() => {\n const el = textareaRef.current;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n }, [value]);\n\n const editable = !readOnly && !!onChange;\n\n return (\n <div\n data-react-fancy-code-view=\"\"\n // No baked-in height: the consumer picks the fill mechanism — `flex-1`\n // inside a flex column (the Editor source body), or `h-full` when the\n // parent has a definite height. A hard-coded `h-full` here collapses to\n // content height under a flex-computed (indefinite) parent.\n className={cn(\"relative overflow-auto\", className)}\n style={{ backgroundColor: colors.background, color: colors.foreground, minHeight, maxHeight }}\n >\n <div className=\"relative min-h-full\">\n {/* Highlight overlay */}\n <pre\n aria-hidden=\"true\"\n className={cn(\"pointer-events-none absolute inset-0 overflow-hidden border-none\", TEXT_METRICS)}\n style={{ whiteSpace: \"pre-wrap\", overflowWrap: \"break-word\" }}\n >\n <code dangerouslySetInnerHTML={{ __html: highlighted + \"\\n\" }} />\n </pre>\n\n {/* Placeholder */}\n {placeholder && value.length === 0 && (\n <div className={cn(\"pointer-events-none absolute left-0 top-0 opacity-40\", TEXT_METRICS)}>\n {placeholder}\n </div>\n )}\n\n {/* Transparent input layer (caret + selection visible, glyphs from overlay) */}\n <textarea\n ref={textareaRef}\n data-react-fancy-code-view-input=\"\"\n value={value}\n onChange={editable ? (e) => onChange!(e.target.value) : undefined}\n readOnly={!editable}\n spellCheck={false}\n autoComplete=\"off\"\n autoCorrect=\"off\"\n autoCapitalize=\"off\"\n aria-label={placeholder}\n className={cn(\n \"relative block w-full resize-none border-none bg-transparent text-transparent outline-none\",\n TEXT_METRICS,\n )}\n style={{\n caretColor: colors.cursorColor,\n overflow: \"hidden\",\n whiteSpace: \"pre-wrap\",\n overflowWrap: \"break-word\",\n minHeight,\n }}\n />\n </div>\n </div>\n );\n}\n\nCodeView.displayName = \"CodeView\";\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/CodeBlock/CodeBlock.tsx"],"names":["CodeBlock"],"mappings":"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","file":"chunk-YRI6OFLY.js","sourcesContent":["import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n highlightCode,\n tokenize,\n LIGHT_COLORS,\n DARK_COLORS,\n type ThemeColors,\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 { useIsDark } from \"../CodeView/use-is-dark\";\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 * `tokenize` returns sorted, non-overlapping, in-bounds tokens, which\n * `highlightLines` relies on.\n */\nfunction tokensFor(code: string, language: string): Token[] {\n return tokenize(code, language) ?? [];\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 * `highlightCode` escapes every slice it emits, highlighted or not.\n */\nfunction highlightLines(code: string, tokens: Token[], colors: ThemeColors): 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(highlightCode(line, clipped, colors));\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 isDark = useIsDark();\n const colors: ThemeColors = isDark ? DARK_COLORS : LIGHT_COLORS;\n\n const tokens = useMemo(() => tokensFor(code, language), [code, language]);\n const html = useMemo(\n () => (showLineNumbers ? null : highlightCode(code, tokens, colors)),\n [code, tokens, colors, showLineNumbers],\n );\n const lines = useMemo(() => {\n if (!showLineNumbers) return null;\n const all = highlightLines(code, tokens, colors);\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, colors, 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: colors.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 className={cn(\n \"group relative overflow-hidden rounded-lg border\",\n className,\n )}\n style={{\n backgroundColor: colors.background,\n color: colors.foreground,\n borderColor: colors.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: colors.gutterBackground,\n color: colors.gutterForeground,\n borderColor: colors.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: colors.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"]}