@particle-academy/react-fancy 5.65.0 → 5.67.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/{PastePills.types-CDyotkUb.d.cts → PastePills.types-CpXzsuwJ.d.cts} +2 -0
  3. package/dist/{PastePills.types-CDyotkUb.d.ts → PastePills.types-CpXzsuwJ.d.ts} +2 -0
  4. package/dist/{PastedFilesInput-CrgNoEBd.d.ts → PastedFilesInput-Bt3KSTVN.d.ts} +1 -1
  5. package/dist/{PastedFilesInput-DQ1eZaHk.d.cts → PastedFilesInput-CUQeOeOA.d.cts} +1 -1
  6. package/dist/button.cjs +3 -4
  7. package/dist/button.js +1 -2
  8. package/dist/{chunk-COXHFZR6.js → chunk-23AVEYUP.js} +14 -8
  9. package/dist/chunk-23AVEYUP.js.map +1 -0
  10. package/dist/{chunk-C5YX5XVL.cjs → chunk-3U7PUZLI.cjs} +214 -4
  11. package/dist/chunk-3U7PUZLI.cjs.map +1 -0
  12. package/dist/{chunk-ATIDUWY6.cjs → chunk-4S5DFN5N.cjs} +12 -12
  13. package/dist/{chunk-ATIDUWY6.cjs.map → chunk-4S5DFN5N.cjs.map} +1 -1
  14. package/dist/{chunk-CMC2TOEQ.cjs → chunk-6CAXLP7D.cjs} +6 -6
  15. package/dist/{chunk-CMC2TOEQ.cjs.map → chunk-6CAXLP7D.cjs.map} +1 -1
  16. package/dist/{chunk-TQGRXJ74.cjs → chunk-7FYRFNPX.cjs} +9 -9
  17. package/dist/{chunk-TQGRXJ74.cjs.map → chunk-7FYRFNPX.cjs.map} +1 -1
  18. package/dist/{chunk-MVVHUTXA.js → chunk-B4LVQ4QI.js} +3 -3
  19. package/dist/{chunk-MVVHUTXA.js.map → chunk-B4LVQ4QI.js.map} +1 -1
  20. package/dist/{chunk-QP35Z2WB.js → chunk-BL5IKSB3.js} +3 -3
  21. package/dist/{chunk-QP35Z2WB.js.map → chunk-BL5IKSB3.js.map} +1 -1
  22. package/dist/{chunk-SZH7TFME.js → chunk-DCAD2JPP.js} +4 -4
  23. package/dist/{chunk-SZH7TFME.js.map → chunk-DCAD2JPP.js.map} +1 -1
  24. package/dist/{chunk-MCQJFMDP.cjs → chunk-E5UU666U.cjs} +478 -36
  25. package/dist/chunk-E5UU666U.cjs.map +1 -0
  26. package/dist/{chunk-L24LUSEE.js → chunk-FXOT4P4P.js} +478 -36
  27. package/dist/chunk-FXOT4P4P.js.map +1 -0
  28. package/dist/{chunk-CAUEKGMR.js → chunk-HEY4SMS2.js} +4 -4
  29. package/dist/{chunk-CAUEKGMR.js.map → chunk-HEY4SMS2.js.map} +1 -1
  30. package/dist/{chunk-QNQNGARW.cjs → chunk-HFK5SBNM.cjs} +27 -9
  31. package/dist/chunk-HFK5SBNM.cjs.map +1 -0
  32. package/dist/{chunk-TLRRMYNV.js → chunk-OXERDFFF.js} +212 -4
  33. package/dist/chunk-OXERDFFF.js.map +1 -0
  34. package/dist/{chunk-ELFTEJBT.js → chunk-QSGAFKK2.js} +3 -3
  35. package/dist/{chunk-ELFTEJBT.js.map → chunk-QSGAFKK2.js.map} +1 -1
  36. package/dist/{chunk-SYEHWUM5.cjs → chunk-RALJMI7C.cjs} +4 -4
  37. package/dist/{chunk-SYEHWUM5.cjs.map → chunk-RALJMI7C.cjs.map} +1 -1
  38. package/dist/{chunk-UM4JXBRZ.cjs → chunk-S3NB6LDV.cjs} +4 -4
  39. package/dist/{chunk-UM4JXBRZ.cjs.map → chunk-S3NB6LDV.cjs.map} +1 -1
  40. package/dist/{chunk-C463C4MM.cjs → chunk-SAG6DHQK.cjs} +22 -16
  41. package/dist/chunk-SAG6DHQK.cjs.map +1 -0
  42. package/dist/{chunk-4XHG6Q42.cjs → chunk-U5LKNGHU.cjs} +21 -21
  43. package/dist/{chunk-4XHG6Q42.cjs.map → chunk-U5LKNGHU.cjs.map} +1 -1
  44. package/dist/{chunk-M52S3TFI.js → chunk-WFR7HKJL.js} +3 -3
  45. package/dist/{chunk-M52S3TFI.js.map → chunk-WFR7HKJL.js.map} +1 -1
  46. package/dist/{chunk-NILA2LZY.js → chunk-YIKXJWO4.js} +27 -9
  47. package/dist/chunk-YIKXJWO4.js.map +1 -0
  48. package/dist/composer.cjs +4 -4
  49. package/dist/composer.d.cts +2 -2
  50. package/dist/composer.d.ts +2 -2
  51. package/dist/composer.js +2 -2
  52. package/dist/content-renderer.cjs +7 -5
  53. package/dist/content-renderer.d.cts +56 -4
  54. package/dist/content-renderer.d.ts +56 -4
  55. package/dist/content-renderer.js +4 -2
  56. package/dist/editor.cjs +6 -5
  57. package/dist/editor.d.cts +1 -1
  58. package/dist/editor.d.ts +1 -1
  59. package/dist/editor.js +4 -3
  60. package/dist/{extensions-6PVd3-vB.d.cts → extensions-DiByZIpD.d.cts} +26 -3
  61. package/dist/{extensions-6PVd3-vB.d.ts → extensions-DiByZIpD.d.ts} +26 -3
  62. package/dist/{index-CCJVT9Hu.d.cts → index-DWtSViJ3.d.cts} +7 -1
  63. package/dist/{index-DRlltbdV.d.ts → index-XR5t8bTq.d.ts} +7 -1
  64. package/dist/index.cjs +47 -48
  65. package/dist/index.cjs.map +1 -1
  66. package/dist/index.d.cts +5 -5
  67. package/dist/index.d.ts +5 -5
  68. package/dist/index.js +10 -11
  69. package/dist/index.js.map +1 -1
  70. package/dist/input.cjs +3 -3
  71. package/dist/input.d.cts +1 -1
  72. package/dist/input.d.ts +1 -1
  73. package/dist/input.js +2 -2
  74. package/dist/inputs.cjs +3 -3
  75. package/dist/inputs.d.cts +1 -1
  76. package/dist/inputs.d.ts +1 -1
  77. package/dist/inputs.js +2 -2
  78. package/dist/json-editor.cjs +15 -16
  79. package/dist/json-editor.js +4 -5
  80. package/dist/magic-wand.cjs +3 -4
  81. package/dist/magic-wand.js +2 -3
  82. package/dist/paste-pills.cjs +14 -14
  83. package/dist/paste-pills.d.cts +3 -3
  84. package/dist/paste-pills.d.ts +3 -3
  85. package/dist/paste-pills.js +1 -1
  86. package/dist/prompt-input.cjs +6 -7
  87. package/dist/prompt-input.d.cts +2 -2
  88. package/dist/prompt-input.d.ts +2 -2
  89. package/dist/prompt-input.js +3 -4
  90. package/dist/reason-tag.cjs +3 -4
  91. package/dist/reason-tag.js +2 -3
  92. package/docs/ContentRenderer.md +138 -3
  93. package/docs/PromptInput.md +24 -1
  94. package/package.json +1 -1
  95. package/dist/chunk-6TBRBN7H.js +0 -212
  96. package/dist/chunk-6TBRBN7H.js.map +0 -1
  97. package/dist/chunk-C463C4MM.cjs.map +0 -1
  98. package/dist/chunk-C5YX5XVL.cjs.map +0 -1
  99. package/dist/chunk-COXHFZR6.js.map +0 -1
  100. package/dist/chunk-L24LUSEE.js.map +0 -1
  101. package/dist/chunk-MCQJFMDP.cjs.map +0 -1
  102. package/dist/chunk-NILA2LZY.js.map +0 -1
  103. package/dist/chunk-QNQNGARW.cjs.map +0 -1
  104. package/dist/chunk-TLRRMYNV.js.map +0 -1
  105. package/dist/chunk-X2FC7ODX.cjs +0 -215
  106. package/dist/chunk-X2FC7ODX.cjs.map +0 -1
