@particle-academy/react-fancy 5.66.0 → 5.68.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 (70) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/ContentRenderer.types-RO_l7_6N.d.cts +137 -0
  3. package/dist/ContentRenderer.types-RO_l7_6N.d.ts +137 -0
  4. package/dist/button.cjs +3 -4
  5. package/dist/button.js +1 -2
  6. package/dist/{chunk-J44EV6SP.js → chunk-23AVEYUP.js} +3 -3
  7. package/dist/{chunk-J44EV6SP.js.map → chunk-23AVEYUP.js.map} +1 -1
  8. package/dist/{chunk-L24LUSEE.js → chunk-2LQR4EAN.js} +553 -35
  9. package/dist/chunk-2LQR4EAN.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-MCQJFMDP.cjs → chunk-464QSZ4A.cjs} +553 -35
  13. package/dist/chunk-464QSZ4A.cjs.map +1 -0
  14. package/dist/{chunk-QP35Z2WB.js → chunk-BL5IKSB3.js} +3 -3
  15. package/dist/{chunk-QP35Z2WB.js.map → chunk-BL5IKSB3.js.map} +1 -1
  16. package/dist/{chunk-4EUDW2WU.js → chunk-DCAD2JPP.js} +3 -3
  17. package/dist/{chunk-4EUDW2WU.js.map → chunk-DCAD2JPP.js.map} +1 -1
  18. package/dist/{chunk-ATIDUWY6.cjs → chunk-E4DXICU3.cjs} +19 -13
  19. package/dist/chunk-E4DXICU3.cjs.map +1 -0
  20. package/dist/{chunk-TLRRMYNV.js → chunk-OXERDFFF.js} +212 -4
  21. package/dist/chunk-OXERDFFF.js.map +1 -0
  22. package/dist/{chunk-SYEHWUM5.cjs → chunk-RALJMI7C.cjs} +4 -4
  23. package/dist/{chunk-SYEHWUM5.cjs.map → chunk-RALJMI7C.cjs.map} +1 -1
  24. package/dist/{chunk-UM4JXBRZ.cjs → chunk-S3NB6LDV.cjs} +4 -4
  25. package/dist/{chunk-UM4JXBRZ.cjs.map → chunk-S3NB6LDV.cjs.map} +1 -1
  26. package/dist/{chunk-MCQWTOTV.cjs → chunk-SAG6DHQK.cjs} +6 -6
  27. package/dist/{chunk-MCQWTOTV.cjs.map → chunk-SAG6DHQK.cjs.map} +1 -1
  28. package/dist/{chunk-CAUEKGMR.js → chunk-TQ77ZWBD.js} +11 -5
  29. package/dist/chunk-TQ77ZWBD.js.map +1 -0
  30. package/dist/{chunk-7HXFEYOV.cjs → chunk-U5LKNGHU.cjs} +15 -15
  31. package/dist/{chunk-7HXFEYOV.cjs.map → chunk-U5LKNGHU.cjs.map} +1 -1
  32. package/dist/{chunk-M52S3TFI.js → chunk-WFR7HKJL.js} +3 -3
  33. package/dist/{chunk-M52S3TFI.js.map → chunk-WFR7HKJL.js.map} +1 -1
  34. package/dist/content-renderer.cjs +7 -5
  35. package/dist/content-renderer.d.cts +4 -21
  36. package/dist/content-renderer.d.ts +4 -21
  37. package/dist/content-renderer.js +4 -2
  38. package/dist/editor.cjs +6 -5
  39. package/dist/editor.d.cts +19 -2
  40. package/dist/editor.d.ts +19 -2
  41. package/dist/editor.js +4 -3
  42. package/dist/index.cjs +37 -38
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +2 -2
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.js +7 -8
  47. package/dist/index.js.map +1 -1
  48. package/dist/json-editor.cjs +13 -14
  49. package/dist/json-editor.js +2 -3
  50. package/dist/magic-wand.cjs +3 -4
  51. package/dist/magic-wand.js +2 -3
  52. package/dist/prompt-input.cjs +5 -6
  53. package/dist/prompt-input.js +2 -3
  54. package/dist/reason-tag.cjs +3 -4
  55. package/dist/reason-tag.js +2 -3
  56. package/docs/ContentRenderer.md +192 -3
  57. package/docs/Editor.md +38 -0
  58. package/package.json +1 -1
  59. package/dist/chunk-6TBRBN7H.js +0 -212
  60. package/dist/chunk-6TBRBN7H.js.map +0 -1
  61. package/dist/chunk-ATIDUWY6.cjs.map +0 -1
  62. package/dist/chunk-C5YX5XVL.cjs.map +0 -1
  63. package/dist/chunk-CAUEKGMR.js.map +0 -1
  64. package/dist/chunk-L24LUSEE.js.map +0 -1
  65. package/dist/chunk-MCQJFMDP.cjs.map +0 -1
  66. package/dist/chunk-TLRRMYNV.js.map +0 -1
  67. package/dist/chunk-X2FC7ODX.cjs +0 -215
  68. package/dist/chunk-X2FC7ODX.cjs.map +0 -1
  69. package/dist/extensions-6PVd3-vB.d.cts +0 -38
  70. package/dist/extensions-6PVd3-vB.d.ts +0 -38
