@particle-academy/react-fancy 4.7.1 → 4.8.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/dist/index.cjs CHANGED
@@ -11591,6 +11591,71 @@ function mergeExtensions(instanceExtensions) {
11591
11591
  }
11592
11592
  return merged;
11593
11593
  }
11594
+ function RenderedContent({
11595
+ html,
11596
+ extensions: instanceExtensions,
11597
+ unsafe = false
11598
+ }) {
11599
+ const extensions = react.useMemo(
11600
+ () => mergeExtensions(instanceExtensions),
11601
+ [instanceExtensions]
11602
+ );
11603
+ const segments = react.useMemo(
11604
+ () => parseSegments(html, extensions),
11605
+ [html, extensions]
11606
+ );
11607
+ const renderHtml = (content) => unsafe ? content : sanitizeHtml(content);
11608
+ if (segments.length === 1 && segments[0].type === "html") {
11609
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: renderHtml(segments[0].content) } });
11610
+ }
11611
+ if (segments.length === 0) {
11612
+ return null;
11613
+ }
11614
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: segments.map((segment, i) => {
11615
+ if (segment.type === "html") {
11616
+ return segment.content ? /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: renderHtml(segment.content) } }, i) : null;
11617
+ }
11618
+ const ext = extensions.find(
11619
+ (e) => e.tag.toLowerCase() === segment.tag
11620
+ );
11621
+ if (!ext) return null;
11622
+ const Component = ext.component;
11623
+ const isBlock = ext.block !== false;
11624
+ const Wrapper = isBlock ? "div" : "span";
11625
+ return /* @__PURE__ */ jsxRuntime.jsx(Wrapper, { "data-render-extension": segment.tag, children: /* @__PURE__ */ jsxRuntime.jsx(Component, { content: segment.content, attributes: segment.attributes }) }, i);
11626
+ }) });
11627
+ }
11628
+ RenderedContent.displayName = "RenderedContent";
11629
+ function ContentRenderer({
11630
+ value,
11631
+ format = "auto",
11632
+ lineSpacing = 1.6,
11633
+ className,
11634
+ extensions: instanceExtensions,
11635
+ unsafe = false
11636
+ }) {
11637
+ const extensions = react.useMemo(
11638
+ () => mergeExtensions(instanceExtensions),
11639
+ [instanceExtensions]
11640
+ );
11641
+ const html = react.useMemo(() => {
11642
+ const safe = value ?? "";
11643
+ const resolvedFormat = format === "auto" ? detectFormat(safe) : format;
11644
+ const raw = resolvedFormat === "markdown" ? marked.marked.parse(safe, { async: false }) : safe;
11645
+ return unsafe ? raw : sanitizeHtml(raw);
11646
+ }, [value, format, unsafe]);
11647
+ const hasExtensions = extensions.length > 0;
11648
+ return /* @__PURE__ */ jsxRuntime.jsx(
11649
+ "div",
11650
+ {
11651
+ "data-react-fancy-content-renderer": "",
11652
+ style: { lineHeight: lineSpacing },
11653
+ className: cn("text-sm", proseClasses, className),
11654
+ children: hasExtensions ? /* @__PURE__ */ jsxRuntime.jsx(RenderedContent, { html, extensions, unsafe }) : /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: html } })
11655
+ }
11656
+ );
11657
+ }
11658
+ ContentRenderer.displayName = "ContentRenderer";
11594
11659
  function toHtml(value, outputFormat, unsafe) {
11595
11660
  if (!value) return "";
11596
11661
  const raw = (() => {
@@ -11610,16 +11675,22 @@ function EditorRoot({
11610
11675
  outputFormat = "html",
11611
11676
  lineSpacing = 1.6,
11612
11677
  placeholder,
11678
+ mode: modeProp,
11613
11679
  extensions: instanceExtensions,
11614
11680
  unsafe = false
11615
11681
  }) {
11616
11682
  const contentRef = react.useRef(null);
11617
- const [, setValue] = useControllableState(controlledValue, defaultValue, onChange);
11683
+ const [value, setValue] = useControllableState(controlledValue, defaultValue, onChange);
11684
+ const mode = useFieldMode(modeProp);
11685
+ const isView = mode === "view";
11618
11686
  const initialHtml = react.useMemo(
11619
- () => toHtml(controlledValue ?? defaultValue, outputFormat, unsafe),
11620
- // Only compute once on mount don't re-run when value changes from user input
11687
+ () => toHtml(value, outputFormat, unsafe),
11688
+ // Seed the contentEditable from the LIVE value when (re)entering edit mode.
11689
+ // EditorContent reads this once on mount, and it only mounts in edit mode —
11690
+ // so this captures the current value on view→edit, not a stale mount snapshot,
11691
+ // and does NOT re-run on every keystroke.
11621
11692
  // eslint-disable-next-line react-hooks/exhaustive-deps
11622
- []
11693
+ [isView, outputFormat, unsafe]
11623
11694
  );
11624
11695
  const extensions = react.useMemo(
11625
11696
  () => mergeExtensions(instanceExtensions),
@@ -11682,10 +11753,35 @@ function EditorRoot({
11682
11753
  _initialHtml: initialHtml,
11683
11754
  _onInput: handleInput
11684
11755
  };
11756
+ if (isView) {
11757
+ return /* @__PURE__ */ jsxRuntime.jsx(
11758
+ "div",
11759
+ {
11760
+ "data-react-fancy-editor": "",
11761
+ "data-mode": "view",
11762
+ className: cn(
11763
+ "overflow-hidden rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900",
11764
+ className
11765
+ ),
11766
+ children: /* @__PURE__ */ jsxRuntime.jsx(
11767
+ ContentRenderer,
11768
+ {
11769
+ value,
11770
+ format: outputFormat,
11771
+ lineSpacing,
11772
+ extensions: instanceExtensions,
11773
+ unsafe,
11774
+ className: "px-4 py-3"
11775
+ }
11776
+ )
11777
+ }
11778
+ );
11779
+ }
11685
11780
  return /* @__PURE__ */ jsxRuntime.jsx(EditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
11686
11781
  "div",
11687
11782
  {
11688
11783
  "data-react-fancy-editor": "",
11784
+ "data-mode": "edit",
11689
11785
  className: cn(
11690
11786
  "overflow-hidden rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900",
11691
11787
  className
@@ -11701,71 +11797,6 @@ var Editor = Object.assign(EditorRoot, {
11701
11797
  Toolbar: ToolbarWithSeparator,
11702
11798
  Content: EditorContent
11703
11799
  });
11704
- function RenderedContent({
11705
- html,
11706
- extensions: instanceExtensions,
11707
- unsafe = false
11708
- }) {
11709
- const extensions = react.useMemo(
11710
- () => mergeExtensions(instanceExtensions),
11711
- [instanceExtensions]
11712
- );
11713
- const segments = react.useMemo(
11714
- () => parseSegments(html, extensions),
11715
- [html, extensions]
11716
- );
11717
- const renderHtml = (content) => unsafe ? content : sanitizeHtml(content);
11718
- if (segments.length === 1 && segments[0].type === "html") {
11719
- return /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: renderHtml(segments[0].content) } });
11720
- }
11721
- if (segments.length === 0) {
11722
- return null;
11723
- }
11724
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: segments.map((segment, i) => {
11725
- if (segment.type === "html") {
11726
- return segment.content ? /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: renderHtml(segment.content) } }, i) : null;
11727
- }
11728
- const ext = extensions.find(
11729
- (e) => e.tag.toLowerCase() === segment.tag
11730
- );
11731
- if (!ext) return null;
11732
- const Component = ext.component;
11733
- const isBlock = ext.block !== false;
11734
- const Wrapper = isBlock ? "div" : "span";
11735
- return /* @__PURE__ */ jsxRuntime.jsx(Wrapper, { "data-render-extension": segment.tag, children: /* @__PURE__ */ jsxRuntime.jsx(Component, { content: segment.content, attributes: segment.attributes }) }, i);
11736
- }) });
11737
- }
11738
- RenderedContent.displayName = "RenderedContent";
11739
- function ContentRenderer({
11740
- value,
11741
- format = "auto",
11742
- lineSpacing = 1.6,
11743
- className,
11744
- extensions: instanceExtensions,
11745
- unsafe = false
11746
- }) {
11747
- const extensions = react.useMemo(
11748
- () => mergeExtensions(instanceExtensions),
11749
- [instanceExtensions]
11750
- );
11751
- const html = react.useMemo(() => {
11752
- const safe = value ?? "";
11753
- const resolvedFormat = format === "auto" ? detectFormat(safe) : format;
11754
- const raw = resolvedFormat === "markdown" ? marked.marked.parse(safe, { async: false }) : safe;
11755
- return unsafe ? raw : sanitizeHtml(raw);
11756
- }, [value, format, unsafe]);
11757
- const hasExtensions = extensions.length > 0;
11758
- return /* @__PURE__ */ jsxRuntime.jsx(
11759
- "div",
11760
- {
11761
- "data-react-fancy-content-renderer": "",
11762
- style: { lineHeight: lineSpacing },
11763
- className: cn("text-sm", proseClasses, className),
11764
- children: hasExtensions ? /* @__PURE__ */ jsxRuntime.jsx(RenderedContent, { html, extensions, unsafe }) : /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: html } })
11765
- }
11766
- );
11767
- }
11768
- ContentRenderer.displayName = "ContentRenderer";
11769
11800
  var MenuContext = react.createContext(null);
11770
11801
  function useMenu() {
11771
11802
  const ctx = react.useContext(MenuContext);