@ai-react-markdown/mantine 2.12.0 → 2.13.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/README.md CHANGED
@@ -69,7 +69,7 @@ yarn add @ai-react-markdown/mantine @ai-react-markdown/core
69
69
  {
70
70
  "react": ">=19",
71
71
  "react-dom": ">=19",
72
- "@ai-react-markdown/core": "^2.12.0",
72
+ "@ai-react-markdown/core": "^2.13.0",
73
73
  "@mantine/core": "^9.0.0",
74
74
  "@mantine/code-highlight": "^9.0.0",
75
75
  "highlight.js": "^11.11.2"
@@ -150,12 +150,13 @@ The Mantine package adds one behavior group on top of core's flat props: the `co
150
150
 
151
151
  ### `codeBlock` (`Partial<MantineCodeBlockOptions>`)
152
152
 
153
- | Field | Type | Default | Description |
154
- | --------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
155
- | `defaultExpanded` | `boolean` | `true` | Whether code blocks start in their expanded state. When `false`, long blocks are collapsed with an expand button. |
156
- | `autoDetectUnknownLanguage` | `boolean` | `false` | When `true`, uses `highlight.js`'s `highlightAuto` to determine the language of code blocks lacking an explicit annotation. |
157
- | `formatJson` | `boolean` | `true` | Format JSON while preserving number tokens, key order and duplicate keys. Set `false` to display source text. |
158
- | `expandNestedJson` | `boolean` | `true` | When JSON formatting is enabled, expand string values containing JSON objects or arrays. Set `false` for whitespace formatting only. |
153
+ | Field | Type | Default | Description |
154
+ | --------------------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
155
+ | `defaultExpanded` | `boolean` | `true` | Whether code blocks start in their expanded state. When `false`, long blocks are collapsed with an expand button. |
156
+ | `autoDetectUnknownLanguage` | `boolean` | `false` | When `true`, uses `highlight.js`'s `highlightAuto` to determine the language of code blocks lacking an explicit annotation. |
157
+ | `formatJson` | `boolean` | `true` | Format JSON while preserving number tokens, key order and duplicate keys. Set `false` to display source text. |
158
+ | `expandNestedJson` | `boolean` | `true` | When JSON formatting is enabled, expand string values containing JSON objects or arrays. Set `false` for whitespace formatting only. |
159
+ | `highlightIntervalMs` | `number` | `50` | Coalesce streaming code display updates over this interval in milliseconds; `0` updates every frame. Completion and replacement update immediately; copy always uses latest source. |
159
160
 
160
161
  ### Example: Collapsed Code Blocks
161
162
 
package/dist/index.cjs CHANGED
@@ -42,7 +42,7 @@ __export(index_exports, {
42
42
  module.exports = __toCommonJS(index_exports);
43
43
 
44
44
  // src/MantineAIMarkdown.tsx
45
- var import_react5 = require("react");
45
+ var import_react6 = require("react");
46
46
  var import_core7 = __toESM(require("@ai-react-markdown/core"), 1);
47
47
  var import_core8 = require("@ai-react-markdown/core");
48
48
 
@@ -62,7 +62,7 @@ var MantineAIMDefaultExtraStyles = ({ children }) => {
62
62
  var DefaultExtraStyles_default = MantineAIMDefaultExtraStyles;
63
63
 
64
64
  // src/components/customized/PreCode.tsx
65
- var import_react4 = require("react");
65
+ var import_react5 = require("react");
66
66
  var import_code_highlight2 = require("@mantine/code-highlight");
67
67
  var import_core5 = require("@ai-react-markdown/core");
68
68
 
@@ -75,7 +75,8 @@ var defaultMantineCodeBlockOptions = Object.freeze({
75
75
  defaultExpanded: true,
76
76
  autoDetectUnknownLanguage: false,
77
77
  formatJson: true,
78
- expandNestedJson: true
78
+ expandNestedJson: true,
79
+ highlightIntervalMs: 50
79
80
  });
80
81
 
81
82
  // src/hooks/useMantineCodeBlockOptions.ts
@@ -88,6 +89,9 @@ function useMantineCodeBlockOptions() {
88
89
  const value = group?.[key];
89
90
  if (value !== void 0) resolved[key] = value;
90
91
  }
92
+ if (!Number.isFinite(resolved.highlightIntervalMs) || resolved.highlightIntervalMs < 0) {
93
+ resolved.highlightIntervalMs = defaultMantineCodeBlockOptions.highlightIntervalMs;
94
+ }
91
95
  return resolved;
92
96
  }, [group]);
93
97
  }
@@ -521,6 +525,51 @@ function createJsonCompletenessScanner() {
521
525
  };
522
526
  }