@@ -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,564 @@ function extensionEditorClasses(extensions) {
813
834
  ].join(" ");
814
835
  }).join(" ");
815
836
  }
816
- function RenderedContent({
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, openFence: null };
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, openFence: fence };
933
+ }
934
+ function partialTagAt(lastLine, names, markdown) {
935
+ if (!names.length) return -1;
936
+ const masked = markdown ? maskInlineCode(lastLine) : lastLine;
937
+ const lt = masked.lastIndexOf("<");
938
+ if (lt < 0) return -1;
939
+ const m = /^<(\/?)([A-Za-z][\w-]*)?([^>]*)$/.exec(masked.slice(lt));
940
+ if (!m) return -1;
941
+ const name = (m[2] ?? "").toLowerCase();
942
+ const rest = m[3];
943
+ let partial = false;
944
+ if (!name) partial = rest === "";
945
+ else if (names.includes(name)) partial = rest === "" || /^\s/.test(rest);
946
+ else partial = rest === "" && names.some((n) => n.startsWith(name));
947
+ return partial ? lt : -1;
948
+ }
949
+ function openTagsBefore(tokens, cut) {
950
+ const stack = [];
951
+ for (const t of tokens) {
952
+ if (t.start >= cut) break;
953
+ if (t.kind === "open") {
954
+ stack.push(t);
955
+ continue;
956
+ }
957
+ for (let k = stack.length - 1; k >= 0; k--) {
958
+ if (stack[k].name === t.name) {
959
+ stack.length = k;
960
+ break;
961
+ }
962
+ }
963
+ }
964
+ return stack;
965
+ }
966
+ function splitMarkdownBlocks(src, tags) {
967
+ const names = tags.map((t) => t.toLowerCase());
968
+ if (names.length === 0) return src ? [{ type: "markdown", source: src }] : [];
969
+ const { tokens } = scan(src, names, true);
970
+ const blocks = [];
971
+ let cursor = 0;
972
+ let i = 0;
973
+ while (i < tokens.length) {
974
+ const open = tokens[i];
975
+ if (open.kind !== "open" || !open.lineStart || open.start < cursor) {
976
+ i++;
977
+ continue;
978
+ }
979
+ let depth = 0;
980
+ let closeIdx = -1;
981
+ for (let j = i; j < tokens.length; j++) {
982
+ const t = tokens[j];
983
+ if (t.name !== open.name) continue;
984
+ depth += t.kind === "open" ? 1 : -1;
985
+ if (depth === 0) {
986
+ closeIdx = j;
987
+ break;
988
+ }
989
+ }
990
+ if (closeIdx < 0) {
991
+ i++;
992
+ continue;
993
+ }
994
+ const close = tokens[closeIdx];
995
+ if (open.start > cursor) blocks.push({ type: "markdown", source: src.slice(cursor, open.start) });
996
+ blocks.push({ type: "tag", tag: open.name, attrs: open.attrs, body: src.slice(open.end, close.start) });
997
+ cursor = close.end;
998
+ i = closeIdx + 1;
999
+ }
1000
+ if (cursor < src.length) blocks.push({ type: "markdown", source: src.slice(cursor) });
1001
+ return blocks;
1002
+ }
1003
+ function streamingCut(src, tags, markdown) {
1004
+ const names = tags.map((t) => t.toLowerCase());
1005
+ const { tokens, openFenceStart } = scan(src, names, markdown);
1006
+ let cut = src.length;
1007
+ if (openFenceStart >= 0) cut = Math.min(cut, openFenceStart);
1008
+ const stack = openTagsBefore(tokens, cut);
1009
+ if (stack.length) cut = Math.min(cut, stack[0].start);
1010
+ const lastLineStart = src.lastIndexOf("\n", cut - 1) + 1;
1011
+ const lastLine = src.slice(lastLineStart, cut);
1012
+ if (markdown && /^ {0,3}(`{1,2}|~{1,2})$/.test(lastLine)) {
1013
+ return Math.min(cut, lastLineStart);
1014
+ }
1015
+ const lt = partialTagAt(lastLine, names, markdown);
1016
+ if (lt >= 0) cut = Math.min(cut, lastLineStart + lt);
1017
+ return cut;
1018
+ }
1019
+ function streamingProgressive(src, tags, markdown) {
1020
+ const names = tags.map((t) => t.toLowerCase());
1021
+ const { tokens, openFenceStart, openFence } = scan(src, names, markdown);
1022
+ let cut = src.length;
1023
+ let fence = openFenceStart >= 0 ? openFence : null;
1024
+ const lastLineStart = src.lastIndexOf("\n", cut - 1) + 1;
1025
+ const lastLine = src.slice(lastLineStart);
1026
+ if (fence) {
1027
+ if (openFenceStart === lastLineStart) {
1028
+ cut = openFenceStart;
1029
+ fence = null;
1030
+ } else {
1031
+ const m = /^ {0,3}(`+|~+)$/.exec(lastLine);
1032
+ if (m && m[1][0] === fence.char && m[1].length < fence.len) cut = lastLineStart;
1033
+ }
1034
+ } else if (markdown && /^ {0,3}(`{1,2}|~{1,2})$/.test(lastLine)) {
1035
+ cut = lastLineStart;
1036
+ } else {
1037
+ const lt = partialTagAt(lastLine, names, markdown);
1038
+ if (lt >= 0) cut = lastLineStart + lt;
1039
+ }
1040
+ let out = src.slice(0, cut);
1041
+ if (fence) {
1042
+ out += (out.endsWith("\n") ? "" : "\n") + fence.char.repeat(fence.len) + "\n";
1043
+ }
1044
+ const stack = openTagsBefore(tokens, fence ? openFenceStart : cut);
1045
+ for (let k = stack.length - 1; k >= 0; k--) out += `</${stack[k].name}>`;
1046
+ return { source: out, openFence: fence !== null };
1047
+ }
1048
+ var FEEDBACK_MS = 2e3;
1049
+ var WRITE_TIMEOUT_MS = 5e3;
1050
+ var CopyContext = react.createContext(null);
1051
+ async function defaultClipboardWriter(payload) {
1052
+ const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
1053
+ if (!clip) throw new Error("Clipboard API unavailable");
1054
+ const Item = globalThis.ClipboardItem;
1055
+ if (payload.html && Item && typeof clip.write === "function") {
1056
+ try {
1057
+ await clip.write([
1058
+ new Item({
1059
+ "text/html": new Blob([payload.html], { type: "text/html" }),
1060
+ "text/plain": new Blob([payload.text], { type: "text/plain" })
1061
+ })
1062
+ ]);
1063
+ return;
1064
+ } catch {
1065
+ }
1066
+ }
1067
+ if (typeof clip.writeText !== "function") throw new Error("Clipboard writeText unavailable");
1068
+ await clip.writeText(payload.text);
1069
+ }
1070
+ async function runWriter(writer, payload) {
1071
+ let timer;
1072
+ try {
1073
+ const result = await Promise.race([
1074
+ Promise.resolve().then(() => writer(payload)),
1075
+ new Promise((resolve) => {
1076
+ timer = setTimeout(() => resolve(false), WRITE_TIMEOUT_MS);
1077
+ })
1078
+ ]);
1079
+ return result !== false;
1080
+ } catch {
1081
+ return false;
1082
+ } finally {
1083
+ if (timer) clearTimeout(timer);
1084
+ }
1085
+ }
1086
+ function codeText(pre, fromMarkdown) {
1087
+ const text = (pre.querySelector("code") ?? pre).textContent ?? "";
1088
+ return fromMarkdown && text.endsWith("\n") ? text.slice(0, -1) : text;
1089
+ }
1090
+ function tableTsv(table) {
1091
+ const rows = [];
1092
+ for (const tr of Array.from(table.querySelectorAll("tr"))) {
1093
+ if (tr.closest("table") !== table) continue;
1094
+ const cells = Array.from(tr.children).filter((c) => c.tagName === "TD" || c.tagName === "TH");
1095
+ rows.push(cells.map((c) => cellText(c)).join(" "));
1096
+ }
1097
+ return rows.join("\n");
1098
+ }
1099
+ function cellText(cell) {
1100
+ const clone = cell.cloneNode(true);
1101
+ for (const br of Array.from(clone.querySelectorAll("br"))) br.replaceWith(" ");
1102
+ return (clone.textContent ?? "").replace(/[\t\r\n]+/g, " ").replace(/ {2,}/g, " ").trim();
1103
+ }
1104
+ function HtmlChunk({
817
1105
  html,
818
- extensions: instanceExtensions,
819
- unsafe = false
1106
+ fromMarkdown,
1107
+ tail = false
820
1108
  }) {
821
- const extensions = react.useMemo(
822
- () => mergeExtensions(instanceExtensions),
823
- [instanceExtensions]
1109
+ const ref = react.useRef(null);
1110
+ const ctx = react.useContext(CopyContext);
1111
+ const [targets, setTargets] = react.useState([]);
1112
+ const code = ctx?.code ?? false;
1113
+ const table = ctx?.table ?? false;
1114
+ const holdLastCode = tail && (ctx?.holdTailCode ?? false);
1115
+ const inner = react.useMemo(() => ({ __html: html }), [html]);
1116
+ react.useLayoutEffect(() => {
1117
+ const root = ref.current;
1118
+ if (!root || !code && !table) {
1119
+ setTargets([]);
1120
+ return;
1121
+ }
1122
+ const found = [];
1123
+ const selector = [code && "pre", table && "table"].filter(Boolean).join(",");
1124
+ const inProgress = holdLastCode ? Array.from(root.querySelectorAll("pre")).pop() : void 0;
1125
+ for (const el of Array.from(root.querySelectorAll(selector))) {
1126
+ if (el === inProgress) continue;
1127
+ const kind = el.tagName === "PRE" ? "code" : "table";
1128
+ const wrapper = document.createElement("div");
1129
+ wrapper.setAttribute("data-react-fancy-content-copy-block", kind);
1130
+ wrapper.className = kind === "code" ? "group relative" : "group relative flex flex-col";
1131
+ const host = document.createElement("div");
1132
+ host.setAttribute("data-react-fancy-content-copy-host", "");
1133
+ host.className = kind === "code" ? "absolute right-2 top-2 z-10" : "flex justify-end";
1134
+ el.replaceWith(wrapper);
1135
+ if (kind === "table") wrapper.append(host, el);
1136
+ else wrapper.append(el, host);
1137
+ found.push({ kind, el, host });
1138
+ }
1139
+ setTargets(found);
1140
+ return () => {
1141
+ for (const t of found) {
1142
+ const wrapper = t.host.parentElement;
1143
+ if (wrapper?.parentNode) wrapper.replaceWith(t.el);
1144
+ }
1145
+ };
1146
+ }, [html, code, table, holdLastCode]);
1147
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1148
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref, dangerouslySetInnerHTML: inner }),
1149
+ targets.map(
1150
+ (t, i) => reactDom.createPortal(
1151
+ /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { kind: t.kind, target: t.el, fromMarkdown }),
1152
+ t.host,
1153
+ `${i}`
1154
+ )
1155
+ )
1156
+ ] });
1157
+ }
1158
+ function CopyButton({
1159
+ kind,
1160
+ target,
1161
+ fromMarkdown
1162
+ }) {
1163
+ const ctx = react.useContext(CopyContext);
1164
+ const [state, setState] = react.useState("idle");
1165
+ const timer = react.useRef(void 0);
1166
+ const btn = react.useRef(null);
1167
+ react.useEffect(() => () => clearTimeout(timer.current), []);
1168
+ const onClick = react.useCallback(async () => {
1169
+ if (!ctx || !btn.current) return;
1170
+ const text = kind === "code" ? codeText(target, fromMarkdown) : tableTsv(target);
1171
+ const ok = await ctx.copy({ kind, text, html: target.outerHTML }, btn.current);
1172
+ setState(ok ? "copied" : "failed");
1173
+ clearTimeout(timer.current);
1174
+ timer.current = setTimeout(() => setState("idle"), FEEDBACK_MS);
1175
+ }, [ctx, kind, target, fromMarkdown]);
1176
+ const idleLabel = kind === "code" ? "Copy code" : "Copy table";
1177
+ const label = state === "copied" ? "Copied" : state === "failed" ? "Copy failed" : idleLabel;
1178
+ return /* @__PURE__ */ jsxRuntime.jsx(
1179
+ chunk3U7PUZLI_cjs.Button,
1180
+ {
1181
+ ref: btn,
1182
+ type: "button",
1183
+ size: "xs",
1184
+ variant: "ghost",
1185
+ icon: state === "copied" ? "check" : state === "failed" ? "x" : "copy",
1186
+ "aria-label": label,
1187
+ title: label,
1188
+ "data-react-fancy-content-copy": kind,
1189
+ "data-copy-state": state,
1190
+ onClick,
1191
+ 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"
1192
+ }
824
1193
  );
825
- const segments = react.useMemo(
826
- () => parseSegments(html, extensions),
827
- [html, extensions]
1194
+ }
1195
+ function useCopyPipeline({
1196
+ writer,
1197
+ onCopy,
1198
+ rootRef,
1199
+ announce
1200
+ }) {
1201
+ const latest = react.useRef({ writer, onCopy, announce });
1202
+ latest.current = { writer, onCopy, announce };
1203
+ return react.useCallback(
1204
+ async (payload, button) => {
1205
+ const { writer: w, onCopy: report, announce: say } = latest.current;
1206
+ const ok = await runWriter(w ?? defaultClipboardWriter, payload);
1207
+ say(ok ? "Copied" : "Copy failed");
1208
+ const all = rootRef.current ? Array.from(rootRef.current.querySelectorAll("[data-react-fancy-content-copy]")) : [];
1209
+ report?.({ ...payload, index: all.indexOf(button), ok });
1210
+ return ok;
1211
+ },
1212
+ [rootRef]
828
1213
  );
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) } });
1214
+ }
1215
+ var MAX_DEPTH = 8;
1216
+ function lastIndexWith(items, renders) {
1217
+ for (let i = items.length - 1; i >= 0; i--) if (renders(items[i])) return i;
1218
+ return -1;
1219
+ }
1220
+ function ContentBody({ source, format, extensions, unsafe, depth = 0, tail = false }) {
1221
+ const active = depth >= MAX_DEPTH ? [] : extensions;
1222
+ const blocks = react.useMemo(() => {
1223
+ if (format !== "markdown") return null;
1224
+ const mdTags = active.filter((e) => e.markdown).map((e) => e.tag);
1225
+ return splitMarkdownBlocks(source, mdTags);
1226
+ }, [source, format, active]);
1227
+ if (!blocks) {
1228
+ const html = unsafe ? source : chunk3U7PUZLI_cjs.sanitizeHtml(source);
1229
+ return /* @__PURE__ */ jsxRuntime.jsx(
1230
+ RenderedContent,
1231
+ {
1232
+ html,
1233
+ extensions: active,
1234
+ unsafe,
1235
+ fromMarkdown: false,
1236
+ depth,
1237
+ tail
1238
+ }
1239
+ );
832
1240
  }
833
- if (segments.length === 0) {
834
- return null;
1241
+ const last = tail ? lastIndexWith(blocks, (b) => b.type === "tag" || b.source.trim() !== "") : -1;
1242
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: blocks.map((block, i) => {
1243
+ if (block.type === "markdown") {
1244
+ const raw = markdownToRawHtml(block.source);
1245
+ const html = unsafe ? raw : chunk3U7PUZLI_cjs.sanitizeHtml(raw);
1246
+ return /* @__PURE__ */ jsxRuntime.jsx(
1247
+ RenderedContent,
1248
+ {
1249
+ html,
1250
+ extensions: active,
1251
+ unsafe,
1252
+ fromMarkdown: true,
1253
+ depth,
1254
+ tail: i === last
1255
+ },
1256
+ i
1257
+ );
1258
+ }
1259
+ const ext = active.find((e) => e.tag.toLowerCase() === block.tag);
1260
+ if (!ext) return null;
1261
+ const Component = ext.component;
1262
+ const Wrapper = ext.block !== false ? "div" : "span";
1263
+ 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(
1264
+ ContentBody,
1265
+ {
1266
+ source: block.body,
1267
+ format: "markdown",
1268
+ extensions: active,
1269
+ unsafe,
1270
+ depth: depth + 1,
1271
+ tail: i === last
1272
+ }
1273
+ ) }) }, i);
1274
+ }) });
1275
+ }
1276
+ function RenderedContent({
1277
+ html,
1278
+ extensions,
1279
+ unsafe,
1280
+ fromMarkdown,
1281
+ depth,
1282
+ tail = false
1283
+ }) {
1284
+ const segments = react.useMemo(() => parseSegments(html, extensions), [html, extensions]);
1285
+ const renderHtml = (content) => unsafe ? content : chunk3U7PUZLI_cjs.sanitizeHtml(content);
1286
+ if (segments.length === 0) return null;
1287
+ if (segments.length === 1 && segments[0].type === "html") {
1288
+ return /* @__PURE__ */ jsxRuntime.jsx(HtmlChunk, { html: renderHtml(segments[0].content), fromMarkdown, tail });
835
1289
  }
1290
+ const last = tail ? lastIndexWith(segments, (s) => s.type === "extension" || s.content.trim() !== "") : -1;
836
1291
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: segments.map((segment, i) => {
837
1292
  if (segment.type === "html") {
838
- return segment.content ? /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: renderHtml(segment.content) } }, i) : null;
1293
+ return segment.content ? /* @__PURE__ */ jsxRuntime.jsx(
1294
+ HtmlChunk,
1295
+ {
1296
+ html: renderHtml(segment.content),
1297
+ fromMarkdown,
1298
+ tail: i === last
1299
+ },
1300
+ i
1301
+ ) : null;
839
1302
  }
840
- const ext = extensions.find(
841
- (e) => e.tag.toLowerCase() === segment.tag
842
- );
1303
+ const ext = extensions.find((e) => e.tag.toLowerCase() === segment.tag);
843
1304
  if (!ext) return null;
844
1305
  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);
1306
+ const Wrapper = ext.block !== false ? "div" : "span";
1307
+ const children = ext.markdown ? /* @__PURE__ */ jsxRuntime.jsx(
1308
+ ContentBody,
1309
+ {
1310
+ source: segment.content,
1311
+ format: "html",
1312
+ extensions,
1313
+ unsafe,
1314
+ depth: depth + 1,
1315
+ tail: i === last
1316
+ }
1317
+ ) : void 0;
1318
+ return /* @__PURE__ */ jsxRuntime.jsx(Wrapper, { "data-render-extension": segment.tag, children: /* @__PURE__ */ jsxRuntime.jsx(Component, { content: segment.content, attributes: segment.attributes, children }) }, i);
848
1319
  }) });
849
1320
  }
850
1321
  RenderedContent.displayName = "RenderedContent";
1322
+ ContentBody.displayName = "ContentBody";
851
1323
  function ContentRenderer({
852
1324
  value,
853
1325
  format = "auto",
854
1326
  lineSpacing = 1.6,
855
1327
  className,
856
1328
  extensions: instanceExtensions,
857
- unsafe = false
1329
+ unsafe = false,
1330
+ copyButtons = true,
1331
+ onCopy,
1332
+ clipboardWriter,
1333
+ streaming = false
858
1334
  }) {
859
1335
  const extensions = react.useMemo(
860
1336
  () => mergeExtensions(instanceExtensions),
861
1337
  [instanceExtensions]
862
1338
  );
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(
1339
+ const safe = value ?? "";
1340
+ const resolvedFormat = format === "auto" ? detectFormat(safe) : format;
1341
+ const progressive = streaming === "progressive";
1342
+ const { source, openFence } = react.useMemo(() => {
1343
+ if (!streaming) return { source: safe, openFence: false };
1344
+ const tags = extensions.map((e) => e.tag);
1345
+ const markdown = resolvedFormat === "markdown";
1346
+ if (progressive) return streamingProgressive(safe, tags, markdown);
1347
+ return { source: safe.slice(0, streamingCut(safe, tags, markdown)), openFence: false };
1348
+ }, [safe, streaming, progressive, extensions, resolvedFormat]);
1349
+ const codeOn = copyButtons === true || typeof copyButtons === "object" && copyButtons.code !== false;
1350
+ const tableOn = copyButtons === true || typeof copyButtons === "object" && copyButtons.table !== false;
1351
+ const copyOn = codeOn || tableOn;
1352
+ const rootRef = react.useRef(null);
1353
+ const [announcement, setAnnouncement] = react.useState("");
1354
+ const announce = react.useCallback((message) => {
1355
+ setAnnouncement("");
1356
+ queueMicrotask(() => setAnnouncement(message));
1357
+ }, []);
1358
+ const copy = useCopyPipeline({ writer: clipboardWriter, onCopy, rootRef, announce });
1359
+ const copyContext = react.useMemo(
1360
+ () => ({ code: codeOn, table: tableOn, copy, holdTailCode: openFence }),
1361
+ [codeOn, tableOn, copy, openFence]
1362
+ );
1363
+ return /* @__PURE__ */ jsxRuntime.jsxs(
871
1364
  "div",
872
1365
  {
1366
+ ref: rootRef,
873
1367
  "data-react-fancy-content-renderer": "",
1368
+ "data-streaming": streaming ? "true" : void 0,
1369
+ "data-streaming-mode": progressive ? "progressive" : void 0,
874
1370
  style: { lineHeight: lineSpacing },
875
1371
  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 } })
1372
+ children: [
1373
+ /* @__PURE__ */ jsxRuntime.jsx(CopyContext.Provider, { value: copyContext, children: /* @__PURE__ */ jsxRuntime.jsx(
1374
+ ContentBody,
1375
+ {
1376
+ source,
1377
+ format: resolvedFormat,
1378
+ extensions,
1379
+ unsafe,
1380
+ tail: true
1381
+ }
1382
+ ) }),
1383
+ copyOn && /* @__PURE__ */ jsxRuntime.jsx(
1384
+ "span",
1385
+ {
1386
+ role: "status",
1387
+ "aria-live": "polite",
1388
+ "aria-atomic": "true",
1389
+ "data-react-fancy-content-renderer-live": "",
1390
+ className: "sr-only",
1391
+ children: announcement
1392
+ }
1393
+ )
1394
+ ]
877
1395
  }
878
1396
  );
879
1397
  }
@@ -889,5 +1407,5 @@ exports.proseClasses = proseClasses;
889
1407
  exports.registerExtension = registerExtension;
890
1408
  exports.registerExtensions = registerExtensions;
891
1409
  exports.renderMarkdown = renderMarkdown;
892
- //# sourceMappingURL=chunk-MCQJFMDP.cjs.map
893
- //# sourceMappingURL=chunk-MCQJFMDP.cjs.map
1410
+ //# sourceMappingURL=chunk-464QSZ4A.cjs.map
1411
+ //# sourceMappingURL=chunk-464QSZ4A.cjs.map