@podlite/editor-react 0.0.40 → 0.0.42

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.
@@ -0,0 +1,2 @@
1
+ import type { Extension } from '@codemirror/state';
2
+ export declare const podliteFoldService: Extension;
@@ -0,0 +1,108 @@
1
+ import { foldService } from '@codemirror/language';
2
+ // =begin Name ... =end Name
3
+ const beginRe = /^(\s*)=begin\s+(\S+)/;
4
+ const endRe = /^(\s*)=end\s+(\S+)/;
5
+ // =headN (level 1-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']);
9
+ // Parse =begin line, return block name or null
10
+ function parseBeginLine(lineText) {
11
+ const m = lineText.match(beginRe);
12
+ return m ? m[2] ?? null : null;
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
+ }
45
+ // Fold =begin Name ... =end Name: fold from end of =begin line to start of =end line
46
+ function foldBeginEnd(state, lineStart) {
47
+ const line = state.doc.lineAt(lineStart);
48
+ const blockName = parseBeginLine(line.text);
49
+ if (!blockName)
50
+ return null;
51
+ // Search forward for matching =end
52
+ for (let i = line.number + 1; i <= state.doc.lines; i++) {
53
+ const candidate = state.doc.line(i);
54
+ const m = candidate.text.match(endRe);
55
+ if (m && m[2] === blockName) {
56
+ // Fold from end of =begin line to start of =end line
57
+ if (line.to >= candidate.from)
58
+ return null;
59
+ return { from: line.to, to: candidate.from };
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ // Fold =headN: from end of heading line to start of next heading of same or higher level (or EOF)
65
+ function foldHeadSection(state, lineStart) {
66
+ const line = state.doc.lineAt(lineStart);
67
+ const m = line.text.match(headRe);
68
+ if (!m)
69
+ return null;
70
+ const level = parseInt(m[2] ?? '1', 10);
71
+ // Search forward for next heading of same or higher level
72
+ let foldEnd = state.doc.length;
73
+ for (let i = line.number + 1; i <= state.doc.lines; i++) {
74
+ const candidate = state.doc.line(i);
75
+ const hm = candidate.text.match(headRe);
76
+ if (hm) {
77
+ const candidateLevel = parseInt(hm[2] ?? '1', 10);
78
+ if (candidateLevel <= level) {
79
+ foldEnd = candidate.from;
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ // Nothing to fold if heading is last line or only whitespace follows
85
+ if (line.to >= foldEnd)
86
+ return null;
87
+ // Trim trailing blank lines from fold range
88
+ let endPos = foldEnd;
89
+ while (endPos > line.to) {
90
+ const prevLine = state.doc.lineAt(endPos - 1);
91
+ if (prevLine.text.trim() !== '') {
92
+ endPos = prevLine.to;
93
+ break;
94
+ }
95
+ endPos = prevLine.from;
96
+ }
97
+ if (line.to >= endPos)
98
+ return null;
99
+ return { from: line.to, to: endPos };
100
+ }
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
+ });
108
+ //# sourceMappingURL=foldPodlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foldPodlite.js","sourceRoot":"","sources":["../src/foldPodlite.ts"],"names":[],"mappings":"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"}
package/esm/index.d.ts CHANGED
@@ -4,4 +4,6 @@ import HighlightedCode from './HighlightedCode';
4
4
  export { Editor2 as Editor2 };
5
5
  export { WindowWrapper as WindowWrapper };
6
6
  export { HighlightedCode as HighlightedCode };
7
+ export type { EditorSessionState } from './types';
8
+ export type { ConverterResult, IPodliteEditor, PodliteEditorRef } from './Editor';
7
9
  export default Editor2;
package/esm/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,UAAU,CAAA;AAC9B,OAAO,aAAa,MAAM,WAAW,CAAA;AACrC,OAAO,eAAe,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,CAAA;AAC7B,OAAO,EAAE,aAAa,IAAI,aAAa,EAAE,CAAA;AACzC,OAAO,EAAE,eAAe,IAAI,eAAe,EAAE,CAAA;AAC7C,eAAe,OAAO,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,UAAU,CAAA;AAC9B,OAAO,aAAa,MAAM,WAAW,CAAA;AACrC,OAAO,eAAe,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,CAAA;AAC7B,OAAO,EAAE,aAAa,IAAI,aAAa,EAAE,CAAA;AACzC,OAAO,EAAE,eAAe,IAAI,eAAe,EAAE,CAAA;AAG7C,eAAe,OAAO,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { Extension } from '@codemirror/state';
2
+ export declare const listContinuationKeymap: Extension;
3
+ export declare const itemLevelKeymap: Extension;
@@ -0,0 +1,124 @@
1
+ import { keymap } from '@codemirror/view';
2
+ // Regex: =item with optional level digit(s) + separator
3
+ const itemRe = /^(\s*=item)(\d*)([ \t])/;
4
+ // Detect list type prefix at start of content
5
+ const prefixRe = /^(\[ \]|\[x\]|\[X\]|#)\s*/;
6
+ // Item is "empty" if content is nothing or only a type prefix
7
+ const emptyContentRe = /^(\[ \]|\[x\]|\[X\]|#)?\s*$/;
8
+ // Parse, Don't Validate — returns typed structure or null
9
+ function parseItemLine(lineText, lineFrom) {
10
+ const m = lineText.match(itemRe);
11
+ if (!m)
12
+ return null;
13
+ const prefix = m[1] ?? ''; // "\s*=item"
14
+ const digits = m[2] ?? ''; // level digits, may be empty
15
+ const sep = m[3] ?? ' '; // separator char
16
+ const indent = prefix.slice(0, prefix.length - 5); // whitespace before "=item"
17
+ const level = digits === '' ? 1 : parseInt(digits, 10);
18
+ const marker = `${indent}=item${digits}`;
19
+ return {
20
+ indent,
21
+ marker,
22
+ level,
23
+ separator: sep,
24
+ markerEnd: lineFrom + prefix.length + digits.length + sep.length,
25
+ content: lineText.slice(prefix.length + digits.length + sep.length),
26
+ };
27
+ }
28
+ // Get the prefix to insert for the new item (checked → unchecked, # → #, none → '')
29
+ function resolveNewPrefix(content) {
30
+ const m = content.trim().match(prefixRe);
31
+ if (!m)
32
+ return '';
33
+ const prefix = m[1] ?? '';
34
+ if (prefix === '[x]' || prefix === '[X]' || prefix === '[ ]')
35
+ return '[ ] ';
36
+ if (prefix === '#')
37
+ return '# ';
38
+ return '';
39
+ }
40
+ // ─── List Continuation on Enter ────────────────────────────────────────────
41
+ export const listContinuationKeymap = keymap.of([
42
+ {
43
+ key: 'Enter',
44
+ run: (view) => {
45
+ const { state } = view;
46
+ const { head, from, to } = state.selection.main;
47
+ // Only single cursor (no selection)
48
+ if (from !== to)
49
+ return false;
50
+ const line = state.doc.lineAt(head);
51
+ const item = parseItemLine(line.text, line.from);
52
+ if (!item)
53
+ return false;
54
+ // Cursor inside marker → default
55
+ if (head < item.markerEnd)
56
+ return false;
57
+ const trimmedContent = item.content.trim();
58
+ // Empty item (including prefix-only like "[ ]" or "#") → remove marker, exit list
59
+ if (emptyContentRe.test(trimmedContent)) {
60
+ view.dispatch({
61
+ changes: { from: line.from, to: line.to, insert: '' },
62
+ selection: { anchor: line.from },
63
+ });
64
+ return true;
65
+ }
66
+ // Determine type prefix for new item
67
+ const newPrefix = resolveNewPrefix(trimmedContent);
68
+ // Split: text before cursor stays, text after → new =itemN [prefix]
69
+ const textAfterCursor = line.text.slice(head - line.from);
70
+ const newItem = `\n${item.marker} ${newPrefix}`;
71
+ view.dispatch({
72
+ changes: {
73
+ from: head,
74
+ to: line.to,
75
+ insert: newItem + textAfterCursor.trimStart(),
76
+ },
77
+ selection: {
78
+ anchor: head + newItem.length,
79
+ },
80
+ });
81
+ return true;
82
+ },
83
+ },
84
+ ]);
85
+ // ─── Tab / Shift-Tab: change item level ────────────────────────────────────
86
+ const maxItemLevel = 6;
87
+ const minItemLevel = 1;
88
+ const indentPerLevel = ' '; // 2 spaces per level
89
+ function changeItemLevel(view, delta) {
90
+ const { state } = view;
91
+ const { from, to } = state.selection.main;
92
+ const startLine = state.doc.lineAt(from);
93
+ const endLine = state.doc.lineAt(to);
94
+ const changes = [];
95
+ for (let lineNum = startLine.number; lineNum <= endLine.number; lineNum++) {
96
+ const line = state.doc.line(lineNum);
97
+ const item = parseItemLine(line.text, line.from);
98
+ if (!item)
99
+ continue;
100
+ const newLevel = item.level + delta;
101
+ if (newLevel < minItemLevel || newLevel > maxItemLevel)
102
+ continue;
103
+ // Adjust indentation: each level = 4 spaces
104
+ const newIndent = delta === 1 ? item.indent + indentPerLevel : item.indent.slice(indentPerLevel.length);
105
+ const oldMarkerLen = item.markerEnd - line.from;
106
+ const newMarker = `${newIndent}=item${newLevel}${item.separator}`;
107
+ changes.push({ from: line.from, to: line.from + oldMarkerLen, insert: newMarker });
108
+ }
109
+ if (changes.length === 0)
110
+ return false;
111
+ view.dispatch({ changes });
112
+ return true;
113
+ }
114
+ export const itemLevelKeymap = keymap.of([
115
+ {
116
+ key: 'Tab',
117
+ run: (view) => changeItemLevel(view, 1),
118
+ },
119
+ {
120
+ key: 'Shift-Tab',
121
+ run: (view) => changeItemLevel(view, -1),
122
+ },
123
+ ]);
124
+ //# sourceMappingURL=listContinuation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listContinuation.js","sourceRoot":"","sources":["../src/listContinuation.ts"],"names":[],"mappings":"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"}
package/esm/types.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ declare type FoldRange = {
2
+ readonly from: number;
3
+ readonly to: number;
4
+ };
5
+ export declare type EditorSessionState = {
6
+ readonly cursorOffset?: number;
7
+ readonly scrollTop?: number;
8
+ readonly foldedRanges?: ReadonlyArray<FoldRange>;
9
+ readonly isPreviewMode?: boolean;
10
+ readonly isHalfPreviewMode?: boolean;
11
+ };
12
+ export {};
package/esm/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/lib/Editor.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { ViewUpdate, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
3
+ import type { EditorSessionState } from './types';
3
4
  export interface ConverterResult {
4
5
  errors?: any;
5
6
  result: JSX.Element | string;
@@ -27,6 +28,10 @@ export interface IPodliteEditor extends ReactCodeMirrorProps {
27
28
  onOpenLink?: (url: string) => void;
28
29
  /** Enable code highlighting */
29
30
  enableHighlighting?: boolean;
31
+ /** Initial editor session state to restore (cursor, scroll, folds) */
32
+ initialEditorState?: EditorSessionState;
33
+ /** Called when editor state changes (cursor move, scroll, fold/unfold) */
34
+ onEditorStateChange?: (state: EditorSessionState) => void;
30
35
  }
31
36
  export interface PodliteEditorRef {
32
37
  editor: React.RefObject<ReactCodeMirrorRef>;
package/lib/Editor.js CHANGED
@@ -10,9 +10,12 @@ const podlite_2 = require("./podlite");
10
10
  const theme_1 = require("./theme");
11
11
  const view_1 = require("@codemirror/view");
12
12
  const commands_1 = require("@codemirror/commands");
13
+ const language_1 = require("@codemirror/language");
13
14
  const react_is_1 = require("react-is");
14
15
  const autocomplete_1 = require("@codemirror/autocomplete");
15
16
  const dict_1 = (0, tslib_1.__importDefault)(require("./dict"));
17
+ const listContinuation_1 = require("./listContinuation");
18
+ const foldPodlite_1 = require("./foldPodlite");
16
19
  const HighlightedCode_1 = (0, tslib_1.__importDefault)(require("./HighlightedCode"));
17
20
  function useDebouncedEffect(fn, deps, time) {
18
21
  const dependencies = [...deps, time];
@@ -30,10 +33,10 @@ function PodliteEditorInternal(props, ref) {
30
33
  if (!isBrowser)
31
34
  return null;
32
35
  const { onChange, enableScroll = true, enablePreview = true, extensions = [], previewWidth = '50%', onPreviewMode, isFullscreen, makePreviewComponent, startLinePreview = 1, // start line number
33
- enableAutocompletion = false, onOpenLink, enableHighlighting = false, ...codemirrorProps } = props;
36
+ enableAutocompletion = false, onOpenLink, enableHighlighting = false, initialEditorState, onEditorStateChange, ...codemirrorProps } = props;
34
37
  const full_preview = previewWidth === '100%';
35
38
  const [value, setValue] = (0, react_1.useState)(props.value || '');
36
- const [text1, setText] = (0, react_1.useState)(props.value || '');
39
+ const pendingTextRef = (0, react_1.useRef)(props.value || '');
37
40
  const codeMirror = (0, react_1.useRef)(null);
38
41
  const container = (0, react_1.useRef)(null);
39
42
  const containerEditor = (0, react_1.useRef)(null);
@@ -41,10 +44,93 @@ function PodliteEditorInternal(props, ref) {
41
44
  const active = (0, react_1.useRef)(full_preview ? 'preview' : 'editor');
42
45
  const topLine = (0, react_1.useRef)(startLinePreview);
43
46
  const $viewHeight = (0, react_1.useRef)(0);
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 }), []);
44
56
  (0, react_1.useImperativeHandle)(ref, () => ({
45
57
  editor: codeMirror,
46
58
  preview: preview,
47
59
  }), [codeMirror]);
60
+ // Helper: serialize current fold ranges from EditorState
61
+ const serializeFoldedRanges = (0, react_1.useCallback)((view) => {
62
+ const ranges = [];
63
+ const folded = (0, language_1.foldedRanges)(view.state);
64
+ const iter = folded.iter();
65
+ while (iter.value) {
66
+ ranges.push({ from: iter.from, to: iter.to });
67
+ iter.next();
68
+ }
69
+ return ranges;
70
+ }, []);
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
76
+ (0, react_1.useEffect)(() => {
77
+ if (initialStateApplied.current || !initialEditorState)
78
+ return;
79
+ const view = codeMirror.current?.view;
80
+ if (!view)
81
+ return;
82
+ initialStateApplied.current = true;
83
+ // Restore cursor position
84
+ if (initialEditorState.cursorOffset != null) {
85
+ const offset = Math.min(initialEditorState.cursorOffset, view.state.doc.length);
86
+ view.dispatch({ selection: { anchor: offset } });
87
+ }
88
+ // Restore fold ranges
89
+ if (initialEditorState.foldedRanges && initialEditorState.foldedRanges.length > 0) {
90
+ const effects = initialEditorState.foldedRanges
91
+ .filter(r => r.from < view.state.doc.length && r.to <= view.state.doc.length)
92
+ .map(r => language_1.foldEffect.of({ from: r.from, to: r.to }));
93
+ if (effects.length > 0) {
94
+ view.dispatch({ effects });
95
+ }
96
+ }
97
+ // Restore scroll position (after folds applied, so layout is correct)
98
+ // and focus the editor so the blinking cursor is visible
99
+ requestAnimationFrame(() => {
100
+ if (initialEditorState.scrollTop != null) {
101
+ view.scrollDOM.scrollTop = initialEditorState.scrollTop;
102
+ }
103
+ view.focus();
104
+ });
105
+ }, [initialEditorState, codeMirror.current?.view]);
106
+ // Expose editor state changes via callback
107
+ (0, react_1.useEffect)(() => {
108
+ if (!onEditorStateChange)
109
+ return;
110
+ const view = codeMirror.current?.view;
111
+ if (!view)
112
+ return;
113
+ const emitState = () => {
114
+ onEditorStateChange({
115
+ cursorOffset: view.state.selection.main.head,
116
+ scrollTop: view.scrollDOM.scrollTop,
117
+ foldedRanges: serializeFoldedRanges(view),
118
+ });
119
+ };
120
+ // Debounce scroll events
121
+ let scrollTimer = null;
122
+ const handleScroll = () => {
123
+ if (scrollTimer)
124
+ clearTimeout(scrollTimer);
125
+ scrollTimer = setTimeout(emitState, 200);
126
+ };
127
+ view.scrollDOM.addEventListener('scroll', handleScroll);
128
+ return () => {
129
+ view.scrollDOM.removeEventListener('scroll', handleScroll);
130
+ if (scrollTimer)
131
+ clearTimeout(scrollTimer);
132
+ };
133
+ }, [onEditorStateChange, codeMirror.current?.view, serializeFoldedRanges]);
48
134
  const height = isFullscreen
49
135
  ? '100%'
50
136
  : typeof codemirrorProps.height === 'number'
@@ -52,7 +138,9 @@ function PodliteEditorInternal(props, ref) {
52
138
  : codemirrorProps.height;
53
139
  const preValue = props.value;
54
140
  (0, react_1.useEffect)(() => setValue(preValue ?? ''), [preValue]);
55
- (0, react_1.useEffect)(() => setText(preValue ?? ''), [preValue]);
141
+ (0, react_1.useEffect)(() => {
142
+ pendingTextRef.current = preValue ?? '';
143
+ }, [preValue]);
56
144
  const fullRef = (0, react_1.useRef)(isFullscreen);
57
145
  (0, react_1.useEffect)(() => {
58
146
  fullRef.current = isFullscreen;
@@ -207,8 +295,9 @@ function PodliteEditorInternal(props, ref) {
207
295
  },
208
296
  ...commands_1.defaultKeymap,
209
297
  ]);
210
- // Add/remove a class on the editor root while Mod (Cmd/Ctrl) is pressed
211
- 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({
212
301
  keydown(e, view) {
213
302
  if (e.metaKey || e.ctrlKey)
214
303
  view.dom.classList.add('cm-mod-pressed');
@@ -220,12 +309,11 @@ function PodliteEditorInternal(props, ref) {
220
309
  blur(_, view) {
221
310
  view.dom.classList.remove('cm-mod-pressed');
222
311
  },
223
- // defensive: if mouse leaves, clear the class (helps when key is released elsewhere)
224
312
  mouseleave(_, view) {
225
313
  view.dom.classList.remove('cm-mod-pressed');
226
314
  },
227
- });
228
- const openStyledLinkOnClick = view_1.EditorView.domEventHandlers({
315
+ }), []);
316
+ const openStyledLinkOnClick = react_1.default.useMemo(() => view_1.EditorView.domEventHandlers({
229
317
  click(event, view) {
230
318
  const target = event.target;
231
319
  if (!target)
@@ -233,35 +321,42 @@ function PodliteEditorInternal(props, ref) {
233
321
  const el = target.closest('.cm-clickable-link');
234
322
  if (!el)
235
323
  return;
236
- // require Cmd/Ctrl for activation
237
324
  const modPressed = event.metaKey || event.ctrlKey;
238
325
  if (!modPressed)
239
326
  return;
240
327
  const from = view.posAtDOM(el, 0);
241
328
  const to = view.posAtDOM(el, el.childNodes.length);
242
329
  const text = view.state.doc.sliceString(from, to).trim();
243
- if (onOpenLink) {
244
- onOpenLink(text); // i.e window.open(text, '_blank', 'noopener')
245
- }
330
+ onOpenLinkRef.current?.(text);
246
331
  event.preventDefault();
247
332
  event.stopPropagation();
248
333
  },
249
- });
250
- let extensionsData = [(0, podlite_2.podliteLang)(), view_1.EditorView.lineWrapping, preventToggleComment];
251
- if (onOpenLink) {
252
- extensionsData.push(modKeyClass, openStyledLinkOnClick, view_1.EditorView.theme({
253
- '&.cm-editor.cm-mod-pressed .cm-clickable-link:hover': {
254
- cursor: 'pointer',
255
- },
256
- }));
257
- }
258
- if (enableScroll) {
259
- extensionsData.push(scrollExtensions);
260
- }
261
- const makeApply = (text) => (editor, completion, from, to) => {
334
+ }), []);
335
+ // Track fold/unfold and cursor changes to emit editor state
336
+ const stateChangeListener = react_1.default.useMemo(() => onEditorStateChange
337
+ ? view_1.EditorView.updateListener.of(update => {
338
+ const hasFoldChange = update.transactions.some(tr => tr.effects.some(e => e.is(language_1.foldEffect) || e.is(language_1.unfoldEffect)));
339
+ const hasCursorChange = update.selectionSet;
340
+ if (hasFoldChange || hasCursorChange) {
341
+ onEditorStateChangeRef.current?.({
342
+ cursorOffset: update.state.selection.main.head,
343
+ scrollTop: update.view.scrollDOM.scrollTop,
344
+ foldedRanges: serializeFoldedRanges(update.view),
345
+ });
346
+ }
347
+ })
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) => {
262
355
  return (0, autocomplete_1.snippet)(text)(editor, completion, from - 1, to);
263
- };
264
- if (enableAutocompletion) {
356
+ }, []);
357
+ const autocompletionExt = react_1.default.useMemo(() => {
358
+ if (!enableAutocompletion)
359
+ return null;
265
360
  const langDict = dict_1.default.filter(({ lang = 'pod6' }) => lang === 'pod6');
266
361
  const completions = langDict.map(({ displayText, text }) => {
267
362
  function cleanBraces(text) {
@@ -276,8 +371,6 @@ function PodliteEditorInternal(props, ref) {
276
371
  });
277
372
  function myCompletions(context) {
278
373
  let before = context.matchBefore(/^\s*=\w*/);
279
- // If completion wasn't explicitly started and there
280
- // is no word before the cursor, don't open completions.
281
374
  if (!context.explicit && !before)
282
375
  return null;
283
376
  return {
@@ -286,11 +379,50 @@ function PodliteEditorInternal(props, ref) {
286
379
  validFor: /^=\w*$/,
287
380
  };
288
381
  }
289
- extensionsData.push((0, autocomplete_1.autocompletion)({ override: [myCompletions] }));
290
- }
291
- useDebouncedEffect(() => {
292
- setValue(text1);
293
- }, [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
+ foldPodlite_1.podliteFoldService,
389
+ (0, language_1.foldGutter)(),
390
+ view_1.keymap.of(language_1.foldKeymap),
391
+ stateChangeListener,
392
+ listContinuation_1.itemLevelKeymap,
393
+ listContinuation_1.listContinuationKeymap,
394
+ preventToggleComment,
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
+ ]);
417
+ // Debounced preview update: check ref every 300ms, update value state only when text changed
418
+ (0, react_1.useEffect)(() => {
419
+ const interval = setInterval(() => {
420
+ if (pendingTextRef.current !== value) {
421
+ setValue(pendingTextRef.current);
422
+ }
423
+ }, 300);
424
+ return () => clearInterval(interval);
425
+ }, [value]);
294
426
  (0, react_1.useEffect)(() => {
295
427
  if (!isBrowser || !container.current)
296
428
  return;
@@ -328,10 +460,14 @@ function PodliteEditorInternal(props, ref) {
328
460
  }, 100);
329
461
  return () => clearTimeout(timer);
330
462
  }, [startLinePreview, enablePreview]); // Only run when startLinePreview or enablePreview changes
331
- const handleChange = (value, viewUpdate) => {
332
- setText(value);
333
- onChange && onChange(value, viewUpdate);
334
- };
463
+ const handleChange = (0, react_1.useCallback)((value, viewUpdate) => {
464
+ pendingTextRef.current = value;
465
+ // Defer parent callback to next frame so CodeMirror textupdate handler
466
+ // completes immediately and the typed character appears without delay
467
+ requestAnimationFrame(() => {
468
+ onChange?.(value, viewUpdate);
469
+ });
470
+ }, [onChange]);
335
471
  (0, react_1.useEffect)(() => {
336
472
  if (preview.current) {
337
473
  const $preview = preview.current;
@@ -482,7 +618,7 @@ function PodliteEditorInternal(props, ref) {
482
618
  };
483
619
  const conentView = (react_1.default.createElement("div", { className: `podlite-editor-content`, style: { height: codemirrorProps?.height } },
484
620
  react_1.default.createElement("div", { className: `podlite-editor-content-editor`, ref: containerEditor },
485
- react_1.default.createElement(react_codemirror_1.default, { theme: theme_1.defaultTheme, ...{ ...codemirrorProps, ...{ basicSetup: { defaultKeymap: false } } }, className: `podlite-editor-inner`, extensions: extensionsData, height: height, ref: codeMirror, onChange: handleChange })),
621
+ 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 })),
486
622
  enablePreview && (react_1.default.createElement("div", { className: clsPreview, style: { overflow: full_preview ? 'visible' : 'hidden' }, ref: preview }, previewContent()))));
487
623
  return (react_1.default.createElement("div", null,
488
624
  react_1.default.createElement("div", { className: cls, ref: container }, conentView),