523
527
 
528
+ // src/components/customized/useCodeFrame.ts
529
+ var import_react4 = require("react");
530
+
531
+ // src/components/customized/codeFrame.ts
532
+ function createCodeFrame(initial, publish) {
533
+ let shown = initial;
534
+ let latest = initial;
535
+ let delay = 0;
536
+ let timer;
537
+ const cancel = () => {
538
+ if (timer !== void 0) clearTimeout(timer);
539
+ timer = void 0;
540
+ };
541
+ const flush = () => {
542
+ cancel();
543
+ if (shown.code === latest.code && shown.language === latest.language) return;
544
+ shown = latest;
545
+ publish(shown);
546
+ };
547
+ return {
548
+ update(next, streaming, interval) {
549
+ const replaced = next.language !== latest.language || !next.code.startsWith(latest.code);
550
+ latest = next;
551
+ if (delay !== interval) cancel();
552
+ delay = interval;
553
+ if (!streaming || interval === 0 || replaced) return flush();
554
+ if (shown.code === next.code && shown.language === next.language) return cancel();
555
+ timer ??= setTimeout(flush, interval);
556
+ },
557
+ // Cancellation is reversible: Strict Mode replays the update effect.
558
+ dispose: cancel
559
+ };
560
+ }
561
+
562
+ // src/components/customized/useCodeFrame.ts
563
+ function useCodeFrame(code, language, streaming, interval) {
564
+ const [frame, setFrame] = (0, import_react4.useState)(() => ({ code, language }));
565
+ const [controller] = (0, import_react4.useState)(() => createCodeFrame({ code, language }, setFrame));
566
+ (0, import_react4.useEffect)(() => {
567
+ controller.update({ code, language }, streaming, interval);
568
+ }, [controller, code, language, streaming, interval]);
569
+ (0, import_react4.useEffect)(() => () => controller.dispose(), [controller]);
570
+ return !streaming || interval === 0 || language !== frame.language || !code.startsWith(frame.code) ? code : frame.code;
571
+ }
572
+
524
573
  // src/components/customized/PreCode.tsx
525
574
  var import_jsx_runtime4 = require("react/jsx-runtime");
526
575
  var hljsAutoPromise = null;
@@ -538,13 +587,13 @@ var AUTODETECT_MIN_CHARS = 32;
538
587
  var HLJS_LOAD_RETRIES = 3;
539
588
  var HLJS_LOAD_RETRY_MS = 1500;