@@ -1,12 +1,33 @@
1
1
  'use strict';
2
2
 
3
- var chunkX2FC7ODX_cjs = require('./chunk-X2FC7ODX.cjs');
3
+ var chunk3U7PUZLI_cjs = require('./chunk-3U7PUZLI.cjs');
4
4
  var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
5
5
  var marked = require('marked');
6
6
  var react = require('react');
7
+ var reactDom = require('react-dom');
7
8
  var jsxRuntime = require('react/jsx-runtime');
8
9
 
9
10
  // src/components/ContentRenderer/extensions.ts
11
+ var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href"]);
12
+ function safeAttributes(attrs) {
13
+ const out = {};
14
+ for (const [name, value] of Object.entries(attrs)) {
15
+ const lower = name.toLowerCase();
16
+ if (lower.startsWith("on") || lower === "srcdoc") continue;
17
+ if (URL_ATTRS.has(lower)) {
18
+ const safe = chunk3U7PUZLI_cjs.sanitizeHref(value);
19
+ if (safe === void 0) continue;
20
+ out[name] = safe;
21
+ continue;
22
+ }
23
+ out[name] = value;
24
+ }
25
+ return out;
26
+ }
27
+ function tagAttributes(raw, unsafe) {
28
+ const attrs = raw ? parseAttributes(raw) : {};
29
+ return unsafe ? attrs : safeAttributes(attrs);
30
+ }
10
31
  var globalRegistry = [];
