@podlite/editor-react 0.0.41 → 0.0.43

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/esm/Wrapper.js CHANGED
@@ -1,87 +1,48 @@
1
- import React, { useRef } from 'react';
2
- const WindowWrapper = ({ children, title = '' }) => {
3
- const wrapperRef = useRef(null);
4
- const handleCopy = async () => {
5
- if (!wrapperRef.current)
1
+ import React, { useRef, useState } from 'react';
2
+ const WindowWrapper = ({ children, title = '', enableCopyPng = false }) => {
3
+ const windowRef = useRef(null);
4
+ const copyBtnRef = useRef(null);
5
+ const [isCopied, setIsCopied] = useState(false);
6
+ const handleCopyPng = async () => {
7
+ if (!windowRef.current)
6
8
  return;
7
- const html = wrapperRef.current.outerHTML;
8
9
  try {
9
- const svg = `
10
- <svg xmlns="http://www.w3.org/2000/svg" width="800" height="600">
11
- <foreignObject width="100%" height="100%">
12
- <div xmlns="http://www.w3.org/1999/xhtml">
13
- ${html}
14
- </div>
15
- </foreignObject>
16
- </svg>
17
- `.trim();
18
- const dataURL = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`;
19
- await navigator.clipboard.writeText(dataURL);
20
- }
21
- catch (err) {
22
- console.error('Copy failed:', err);
23
- }
24
- };
25
- const copyElementToClipboard = async () => {
26
- if (!wrapperRef.current)
27
- return;
28
- try {
29
- // setIsLoading(true);
30
- const canvas = document.createElement('canvas');
31
- const context = canvas.getContext('2d');
32
- const element = wrapperRef.current;
33
- const rect = element.getBoundingClientRect();
34
- canvas.width = rect.width * 2;
35
- canvas.height = rect.height * 2;
36
- const svgData = `
37
- <svg xmlns="http://www.w3.org/2000/svg" width="${rect.width}" height="${rect.height}">
38
- <foreignObject width="100%" height="100%">
39
- <div xmlns="http://www.w3.org/1999/xhtml">
40
- ${element.outerHTML}
41
- </div>
42
- </foreignObject>
43
- </svg>
44
- `;
45
- console.log('1');
46
- const base64Data = btoa(unescape(encodeURIComponent(svgData)));
47
- // const base64Data = btoa(encodeURIComponent(svgData));
48
- const dataUrl = `data:image/svg+xml;base64,${base64Data}`;
49
- console.log(dataUrl);
50
- const image = new Image();
51
- image.crossOrigin = 'anonymous';
52
- console.log('2');
53
- await new Promise((resolve, reject) => {
54
- image.onload = resolve;
55
- image.onerror = e => {
56
- console.error('Image load error:', e);
57
- reject(e);
58
- };
59
- image.src = dataUrl;
60
- });
61
- console.log('3');
62
- if (context) {
63
- context.scale(2, 2);
64
- context.drawImage(image, 0, 0);
10
+ if (copyBtnRef.current) {
11
+ copyBtnRef.current.style.display = 'none';
65
12
  }
66
- console.log('4');
67
- const imageBlob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
68
- if (imageBlob) {
69
- await navigator.clipboard.write([new ClipboardItem({ 'image/png': imageBlob })]);
13
+ const domtoimage = (await import('dom-to-image-more')).default;
14
+ const blob = await domtoimage.toBlob(windowRef.current, { quality: 1.0 });
15
+ if (copyBtnRef.current) {
16
+ copyBtnRef.current.style.display = '';
70
17
  }
18
+ await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
19
+ setIsCopied(true);
20
+ setTimeout(() => setIsCopied(false), 2000);
71
21
  }
72
22
  catch (err) {
73
- console.error('Screenshot failed:', err);
74
- }
75
- finally {
76
- // setIsLoading(false);
23
+ if (copyBtnRef.current) {
24
+ copyBtnRef.current.style.display = '';
25
+ }
26
+ console.error('Copy PNG failed:', err);
77
27
  }
78
28
  };
79
- return (React.createElement("div", { className: "window", ref: wrapperRef },
80
- React.createElement("div", { className: "titlebar" },
81
- React.createElement("div", { className: "buttons" }),
82
- title && React.createElement("span", { className: "title" }, title)),
83
- React.createElement("div", { className: "content" }, children),
29
+ return (React.createElement("div", { className: "window-capture", ref: windowRef },
30
+ React.createElement("div", { className: "window" },
31
+ React.createElement("div", { className: "titlebar" },
32
+ React.createElement("div", { className: "buttons" }),
33
+ title && React.createElement("span", { className: "title" }, title),
34
+ React.createElement("div", { className: "titlebar-spacer" }),
35
+ enableCopyPng && (React.createElement("button", { ref: copyBtnRef, className: "copy-png-btn", onClick: handleCopyPng, title: "Copy as PNG" }, isCopied ? '\u2713' : '\u2398'))),
36
+ React.createElement("div", { className: "content" }, children)),
84
37
  React.createElement("style", null, `
38
+ .window-capture {
39
+ padding: 1.5rem;
40
+ display: inline-block;
41
+ margin: 0.5rem;
42
+ max-width: 100%;
43
+ box-sizing: border-box;
44
+ }
45
+
85
46
  .window {
86
47
  position: relative;
87
48
  margin: 2rem;
@@ -89,6 +50,8 @@ const WindowWrapper = ({ children, title = '' }) => {
89
50
  background: white;
90
51
  box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.2);
91
52
  border: 1px solid #ccc;
53
+ max-width: 100%;
54
+ box-sizing: border-box;
92
55
  }
93
56
 
94
57
  .titlebar {
@@ -101,6 +64,10 @@ const WindowWrapper = ({ children, title = '' }) => {
101
64
  align-items: center;
102
65
  }
103
66
 
67
+ .titlebar-spacer {
68
+ flex: 1;
69
+ }
70
+
104
71
  .buttons {
105
72
  display: flex;
106
73
  gap: 6px;
@@ -140,6 +107,23 @@ const WindowWrapper = ({ children, title = '' }) => {
140
107
  .content {
141
108
  padding: 16px;
142
109
  }
110
+
111
+ .copy-png-btn {
112
+ background: none;
113
+ border: 1px solid #ccc;
114
+ border-radius: 4px;
115
+ padding: 2px 8px;
116
+ cursor: pointer;
117
+ font-size: 14px;
118
+ color: #666;
119
+ line-height: 1;
120
+ transition: background 0.15s, color 0.15s;
121
+ }
122
+
123
+ .copy-png-btn:hover {
124
+ background: #e0e0e0;
125
+ color: #333;
126
+ }
143
127
  `)));
144
128
  };
145
129
  export default WindowWrapper;
@@ -1 +1 @@
1
- {"version":3,"file":"Wrapper.js","sourceRoot":"","sources":["../src/Wrapper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAErC,MAAM,aAAa,GAAG,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAG,EAAE,EAAE,EAAE,EAAE;IACjD,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC/C,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;QAC5B,IAAI,CAAC,UAAU,CAAC,OAAO;YAAE,OAAM;QAC/B,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,CAAA;QAEzC,IAAI;YACF,MAAM,GAAG,GAAG;;;;oBAIE,IAAI;;;;WAIb,CAAC,IAAI,EAAE,CAAA;YAEZ,MAAM,OAAO,GAAG,6BAA6B,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;YACtF,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;SAC7C;QAAC,OAAO,GAAG,EAAE;YACZ,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAA;SACnC;IACH,CAAC,CAAA;IAED,MAAM,sBAAsB,GAAG,KAAK,IAAI,EAAE;QACxC,IAAI,CAAC,UAAU,CAAC,OAAO;YAAE,OAAM;QAE/B,IAAI;YACF,wBAAwB;YAExB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;YAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACvC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAA;YAClC,MAAM,IAAI,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAA;YAE5C,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAA;YAC7B,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;YAE/B,MAAM,OAAO,GAAG;6DACuC,IAAI,CAAC,KAAK,aAAa,IAAI,CAAC,MAAM;;;oBAG3E,OAAO,CAAC,SAAS;;;;WAI1B,CAAA;YACL,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAChB,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;YAC9D,0DAA0D;YAC1D,MAAM,OAAO,GAAG,6BAA6B,UAAU,EAAE,CAAA;YACzD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACpB,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAA;YACzB,KAAK,CAAC,WAAW,GAAG,WAAW,CAAA;YAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAChB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACpC,KAAK,CAAC,MAAM,GAAG,OAAO,CAAA;gBACtB,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE;oBAClB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAA;oBACrC,MAAM,CAAC,CAAC,CAAC,CAAA;gBACX,CAAC,CAAA;gBACD,KAAK,CAAC,GAAG,GAAG,OAAO,CAAA;YACrB,CAAC,CAAC,CAAA;YACF,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAChB,IAAI,OAAO,EAAE;gBACX,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;gBACnB,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;aAC/B;YACD,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAChB,MAAM,SAAS,GAAG,MAAM,IAAI,OAAO,CAAc,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAA;YAEhG,IAAI,SAAS,EAAE;gBACb,MAAM,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAA;aACjF;SACF;QAAC,OAAO,GAAG,EAAE;YACZ,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAA;SACzC;gBAAS;YACR,yBAAyB;SAC1B;IACH,CAAC,CAAA;IAED,OAAO,CACL,6BAAK,SAAS,EAAC,QAAQ,EAAC,GAAG,EAAE,UAAU;QACrC,6BAAK,SAAS,EAAC,UAAU;YACvB,6BAAK,SAAS,EAAC,SAAS,GAIlB;YACL,KAAK,IAAI,8BAAM,SAAS,EAAC,OAAO,IAAE,KAAK,CAAQ,CAC5C;QACN,6BAAK,SAAS,EAAC,SAAS,IAAE,QAAQ,CAAO;QACzC,mCAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2DP,CAAS,CACN,CACP,CAAA;AACH,CAAC,CAAA;AAED,eAAe,aAAa,CAAA"}
1
+ {"version":3,"file":"Wrapper.js","sourceRoot":"","sources":["../src/Wrapper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAQ/C,MAAM,aAAa,GAAG,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAG,EAAE,EAAE,aAAa,GAAG,KAAK,EAAsB,EAAE,EAAE;IAC5F,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC9C,MAAM,UAAU,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IAClD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE/C,MAAM,aAAa,GAAG,KAAK,IAAI,EAAE;QAC/B,IAAI,CAAC,SAAS,CAAC,OAAO;YAAE,OAAM;QAC9B,IAAI;YACF,IAAI,UAAU,CAAC,OAAO,EAAE;gBACtB,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;aAC1C;YACD,MAAM,UAAU,GAAG,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAA;YAC9D,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAA;YACzE,IAAI,UAAU,CAAC,OAAO,EAAE;gBACtB,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAA;aACtC;YACD,MAAM,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;YAC3E,WAAW,CAAC,IAAI,CAAC,CAAA;YACjB,UAAU,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA;SAC3C;QAAC,OAAO,GAAG,EAAE;YACZ,IAAI,UAAU,CAAC,OAAO,EAAE;gBACtB,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAA;aACtC;YACD,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAA;SACvC;IACH,CAAC,CAAA;IAED,OAAO,CACL,6BAAK,SAAS,EAAC,gBAAgB,EAAC,GAAG,EAAE,SAAS;QAC5C,6BAAK,SAAS,EAAC,QAAQ;YACrB,6BAAK,SAAS,EAAC,UAAU;gBACvB,6BAAK,SAAS,EAAC,SAAS,GAAO;gBAC9B,KAAK,IAAI,8BAAM,SAAS,EAAC,OAAO,IAAE,KAAK,CAAQ;gBAChD,6BAAK,SAAS,EAAC,iBAAiB,GAAO;gBACtC,aAAa,IAAI,CAChB,gCAAQ,GAAG,EAAE,UAAU,EAAE,SAAS,EAAC,cAAc,EAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAC,aAAa,IAC1F,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CACxB,CACV,CACG;YACN,6BAAK,SAAS,EAAC,SAAS,IAAE,QAAQ,CAAO,CACrC;QACN,mCAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0FP,CAAS,CACN,CACP,CAAA;AACH,CAAC,CAAA;AAED,eAAe,aAAa,CAAA"}
@@ -4,11 +4,44 @@ const beginRe = /^(\s*)=begin\s+(\S+)/;
4
4
  const endRe = /^(\s*)=end\s+(\S+)/;
5
5
  // =headN (level 1-6)
6
6
  const headRe = /^(\s*)=head(\d+)\s/;
7
+ // Verbatim block types where content should not be folded
8
+ const verbatimTypes = new Set(['code', 'comment', 'data', 'input', 'output']);
7
9
  // Parse =begin line, return block name or null
8
10
  function parseBeginLine(lineText) {
9
11
  const m = lineText.match(beginRe);
10
12
  return m ? m[2] ?? null : null;
11
13
  }
14
+ // Cache verbatim ranges per document version to avoid rescanning on every fold query
15
+ let _cachedVerbatimDoc = null;
16
+ let _cachedVerbatimRanges = [];
17
+ function getVerbatimRanges(state) {
18
+ // EditorState.doc is immutable — same reference means same content
19
+ if (_cachedVerbatimDoc === state.doc)
20
+ return _cachedVerbatimRanges;
21
+ const ranges = [];
22
+ for (let i = 1; i <= state.doc.lines; i++) {
23
+ const line = state.doc.line(i);
24
+ const m = line.text.match(beginRe);
25
+ if (m && verbatimTypes.has(m[2])) {
26
+ for (let j = i + 1; j <= state.doc.lines; j++) {
27
+ const endLine = state.doc.line(j);
28
+ const em = endLine.text.match(endRe);
29
+ if (em && em[2] === m[2]) {
30
+ if (i + 1 <= j - 1) {
31
+ ranges.push({ from: i + 1, to: j - 1 });
32
+ }
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ _cachedVerbatimDoc = state.doc;
39
+ _cachedVerbatimRanges = ranges;
40
+ return ranges;
41
+ }
42
+ function isInsideVerbatim(lineNumber, ranges) {
43
+ return ranges.some(r => lineNumber >= r.from && lineNumber <= r.to);
44
+ }
12
45
  // Fold =begin Name ... =end Name: fold from end of =begin line to start of =end line
13
46
  function foldBeginEnd(state, lineStart) {
14
47
  const line = state.doc.lineAt(lineStart);
@@ -65,5 +98,11 @@ function foldHeadSection(state, lineStart) {
65
98
  return null;
66
99
  return { from: line.to, to: endPos };
67
100
  }
68
- export const podliteFoldService = foldService.of((state, lineStart) => foldBeginEnd(state, lineStart) ?? foldHeadSection(state, lineStart));
101
+ export const podliteFoldService = foldService.of((state, lineStart) => {
102
+ const line = state.doc.lineAt(lineStart);
103
+ const verbatimRanges = getVerbatimRanges(state);
104
+ if (isInsideVerbatim(line.number, verbatimRanges))
105
+ return null;
106
+ return foldBeginEnd(state, lineStart) ?? foldHeadSection(state, lineStart);
107
+ });
69
108
  //# sourceMappingURL=foldPodlite.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"foldPodlite.js","sourceRoot":"","sources":["../src/foldPodlite.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"foldPodlite.js","sourceRoot":"","sources":["../src/foldPodlite.ts"],"names":[],"mappings":"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"}
package/lib/Editor.css CHANGED
@@ -53,3 +53,22 @@
53
53
  .podlite-editor h6 a {
54
54
  display: none;
55
55
  }
56
+ .podlite-editor .podlite-editor-preview > .content {
57
+ margin-top: 0;
58
+ }
59
+ .podlite-editor .podlite-editor-preview li,
60
+ .podlite-editor .podlite-editor-preview .line-src,
61
+ .podlite-editor .podlite-editor-preview ul {
62
+ margin-top: 0;
63
+ }
64
+ .podlite-editor-preview .content > :first-child,
65
+ .podlite-editor-preview .content > .line-src:first-child {
66
+ margin-top: 0;
67
+ }
68
+ .podlite-editor-preview .content > .line-src:first-child > * {
69
+ margin-top: 0;
70
+ }
71
+ /* Compensate for missing fold gutter width when folding is disabled */
72
+ .podlite-editor .cm-editor:not(:has(.cm-foldGutter)) .cm-content {
73
+ padding-left: 11px;
74
+ }
package/lib/Editor.d.ts CHANGED
@@ -28,6 +28,8 @@ export interface IPodliteEditor extends ReactCodeMirrorProps {
28
28
  onOpenLink?: (url: string) => void;
29
29
  /** Enable code highlighting */
30
30
  enableHighlighting?: boolean;
31
+ /** Enable code folding (fold gutter + fold keymap) @default `true` */
32
+ enableFolding?: boolean;
31
33
  /** Initial editor session state to restore (cursor, scroll, folds) */
32
34
  initialEditorState?: EditorSessionState;
33
35
  /** Called when editor state changes (cursor move, scroll, fold/unfold) */
package/lib/Editor.js CHANGED
@@ -33,10 +33,10 @@ function PodliteEditorInternal(props, ref) {
33
33
  if (!isBrowser)
34
34
  return null;
35
35
  const { onChange, enableScroll = true, enablePreview = true, extensions = [], previewWidth = '50%', onPreviewMode, isFullscreen, makePreviewComponent, startLinePreview = 1, // start line number
36
- enableAutocompletion = false, onOpenLink, enableHighlighting = false, initialEditorState, onEditorStateChange, ...codemirrorProps } = props;
36
+ enableAutocompletion = false, onOpenLink, enableHighlighting = false, enableFolding = true, initialEditorState, onEditorStateChange, ...codemirrorProps } = props;
37
37
  const full_preview = previewWidth === '100%';
38
38
  const [value, setValue] = (0, react_1.useState)(props.value || '');
39
- const [text1, setText] = (0, react_1.useState)(props.value || '');
39
+ const pendingTextRef = (0, react_1.useRef)(props.value || '');
40
40
  const codeMirror = (0, react_1.useRef)(null);
41
41
  const container = (0, react_1.useRef)(null);
42
42
  const containerEditor = (0, react_1.useRef)(null);
@@ -45,6 +45,14 @@ function PodliteEditorInternal(props, ref) {
45
45
  const topLine = (0, react_1.useRef)(startLinePreview);
46
46
  const $viewHeight = (0, react_1.useRef)(0);
47
47
  const initialStateApplied = (0, react_1.useRef)(false);
48
+ // Stable refs for callbacks used in memoized extensions
49
+ // Stable refs for callbacks used in memoized extensions
50
+ const onEditorStateChangeRef = (0, react_1.useRef)(onEditorStateChange);
51
+ onEditorStateChangeRef.current = onEditorStateChange;
52
+ const onOpenLinkRef = (0, react_1.useRef)(onOpenLink);
53
+ onOpenLinkRef.current = onOpenLink;
54
+ // Stable basicSetup config to prevent CodeMirror reconfiguration on re-render
55
+ const basicSetupConfig = react_1.default.useMemo(() => ({ defaultKeymap: false }), []);
48
56
  (0, react_1.useImperativeHandle)(ref, () => ({
49
57
  editor: codeMirror,
50
58
  preview: preview,
@@ -60,7 +68,11 @@ function PodliteEditorInternal(props, ref) {
60
68
  }
61
69
  return ranges;
62
70
  }, []);
63
- // Restore editor session state once after mount
71
+ // Reset applied flag when initialEditorState changes (new file opened)
72
+ (0, react_1.useEffect)(() => {
73
+ initialStateApplied.current = false;
74
+ }, [initialEditorState]);
75
+ // Restore editor session state once after mount or file change
64
76
  (0, react_1.useEffect)(() => {
65
77
  if (initialStateApplied.current || !initialEditorState)
66
78
  return;
@@ -126,7 +138,9 @@ function PodliteEditorInternal(props, ref) {
126
138
  : codemirrorProps.height;
127
139
  const preValue = props.value;
128
140
  (0, react_1.useEffect)(() => setValue(preValue ?? ''), [preValue]);
129
- (0, react_1.useEffect)(() => setText(preValue ?? ''), [preValue]);
141
+ (0, react_1.useEffect)(() => {
142
+ pendingTextRef.current = preValue ?? '';
143
+ }, [preValue]);
130
144
  const fullRef = (0, react_1.useRef)(isFullscreen);
131
145
  (0, react_1.useEffect)(() => {
132
146
  fullRef.current = isFullscreen;
@@ -281,8 +295,9 @@ function PodliteEditorInternal(props, ref) {
281
295
  },
282
296
  ...commands_1.defaultKeymap,
283
297
  ]);
284
- // Add/remove a class on the editor root while Mod (Cmd/Ctrl) is pressed
285
- const modKeyClass = view_1.EditorView.domEventHandlers({
298
+ // Memoized extensions to prevent CodeMirror reconfiguration on re-render
299
+ // (reconfiguration closes panels like search)
300
+ const modKeyClass = react_1.default.useMemo(() => view_1.EditorView.domEventHandlers({
286
301
  keydown(e, view) {
287
302
  if (e.metaKey || e.ctrlKey)
288
303
  view.dom.classList.add('cm-mod-pressed');
@@ -294,12 +309,11 @@ function PodliteEditorInternal(props, ref) {
294
309
  blur(_, view) {
295
310
  view.dom.classList.remove('cm-mod-pressed');
296
311
  },
297
- // defensive: if mouse leaves, clear the class (helps when key is released elsewhere)
298
312
  mouseleave(_, view) {
299
313
  view.dom.classList.remove('cm-mod-pressed');
300
314
  },
301
- });
302
- const openStyledLinkOnClick = view_1.EditorView.domEventHandlers({
315
+ }), []);
316
+ const openStyledLinkOnClick = react_1.default.useMemo(() => view_1.EditorView.domEventHandlers({
303
317
  click(event, view) {
304
318
  const target = event.target;
305
319
  if (!target)
@@ -307,60 +321,42 @@ function PodliteEditorInternal(props, ref) {
307
321
  const el = target.closest('.cm-clickable-link');
308
322
  if (!el)
309
323
  return;
310
- // require Cmd/Ctrl for activation
311
324
  const modPressed = event.metaKey || event.ctrlKey;
312
325
  if (!modPressed)
313
326
  return;
314
327
  const from = view.posAtDOM(el, 0);
315
328
  const to = view.posAtDOM(el, el.childNodes.length);
316
329
  const text = view.state.doc.sliceString(from, to).trim();
317
- if (onOpenLink) {
318
- onOpenLink(text); // i.e window.open(text, '_blank', 'noopener')
319
- }
330
+ onOpenLinkRef.current?.(text);
320
331
  event.preventDefault();
321
332
  event.stopPropagation();
322
333
  },
323
- });
334
+ }), []);
324
335
  // Track fold/unfold and cursor changes to emit editor state
325
- const stateChangeListener = onEditorStateChange
336
+ const stateChangeListener = react_1.default.useMemo(() => onEditorStateChange
326
337
  ? view_1.EditorView.updateListener.of(update => {
327
- // Emit on fold/unfold (transactions with fold effects) or cursor move
328
338
  const hasFoldChange = update.transactions.some(tr => tr.effects.some(e => e.is(language_1.foldEffect) || e.is(language_1.unfoldEffect)));
329
339
  const hasCursorChange = update.selectionSet;
330
340
  if (hasFoldChange || hasCursorChange) {
331
- onEditorStateChange({
341
+ onEditorStateChangeRef.current?.({
332
342
  cursorOffset: update.state.selection.main.head,
333
343
  scrollTop: update.view.scrollDOM.scrollTop,
334
344
  foldedRanges: serializeFoldedRanges(update.view),
335
345
  });
336
346
  }
337
347
  })
338
- : [];
339
- let extensionsData = [
340
- (0, podlite_2.podliteLang)(),
341
- view_1.EditorView.lineWrapping,
342
- foldPodlite_1.podliteFoldService,
343
- (0, language_1.foldGutter)(),
344
- view_1.keymap.of(language_1.foldKeymap),
345
- stateChangeListener,
346
- listContinuation_1.itemLevelKeymap,
347
- listContinuation_1.listContinuationKeymap,
348
- preventToggleComment,
349
- ];
350
- if (onOpenLink) {
351
- extensionsData.push(modKeyClass, openStyledLinkOnClick, view_1.EditorView.theme({
352
- '&.cm-editor.cm-mod-pressed .cm-clickable-link:hover': {
353
- cursor: 'pointer',
354
- },
355
- }));
356
- }
357
- if (enableScroll) {
358
- extensionsData.push(scrollExtensions);
359
- }
360
- const makeApply = (text) => (editor, completion, from, to) => {
348
+ : [], [!!onEditorStateChange, serializeFoldedRanges]);
349
+ const linkTheme = react_1.default.useMemo(() => view_1.EditorView.theme({
350
+ '&.cm-editor.cm-mod-pressed .cm-clickable-link:hover': {
351
+ cursor: 'pointer',
352
+ },
353
+ }), []);
354
+ const makeApply = (0, react_1.useCallback)((text) => (editor, completion, from, to) => {
361
355
  return (0, autocomplete_1.snippet)(text)(editor, completion, from - 1, to);
362
- };
363
- if (enableAutocompletion) {
356
+ }, []);
357
+ const autocompletionExt = react_1.default.useMemo(() => {
358
+ if (!enableAutocompletion)
359
+ return null;
364
360
  const langDict = dict_1.default.filter(({ lang = 'pod6' }) => lang === 'pod6');
365
361
  const completions = langDict.map(({ displayText, text }) => {
366
362
  function cleanBraces(text) {
@@ -375,8 +371,6 @@ function PodliteEditorInternal(props, ref) {
375
371
  });
376
372
  function myCompletions(context) {
377
373
  let before = context.matchBefore(/^\s*=\w*/);
378
- // If completion wasn't explicitly started and there
379
- // is no word before the cursor, don't open completions.
380
374
  if (!context.explicit && !before)
381
375
  return null;
382
376
  return {
@@ -385,11 +379,51 @@ function PodliteEditorInternal(props, ref) {
385
379
  validFor: /^=\w*$/,
386
380
  };
387
381
  }
388
- extensionsData.push((0, autocomplete_1.autocompletion)({ override: [myCompletions] }));
389
- }
390
- useDebouncedEffect(() => {
391
- setValue(text1);
392
- }, [text1], 50);
382
+ return (0, autocomplete_1.autocompletion)({ override: [myCompletions] });
383
+ }, [enableAutocompletion, makeApply]);
384
+ const extensionsData = react_1.default.useMemo(() => {
385
+ const exts = [
386
+ (0, podlite_2.podliteLang)(),
387
+ view_1.EditorView.lineWrapping,
388
+ stateChangeListener,
389
+ listContinuation_1.itemLevelKeymap,
390
+ listContinuation_1.listContinuationKeymap,
391
+ preventToggleComment,
392
+ ];
393
+ if (enableFolding) {
394
+ exts.push(foldPodlite_1.podliteFoldService, (0, language_1.foldGutter)(), view_1.keymap.of(language_1.foldKeymap));
395
+ }
396
+ if (onOpenLink) {
397
+ exts.push(modKeyClass, openStyledLinkOnClick, linkTheme);
398
+ }
399
+ if (enableScroll) {
400
+ exts.push(scrollExtensions);
401
+ }
402
+ if (autocompletionExt) {
403
+ exts.push(autocompletionExt);
404
+ }
405
+ return exts;
406
+ }, [
407
+ stateChangeListener,
408
+ modKeyClass,
409
+ openStyledLinkOnClick,
410
+ linkTheme,
411
+ !!onOpenLink,
412
+ enableScroll,
413
+ scrollExtensions,
414
+ preventToggleComment,
415
+ autocompletionExt,
416
+ enableFolding,
417
+ ]);
418
+ // Debounced preview update: check ref every 300ms, update value state only when text changed
419
+ (0, react_1.useEffect)(() => {
420
+ const interval = setInterval(() => {
421
+ if (pendingTextRef.current !== value) {
422
+ setValue(pendingTextRef.current);
423
+ }
424
+ }, 300);
425
+ return () => clearInterval(interval);
426
+ }, [value]);
393
427
  (0, react_1.useEffect)(() => {
394
428
  if (!isBrowser || !container.current)
395
429
  return;
@@ -427,18 +461,14 @@ function PodliteEditorInternal(props, ref) {
427
461
  }, 100);
428
462
  return () => clearTimeout(timer);
429
463
  }, [startLinePreview, enablePreview]); // Only run when startLinePreview or enablePreview changes
430
- const handleChange = (value, viewUpdate) => {
431
- setText(value);
432
- onChange && onChange(value, viewUpdate);
433
- if (onEditorStateChange) {
434
- const view = viewUpdate.view;
435
- onEditorStateChange({
436
- cursorOffset: view.state.selection.main.head,
437
- scrollTop: view.scrollDOM.scrollTop,
438
- foldedRanges: serializeFoldedRanges(view),
439
- });
440
- }
441
- };
464
+ const handleChange = (0, react_1.useCallback)((value, viewUpdate) => {
465
+ pendingTextRef.current = value;
466
+ // Defer parent callback to next frame so CodeMirror textupdate handler
467
+ // completes immediately and the typed character appears without delay
468
+ requestAnimationFrame(() => {
469
+ onChange?.(value, viewUpdate);
470
+ });
471
+ }, [onChange]);
442
472
  (0, react_1.useEffect)(() => {
443
473
  if (preview.current) {
444
474
  const $preview = preview.current;
@@ -589,7 +619,7 @@ function PodliteEditorInternal(props, ref) {
589
619
  };
590
620
  const conentView = (react_1.default.createElement("div", { className: `podlite-editor-content`, style: { height: codemirrorProps?.height } },
591
621
  react_1.default.createElement("div", { className: `podlite-editor-content-editor`, ref: containerEditor },
592
- react_1.default.createElement(react_codemirror_1.default, { theme: theme_1.defaultTheme, indentWithTab: false, ...{ ...codemirrorProps, ...{ basicSetup: { defaultKeymap: false } } }, className: `podlite-editor-inner`, extensions: extensionsData, height: height, ref: codeMirror, onChange: handleChange })),
622
+ react_1.default.createElement(react_codemirror_1.default, { theme: theme_1.defaultTheme, indentWithTab: false, autoFocus: true, ...codemirrorProps, basicSetup: basicSetupConfig, className: `podlite-editor-inner`, extensions: extensionsData, height: height, ref: codeMirror, onChange: handleChange })),
593
623
  enablePreview && (react_1.default.createElement("div", { className: clsPreview, style: { overflow: full_preview ? 'visible' : 'hidden' }, ref: preview }, previewContent()))));
594
624
  return (react_1.default.createElement("div", null,
595
625
  react_1.default.createElement("div", { className: cls, ref: container }, conentView),