540
589
  function useAutoDetectedLanguage(codeText, enabled, streaming) {
541
- const [detected, setDetected] = (0, import_react4.useState)(
590
+ const [detected, setDetected] = (0, import_react5.useState)(
542
591
  null
543
592
  );
544
- const [loadAttempt, setLoadAttempt] = (0, import_react4.useState)(0);
545
- const loadFailuresRef = (0, import_react4.useRef)(0);
546
- const previousTextRef = (0, import_react4.useRef)(codeText);
547
- (0, import_react4.useEffect)(() => {
593
+ const [loadAttempt, setLoadAttempt] = (0, import_react5.useState)(0);
594
+ const loadFailuresRef = (0, import_react5.useRef)(0);
595
+ const previousTextRef = (0, import_react5.useRef)(codeText);
596
+ (0, import_react5.useEffect)(() => {
548
597
  const appended = codeText.startsWith(previousTextRef.current);
549
598
  previousTextRef.current = codeText;
550
599
  if (!enabled) {
@@ -590,7 +639,9 @@ function preloadMantineCodeAssets() {
590
639
  () => void 0
591
640
  );
592
641
  }
593
- function RawCodeCopy({ code }) {
642
+ var RawCodeContext = (0, import_react5.createContext)("");
643
+ function RawCodeCopy() {
644
+ const code = (0, import_react5.useContext)(RawCodeContext);
594
645
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_core6.CopyButton, { value: code, children: ({ copied, copy }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
595
646
  import_code_highlight2.CodeHighlightControl,
596
647
  {
@@ -616,86 +667,105 @@ function RawCodeCopy({ code }) {
616
667
  }
617
668
  ) });
618
669
  }
670
+ function createCachedHighlightAdapter(highlight) {
671
+ let previous;
672
+ let result;
673
+ return {
674
+ getHighlighter: () => (input) => {
675
+ if (!previous || input.code !== previous.code || input.language !== previous.language || input.colorScheme !== previous.colorScheme) {
676
+ result = highlight(input);
677
+ previous = input;
678
+ }
679
+ return result;
680
+ }
681
+ };
682
+ }
683
+ var OrdinaryCodeHighlight = (0, import_react5.memo)(function OrdinaryCodeHighlight2({
684
+ code,
685
+ language,
686
+ fileName,
687
+ fontSize,
688
+ defaultExpanded
689
+ }) {
690
+ const highlight = (0, import_code_highlight2.useHighlight)();
691
+ const adapter = (0, import_react5.useMemo)(() => createCachedHighlightAdapter(highlight), [highlight]);
692
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_code_highlight2.CodeHighlightAdapterProvider, { adapter, children: fileName === "unknown" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
693
+ import_code_highlight2.CodeHighlight,
694
+ {
695
+ mb: 15,
696
+ fz: fontSize,
697
+ w: "100%",
698
+ code,
699
+ withBorder: true,
700
+ withExpandButton: true,
701
+ defaultExpanded,
702
+ maxCollapsedHeight: "320px",
703
+ withCopyButton: false,
704
+ controls: [/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RawCodeCopy, {}, "copy")]
705
+ }
706
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
707
+ import_code_highlight2.CodeHighlightTabs,
708
+ {
709
+ mb: 15,
710
+ fz: fontSize,
711
+ w: "100%",
712
+ code: [
713
+ {
714
+ fileName,
715
+ code,
716
+ language
717
+ }
718
+ ],
719
+ withBorder: true,
720
+ withExpandButton: true,
721
+ defaultExpanded,
722
+ maxCollapsedHeight: "320px",
723
+ withCopyButton: false,
724
+ controls: [/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RawCodeCopy, {}, "copy")]
725
+ }
726
+ ) });
727
+ });
619
728
  var SpecialCodeLanguage = /* @__PURE__ */ ((SpecialCodeLanguage2) => {
620
729
  SpecialCodeLanguage2["Mermaid"] = "mermaid";
621
730
  return SpecialCodeLanguage2;
622
731
  })(SpecialCodeLanguage || {});
623
732
  var SPECIAL_LANGUAGES = new Set(Object.values(SpecialCodeLanguage));
624
- var MantineAIMPreCode = (0, import_react4.memo)(
733
+ var MantineAIMPreCode = (0, import_react5.memo)(
625
734
  (props) => {
626
735
  const { fontSize } = (0, import_core5.useAIMarkdownTheme)();
627
736
  const { streaming } = (0, import_core5.useAIMarkdownState)();
628
- const { autoDetectUnknownLanguage, defaultExpanded, formatJson, expandNestedJson } = useMantineCodeBlockOptions();
737
+ const { autoDetectUnknownLanguage, defaultExpanded, formatJson, expandNestedJson, highlightIntervalMs } = useMantineCodeBlockOptions();
629
738
  const detectedLanguage = useAutoDetectedLanguage(
630
739
  props.codeText,
631
740
  autoDetectUnknownLanguage && !props.existLanguage,
632
741
  streaming
633
742
  );
634
743
  const codeLanguage = (props.existLanguage || detectedLanguage).toLowerCase();
635
- const [usedCodeLanguage, usedFileName] = (0, import_react4.useMemo)(
744
+ const [usedCodeLanguage, usedFileName] = (0, import_react5.useMemo)(
636
745
  () => codeLanguage ? [codeLanguage, codeLanguage] : ["plaintext", "unknown"],
637
746
  [codeLanguage]
638
747
  );
639
748
  const isSpecialCodeBlock = SPECIAL_LANGUAGES.has(codeLanguage);
640
- const [scanJson] = (0, import_react4.useState)(createJsonCompletenessScanner);
641
- const jsonComplete = (0, import_react4.useMemo)(
749
+ const [scanJson] = (0, import_react5.useState)(createJsonCompletenessScanner);
750
+ const jsonComplete = (0, import_react5.useMemo)(
642
751
  () => usedCodeLanguage === "json" && formatJson && streaming ? scanJson(props.codeText) : false,
643
752
  [usedCodeLanguage, formatJson, streaming, scanJson, props.codeText]
644
753
  );
645
- const normalCodeBlockContent = (0, import_react4.useMemo)(() => {
754
+ const normalCodeBlockContent = (0, import_react5.useMemo)(() => {
646
755
  if (isSpecialCodeBlock) return null;
647
756
  let usedCodeStr = props.codeText;
648
757
  if (formatJson && usedCodeStr && usedCodeLanguage === "json" && (!streaming || jsonComplete)) {
649
758
  usedCodeStr = prettyPrintJson(usedCodeStr, expandNestedJson);
650
759
  }
651
- return usedFileName === "unknown" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
652
- import_code_highlight2.CodeHighlight,
653
- {
654
- mb: 15,
655
- fz: fontSize,
656
- w: "100%",
657
- code: usedCodeStr,
658
- withBorder: true,
659
- withExpandButton: true,
660
- defaultExpanded,
661
- maxCollapsedHeight: "320px",
662
- withCopyButton: false,
663
- controls: [/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RawCodeCopy, { code: props.codeText }, "copy")]
664
- }
665
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
666
- import_code_highlight2.CodeHighlightTabs,
667
- {
668
- mb: 15,
669
- fz: fontSize,
670
- w: "100%",
671
- code: [
672
- {
673
- fileName: usedFileName,
674
- code: usedCodeStr,
675
- language: usedCodeLanguage
676
- }
677
- ],
678
- withBorder: true,
679
- withExpandButton: true,
680
- defaultExpanded,
681
- maxCollapsedHeight: "320px",
682
- withCopyButton: false,
683
- controls: [/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RawCodeCopy, { code: props.codeText }, "copy")]
684
- }
685
- );
686
- }, [
687
- isSpecialCodeBlock,
688
- props.codeText,
760
+ return usedCodeStr;
761
+ }, [isSpecialCodeBlock, props.codeText, usedCodeLanguage, streaming, formatJson, expandNestedJson, jsonComplete]);
762
+ const displayedCode = useCodeFrame(
763
+ normalCodeBlockContent ?? "",
689
764
  usedCodeLanguage,
690
- usedFileName,
691
- fontSize,
692
- defaultExpanded,
693
- streaming,
694
- formatJson,
695
- expandNestedJson,
696
- jsonComplete
697
- ]);
698
- const specialCodeBlockContent = (0, import_react4.useMemo)(() => {
765
+ streaming && !isSpecialCodeBlock,
766
+ highlightIntervalMs
767
+ );
768
+ const specialCodeBlockContent = (0, import_react5.useMemo)(() => {
699
769
  switch (codeLanguage) {
700
770
  case "mermaid" /* Mermaid */:
701
771
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MermaidCode_default, { code: props.codeText });
@@ -703,7 +773,16 @@ var MantineAIMPreCode = (0, import_react4.memo)(
703
773
  return null;
704
774
  }
705
775
  }, [codeLanguage, props.codeText]);
706
- return isSpecialCodeBlock ? specialCodeBlockContent : normalCodeBlockContent;
776
+ return isSpecialCodeBlock ? specialCodeBlockContent : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RawCodeContext.Provider, { value: props.codeText, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
777
+ OrdinaryCodeHighlight,
778
+ {
779
+ code: displayedCode,
780
+ language: usedCodeLanguage,
781
+ fileName: usedFileName,
782
+ fontSize,
783
+ defaultExpanded
784
+ }
785
+ ) });
707
786
  }
708
787
  );
709
788
  MantineAIMPreCode.displayName = "MantineAIMPreCode";
@@ -740,12 +819,12 @@ var MantineAIMarkdownComponent = ({
740
819
  ...props
741
820
  }) => {
742
821
  const stableCustomComponents = (0, import_core8.useStableValue)(customComponents);
743
- const usedComponents = (0, import_react5.useMemo)(() => {
822
+ const usedComponents = (0, import_react6.useMemo)(() => {
744
823
  return stableCustomComponents ? { ...DefaultCustomComponents, ...stableCustomComponents } : DefaultCustomComponents;
745
824
  }, [stableCustomComponents]);
746
825
  const computedColorScheme = (0, import_core9.useComputedColorScheme)("light");
747
826
  const stable = (0, import_core8.useStableRecord)({ codeBlock }, MANTINE_STABILITY_TABLE);
748
- const behaviorGroups = (0, import_react5.useMemo)(
827
+ const behaviorGroups = (0, import_react6.useMemo)(
749
828
  () => stable.codeBlock != null ? { codeBlock: stable.codeBlock } : NO_GROUPS,
750
829
  [stable.codeBlock]
751
830
  );
@@ -760,7 +839,7 @@ var MantineAIMarkdownComponent = ({
760
839
  }
761
840
  ) });
762
841
  };
763
- var MantineAIMarkdown = (0, import_react5.memo)(MantineAIMarkdownComponent);
842
+ var MantineAIMarkdown = (0, import_react6.memo)(MantineAIMarkdownComponent);
764
843
  MantineAIMarkdown.displayName = "MantineAIMarkdown";
765
844
  var MantineAIMarkdown_default = MantineAIMarkdown;
766
845