11
32
  function registerExtension(extension) {
12
33
  const idx = globalRegistry.findIndex(
@@ -88,7 +109,7 @@ function markdownToRawHtml(markdown) {
88
109
  return marked.marked.parse(markdown ?? "", { async: false });
89
110
  }
90
111
  function renderMarkdown(markdown, sanitize) {
91
- return chunkX2FC7ODX_cjs.sanitizeHtml(markdownToRawHtml(markdown), sanitize);
112
+ return chunk3U7PUZLI_cjs.sanitizeHtml(markdownToRawHtml(markdown), sanitize);
92
113
  }
93
114
  var proseClasses = [
94
115
  "[&_h1]:text-2xl [&_h1]:font-bold [&_h1]:mt-4 [&_h1]:mb-2",
@@ -813,67 +834,488 @@ function extensionEditorClasses(extensions) {
813
834
  ].join(" ");
814
835
  }).join(" ");
815
836
  }
816
- function RenderedContent({
817
- html,
818
- extensions: instanceExtensions,
819
- unsafe = false
837
+
838
+ // src/components/ContentRenderer/markdown-blocks.ts
839
+ var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
840
+ function escapeRegExp(s) {
841
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
842
+ }
843
+ function maskInlineCode(line) {
844
+ let out = "";
845
+ let i = 0;
846
+ while (i < line.length) {
847
+ if (line[i] !== "`") {
848
+ out += line[i];
849
+ i++;
850
+ continue;
851
+ }
852
+ let n = 0;
853
+ while (line[i + n] === "`") n++;
854
+ const fence = "`".repeat(n);
855
+ let search = i + n;
856
+ let close = -1;
857
+ while (search < line.length) {
858
+ const at = line.indexOf(fence, search);
859
+ if (at < 0) break;
860
+ let len = 0;
861
+ while (line[at + len] === "`") len++;
862
+ if (len === n) {
863
+ close = at;
864
+ break;
865
+ }
866
+ search = at + len;
867
+ }
868
+ if (close < 0) {
869
+ out += fence;
870
+ i += n;
871
+ continue;
872
+ }
873
+ out += " ".repeat(close + n - i);
874
+ i = close + n;
875
+ }
876
+ return out;
877
+ }
878
+ function scan(src, names, trackFences) {
879
+ const tokens = [];
880
+ let openFenceStart = -1;
881
+ if (names.length === 0 && !trackFences) return { tokens, openFenceStart };
882
+ const tagRe = names.length ? new RegExp(`<(\\/?)(${names.map(escapeRegExp).join("|")})(?=[\\s>/])([^>]*)>`, "gi") : null;
883
+ let fence = null;
884
+ let pos = 0;
885
+ while (pos <= src.length) {
886
+ const nl = src.indexOf("\n", pos);
887
+ const lineEnd = nl < 0 ? src.length : nl;
888
+ const line = src.slice(pos, lineEnd);
889
+ if (trackFences) {
890
+ if (fence) {
891
+ const close = new RegExp(`^ {0,3}${fence.char === "`" ? "`" : "~"}{${fence.len},}\\s*$`);
892
+ if (close.test(line)) {
893
+ fence = null;
894
+ openFenceStart = -1;
895
+ }
896
+ pos = lineEnd + 1;
897
+ if (nl < 0) break;
898
+ continue;
899
+ }
900
+ const m = FENCE_OPEN.exec(line);
901
+ if (m && !(m[1][0] === "`" && m[2].includes("`"))) {
902
+ fence = { char: m[1][0], len: m[1].length };
903
+ openFenceStart = pos;
904
+ pos = lineEnd + 1;
905
+ if (nl < 0) break;
906
+ continue;
907
+ }
908
+ }
909
+ if (tagRe) {
910
+ const masked = trackFences ? maskInlineCode(line) : line;
911
+ const indent2 = /^ {0,3}/.exec(masked)[0].length;
912
+ tagRe.lastIndex = 0;
913
+ let t;
914
+ while ((t = tagRe.exec(masked)) !== null) {
915
+ const isClose = t[1] === "/";
916
+ const attrs = t[3];
917
+ if (isClose && attrs.trim() !== "") continue;
918
+ if (!isClose && attrs.trimEnd().endsWith("/")) continue;
919
+ tokens.push({
920
+ kind: isClose ? "close" : "open",
921
+ name: t[2].toLowerCase(),
922
+ start: pos + t.index,
923
+ end: pos + t.index + t[0].length,
924
+ attrs: isClose ? "" : attrs.trim(),
925
+ lineStart: t.index === indent2
926
+ });
927
+ }
928
+ }
929
+ if (nl < 0) break;
930
+ pos = lineEnd + 1;
931
+ }
932
+ return { tokens, openFenceStart };
933
+ }
934
+ function splitMarkdownBlocks(src, tags) {
935
+ const names = tags.map((t) => t.toLowerCase());
936
+ if (names.length === 0) return src ? [{ type: "markdown", source: src }] : [];
937
+ const { tokens } = scan(src, names, true);
938
+ const blocks = [];
939
+ let cursor = 0;
940
+ let i = 0;
941
+ while (i < tokens.length) {
942
+ const open = tokens[i];
943
+ if (open.kind !== "open" || !open.lineStart || open.start < cursor) {
944
+ i++;
945
+ continue;
946
+ }
947
+ let depth = 0;
948
+ let closeIdx = -1;
949
+ for (let j = i; j < tokens.length; j++) {
950
+ const t = tokens[j];
951
+ if (t.name !== open.name) continue;
952
+ depth += t.kind === "open" ? 1 : -1;
953
+ if (depth === 0) {
954
+ closeIdx = j;
955
+ break;
956
+ }
957
+ }
958
+ if (closeIdx < 0) {
959
+ i++;
960
+ continue;
961
+ }
962
+ const close = tokens[closeIdx];
963
+ if (open.start > cursor) blocks.push({ type: "markdown", source: src.slice(cursor, open.start) });
964
+ blocks.push({ type: "tag", tag: open.name, attrs: open.attrs, body: src.slice(open.end, close.start) });
965
+ cursor = close.end;
966
+ i = closeIdx + 1;
967
+ }
968
+ if (cursor < src.length) blocks.push({ type: "markdown", source: src.slice(cursor) });
969
+ return blocks;
970
+ }
971
+ function streamingCut(src, tags, markdown) {
972
+ const names = tags.map((t) => t.toLowerCase());
973
+ const { tokens, openFenceStart } = scan(src, names, markdown);
974
+ let cut = src.length;
975
+ if (openFenceStart >= 0) cut = Math.min(cut, openFenceStart);
976
+ const stack = [];
977
+ for (const t of tokens) {
978
+ if (t.start >= cut) break;
979
+ if (t.kind === "open") {
980
+ stack.push(t);
981
+ continue;
982
+ }
983
+ for (let k = stack.length - 1; k >= 0; k--) {
984
+ if (stack[k].name === t.name) {
985
+ stack.length = k;
986
+ break;
987
+ }
988
+ }
989
+ }
990
+ if (stack.length) cut = Math.min(cut, stack[0].start);
991
+ const lastLineStart = src.lastIndexOf("\n", cut - 1) + 1;
992
+ const lastLine = src.slice(lastLineStart, cut);
993
+ if (markdown && /^ {0,3}(`{1,2}|~{1,2})$/.test(lastLine)) {
994
+ return Math.min(cut, lastLineStart);
995
+ }
996
+ if (names.length) {
997
+ const masked = markdown ? maskInlineCode(lastLine) : lastLine;
998
+ const lt = masked.lastIndexOf("<");
999
+ if (lt >= 0) {
1000
+ const m = /^<(\/?)([A-Za-z][\w-]*)?([^>]*)$/.exec(masked.slice(lt));
1001
+ if (m) {
1002
+ const name = (m[2] ?? "").toLowerCase();
1003
+ const rest = m[3];
1004
+ let partial = false;
1005
+ if (!name) partial = rest === "";
1006
+ else if (names.includes(name)) partial = rest === "" || /^\s/.test(rest);
1007
+ else partial = rest === "" && names.some((n) => n.startsWith(name));
1008
+ if (partial) cut = Math.min(cut, lastLineStart + lt);
1009
+ }
1010
+ }
1011
+ }
1012
+ return cut;
1013
+ }
1014
+ var FEEDBACK_MS = 2e3;
1015
+ var WRITE_TIMEOUT_MS = 5e3;
1016
+ var CopyContext = react.createContext(null);
1017
+ async function defaultClipboardWriter(payload) {
1018
+ const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
1019
+ if (!clip) throw new Error("Clipboard API unavailable");
1020
+ const Item = globalThis.ClipboardItem;
1021
+ if (payload.html && Item && typeof clip.write === "function") {
1022
+ try {
1023
+ await clip.write([
1024
+ new Item({
1025
+ "text/html": new Blob([payload.html], { type: "text/html" }),
1026
+ "text/plain": new Blob([payload.text], { type: "text/plain" })
1027
+ })
1028
+ ]);
1029
+ return;
1030
+ } catch {
1031
+ }
1032
+ }
1033
+ if (typeof clip.writeText !== "function") throw new Error("Clipboard writeText unavailable");
1034
+ await clip.writeText(payload.text);
1035
+ }
1036
+ async function runWriter(writer, payload) {
1037
+ let timer;
1038
+ try {
1039
+ const result = await Promise.race([
1040
+ Promise.resolve().then(() => writer(payload)),
1041
+ new Promise((resolve) => {
1042
+ timer = setTimeout(() => resolve(false), WRITE_TIMEOUT_MS);
1043
+ })
1044
+ ]);
1045
+ return result !== false;
1046
+ } catch {
1047
+ return false;
1048
+ } finally {
1049
+ if (timer) clearTimeout(timer);
1050
+ }
1051
+ }
1052
+ function codeText(pre, fromMarkdown) {
1053
+ const text = (pre.querySelector("code") ?? pre).textContent ?? "";
1054
+ return fromMarkdown && text.endsWith("\n") ? text.slice(0, -1) : text;
1055
+ }
1056
+ function tableTsv(table) {
1057
+ const rows = [];
1058
+ for (const tr of Array.from(table.querySelectorAll("tr"))) {
1059
+ if (tr.closest("table") !== table) continue;
1060
+ const cells = Array.from(tr.children).filter((c) => c.tagName === "TD" || c.tagName === "TH");
1061
+ rows.push(cells.map((c) => cellText(c)).join(" "));
1062
+ }
1063
+ return rows.join("\n");
1064
+ }
1065
+ function cellText(cell) {
1066
+ const clone = cell.cloneNode(true);
1067
+ for (const br of Array.from(clone.querySelectorAll("br"))) br.replaceWith(" ");
1068
+ return (clone.textContent ?? "").replace(/[\t\r\n]+/g, " ").replace(/ {2,}/g, " ").trim();
1069
+ }
1070
+ function HtmlChunk({ html, fromMarkdown }) {
1071
+ const ref = react.useRef(null);
1072
+ const ctx = react.useContext(CopyContext);
1073
+ const [targets, setTargets] = react.useState([]);
1074
+ const code = ctx?.code ?? false;
1075
+ const table = ctx?.table ?? false;
1076
+ const inner = react.useMemo(() => ({ __html: html }), [html]);
1077
+ react.useLayoutEffect(() => {
1078
+ const root = ref.current;
1079
+ if (!root || !code && !table) {
1080
+ setTargets([]);
1081
+ return;
1082
+ }
1083
+ const found = [];
1084
+ const selector = [code && "pre", table && "table"].filter(Boolean).join(",");
1085
+ for (const el of Array.from(root.querySelectorAll(selector))) {
1086
+ const kind = el.tagName === "PRE" ? "code" : "table";
1087
+ const wrapper = document.createElement("div");
1088
+ wrapper.setAttribute("data-react-fancy-content-copy-block", kind);
1089
+ wrapper.className = kind === "code" ? "group relative" : "group relative flex flex-col";
1090
+ const host = document.createElement("div");
1091
+ host.setAttribute("data-react-fancy-content-copy-host", "");
1092
+ host.className = kind === "code" ? "absolute right-2 top-2 z-10" : "flex justify-end";
1093
+ el.replaceWith(wrapper);
1094
+ if (kind === "table") wrapper.append(host, el);
1095
+ else wrapper.append(el, host);
1096
+ found.push({ kind, el, host });
1097
+ }
1098
+ setTargets(found);
1099
+ return () => {
1100
+ for (const t of found) {
1101
+ const wrapper = t.host.parentElement;
1102
+ if (wrapper?.parentNode) wrapper.replaceWith(t.el);
1103
+ }
1104
+ };
1105
+ }, [html, code, table]);
1106
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1107
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref, dangerouslySetInnerHTML: inner }),
1108
+ targets.map(
1109
+ (t, i) => reactDom.createPortal(
1110
+ /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { kind: t.kind, target: t.el, fromMarkdown }),
1111
+ t.host,
1112
+ `${i}`
1113
+ )
1114
+ )
1115
+ ] });
1116
+ }
1117
+ function CopyButton({
1118
+ kind,
1119
+ target,
1120
+ fromMarkdown
820
1121
  }) {
821
- const extensions = react.useMemo(
822
- () => mergeExtensions(instanceExtensions),
823
- [instanceExtensions]
1122
+ const ctx = react.useContext(CopyContext);
1123
+ const [state, setState] = react.useState("idle");
1124
+ const timer = react.useRef(void 0);
1125
+ const btn = react.useRef(null);
1126
+ react.useEffect(() => () => clearTimeout(timer.current), []);
1127
+ const onClick = react.useCallback(async () => {
1128
+ if (!ctx || !btn.current) return;
1129
+ const text = kind === "code" ? codeText(target, fromMarkdown) : tableTsv(target);
1130
+ const ok = await ctx.copy({ kind, text, html: target.outerHTML }, btn.current);
1131
+ setState(ok ? "copied" : "failed");
1132
+ clearTimeout(timer.current);
1133
+ timer.current = setTimeout(() => setState("idle"), FEEDBACK_MS);
1134
+ }, [ctx, kind, target, fromMarkdown]);
1135
+ const idleLabel = kind === "code" ? "Copy code" : "Copy table";
1136
+ const label = state === "copied" ? "Copied" : state === "failed" ? "Copy failed" : idleLabel;
1137
+ return /* @__PURE__ */ jsxRuntime.jsx(
1138
+ chunk3U7PUZLI_cjs.Button,
1139
+ {
1140
+ ref: btn,
1141
+ type: "button",
1142
+ size: "xs",
1143
+ variant: "ghost",
1144
+ icon: state === "copied" ? "check" : state === "failed" ? "x" : "copy",
1145
+ "aria-label": label,
1146
+ title: label,
1147
+ "data-react-fancy-content-copy": kind,
1148
+ "data-copy-state": state,
1149
+ onClick,
1150
+ className: kind === "code" ? "text-zinc-300 hover:bg-zinc-800 hover:text-white opacity-70 group-hover:opacity-100 focus-visible:opacity-100" : "opacity-70 group-hover:opacity-100 focus-visible:opacity-100"
1151
+ }
824
1152
  );
825
- const segments = react.useMemo(
826
- () => parseSegments(html, extensions),
827
- [html, extensions]
1153
+ }
1154
+ function useCopyPipeline({
1155
+ writer,
1156
+ onCopy,
1157
+ rootRef,
1158
+ announce
1159
+ }) {
1160
+ const latest = react.useRef({ writer, onCopy, announce });
1161
+ latest.current = { writer, onCopy, announce };
1162
+ return react.useCallback(
1163
+ async (payload, button) => {
1164
+ const { writer: w, onCopy: report, announce: say } = latest.current;
1165
+ const ok = await runWriter(w ?? defaultClipboardWriter, payload);
1166
+ say(ok ? "Copied" : "Copy failed");
1167
+ const all = rootRef.current ? Array.from(rootRef.current.querySelectorAll("[data-react-fancy-content-copy]")) : [];
1168
+ report?.({ ...payload, index: all.indexOf(button), ok });
1169
+ return ok;
1170
+ },
1171
+ [rootRef]
828
1172
  );
829
- const renderHtml = (content) => unsafe ? content : chunkX2FC7ODX_cjs.sanitizeHtml(content);
830
- if (segments.length === 1 && segments[0].type === "html") {
831
- return /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: renderHtml(segments[0].content) } });
1173
+ }
1174
+ var MAX_DEPTH = 8;
1175
+ function ContentBody({ source, format, extensions, unsafe, depth = 0 }) {
1176
+ const active = depth >= MAX_DEPTH ? [] : extensions;
1177
+ const blocks = react.useMemo(() => {
1178
+ if (format !== "markdown") return null;
1179
+ const mdTags = active.filter((e) => e.markdown).map((e) => e.tag);
1180
+ return splitMarkdownBlocks(source, mdTags);
1181
+ }, [source, format, active]);
1182
+ if (!blocks) {
1183
+ const html = unsafe ? source : chunk3U7PUZLI_cjs.sanitizeHtml(source);
1184
+ return /* @__PURE__ */ jsxRuntime.jsx(RenderedContent, { html, extensions: active, unsafe, fromMarkdown: false, depth });
832
1185
  }
833
- if (segments.length === 0) {
834
- return null;
1186
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: blocks.map((block, i) => {
1187
+ if (block.type === "markdown") {
1188
+ const raw = markdownToRawHtml(block.source);
1189
+ const html = unsafe ? raw : chunk3U7PUZLI_cjs.sanitizeHtml(raw);
1190
+ return /* @__PURE__ */ jsxRuntime.jsx(
1191
+ RenderedContent,
1192
+ {
1193
+ html,
1194
+ extensions: active,
1195
+ unsafe,
1196
+ fromMarkdown: true,
1197
+ depth
1198
+ },
1199
+ i
1200
+ );
1201
+ }
1202
+ const ext = active.find((e) => e.tag.toLowerCase() === block.tag);
1203
+ if (!ext) return null;
1204
+ const Component = ext.component;
1205
+ const Wrapper = ext.block !== false ? "div" : "span";
1206
+ return /* @__PURE__ */ jsxRuntime.jsx(Wrapper, { "data-render-extension": block.tag, children: /* @__PURE__ */ jsxRuntime.jsx(Component, { content: block.body, attributes: tagAttributes(block.attrs, unsafe), children: /* @__PURE__ */ jsxRuntime.jsx(
1207
+ ContentBody,
1208
+ {
1209
+ source: block.body,
1210
+ format: "markdown",
1211
+ extensions: active,
1212
+ unsafe,
1213
+ depth: depth + 1
1214
+ }
1215
+ ) }) }, i);
1216
+ }) });
1217
+ }
1218
+ function RenderedContent({ html, extensions, unsafe, fromMarkdown, depth }) {
1219
+ const segments = react.useMemo(() => parseSegments(html, extensions), [html, extensions]);
1220
+ const renderHtml = (content) => unsafe ? content : chunk3U7PUZLI_cjs.sanitizeHtml(content);
1221
+ if (segments.length === 0) return null;
1222
+ if (segments.length === 1 && segments[0].type === "html") {
1223
+ return /* @__PURE__ */ jsxRuntime.jsx(HtmlChunk, { html: renderHtml(segments[0].content), fromMarkdown });
835
1224
  }
836
1225
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: segments.map((segment, i) => {
837
1226
  if (segment.type === "html") {
838
- return segment.content ? /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: renderHtml(segment.content) } }, i) : null;
1227
+ return segment.content ? /* @__PURE__ */ jsxRuntime.jsx(HtmlChunk, { html: renderHtml(segment.content), fromMarkdown }, i) : null;
839
1228
  }
840
- const ext = extensions.find(
841
- (e) => e.tag.toLowerCase() === segment.tag
842
- );
1229
+ const ext = extensions.find((e) => e.tag.toLowerCase() === segment.tag);
843
1230
  if (!ext) return null;
844
1231
  const Component = ext.component;
845
- const isBlock = ext.block !== false;
846
- const Wrapper = isBlock ? "div" : "span";
847
- return /* @__PURE__ */ jsxRuntime.jsx(Wrapper, { "data-render-extension": segment.tag, children: /* @__PURE__ */ jsxRuntime.jsx(Component, { content: segment.content, attributes: segment.attributes }) }, i);
1232
+ const Wrapper = ext.block !== false ? "div" : "span";
1233
+ const children = ext.markdown ? /* @__PURE__ */ jsxRuntime.jsx(
1234
+ ContentBody,
1235
+ {
1236
+ source: segment.content,
1237
+ format: "html",
1238
+ extensions,
1239
+ unsafe,
1240
+ depth: depth + 1
1241
+ }
1242
+ ) : void 0;
1243
+ return /* @__PURE__ */ jsxRuntime.jsx(Wrapper, { "data-render-extension": segment.tag, children: /* @__PURE__ */ jsxRuntime.jsx(Component, { content: segment.content, attributes: segment.attributes, children }) }, i);
848
1244
  }) });
849
1245
  }
850
1246
  RenderedContent.displayName = "RenderedContent";
1247
+ ContentBody.displayName = "ContentBody";
851
1248
  function ContentRenderer({
852
1249
  value,
853
1250
  format = "auto",
854
1251
  lineSpacing = 1.6,
855
1252
  className,
856
1253
  extensions: instanceExtensions,
857
- unsafe = false
1254
+ unsafe = false,
1255
+ copyButtons = true,
1256
+ onCopy,
1257
+ clipboardWriter,
1258
+ streaming = false
858
1259
  }) {
859
1260
  const extensions = react.useMemo(
860
1261
  () => mergeExtensions(instanceExtensions),
861
1262
  [instanceExtensions]
862
1263
  );
863
- const html = react.useMemo(() => {
864
- const safe = value ?? "";
865
- const resolvedFormat = format === "auto" ? detectFormat(safe) : format;
866
- const raw = resolvedFormat === "markdown" ? markdownToRawHtml(safe) : safe;
867
- return unsafe ? raw : chunkX2FC7ODX_cjs.sanitizeHtml(raw);
868
- }, [value, format, unsafe]);
869
- const hasExtensions = extensions.length > 0;
870
- return /* @__PURE__ */ jsxRuntime.jsx(
1264
+ const safe = value ?? "";
1265
+ const resolvedFormat = format === "auto" ? detectFormat(safe) : format;
1266
+ const source = react.useMemo(() => {
1267
+ if (!streaming) return safe;
1268
+ const cut = streamingCut(
1269
+ safe,
1270
+ extensions.map((e) => e.tag),
1271
+ resolvedFormat === "markdown"
1272
+ );
1273
+ return safe.slice(0, cut);
1274
+ }, [safe, streaming, extensions, resolvedFormat]);
1275
+ const codeOn = copyButtons === true || typeof copyButtons === "object" && copyButtons.code !== false;
1276
+ const tableOn = copyButtons === true || typeof copyButtons === "object" && copyButtons.table !== false;
1277
+ const copyOn = codeOn || tableOn;
1278
+ const rootRef = react.useRef(null);
1279
+ const [announcement, setAnnouncement] = react.useState("");
1280
+ const announce = react.useCallback((message) => {
1281
+ setAnnouncement("");
1282
+ queueMicrotask(() => setAnnouncement(message));
1283
+ }, []);
1284
+ const copy = useCopyPipeline({ writer: clipboardWriter, onCopy, rootRef, announce });
1285
+ const copyContext = react.useMemo(
1286
+ () => ({ code: codeOn, table: tableOn, copy }),
1287
+ [codeOn, tableOn, copy]
1288
+ );
1289
+ return /* @__PURE__ */ jsxRuntime.jsxs(
871
1290
  "div",
872
1291
  {
1292
+ ref: rootRef,
873
1293
  "data-react-fancy-content-renderer": "",
1294
+ "data-streaming": streaming ? "true" : void 0,
874
1295
  style: { lineHeight: lineSpacing },
875
1296
  className: chunkZU5Q77OT_cjs.cn("text-sm", proseClasses, className),
876
- children: hasExtensions ? /* @__PURE__ */ jsxRuntime.jsx(RenderedContent, { html, extensions, unsafe }) : /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: html } })
1297
+ children: [
1298
+ /* @__PURE__ */ jsxRuntime.jsx(CopyContext.Provider, { value: copyContext, children: /* @__PURE__ */ jsxRuntime.jsx(
1299
+ ContentBody,
1300
+ {
1301
+ source,
1302
+ format: resolvedFormat,
1303
+ extensions,
1304
+ unsafe
1305
+ }
1306
+ ) }),
1307
+ copyOn && /* @__PURE__ */ jsxRuntime.jsx(
1308
+ "span",
1309
+ {
1310
+ role: "status",
1311
+ "aria-live": "polite",
1312
+ "aria-atomic": "true",
1313
+ "data-react-fancy-content-renderer-live": "",
1314
+ className: "sr-only",
1315
+ children: announcement
1316
+ }
1317
+ )
1318
+ ]
877
1319
  }
878
1320
  );
879
1321
  }
@@ -889,5 +1331,5 @@ exports.proseClasses = proseClasses;
889
1331
  exports.registerExtension = registerExtension;
890
1332
  exports.registerExtensions = registerExtensions;
891
1333
  exports.renderMarkdown = renderMarkdown;
892
- //# sourceMappingURL=chunk-MCQJFMDP.cjs.map
893
- //# sourceMappingURL=chunk-MCQJFMDP.cjs.map
1334
+ //# sourceMappingURL=chunk-E5UU666U.cjs.map
1335
+ //# sourceMappingURL=chunk-E5UU666U.cjs.map