@uiw/react-codemirror 4.23.13 → 4.24.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 +1 -0
- package/cjs/timeoutLatch.d.ts +22 -0
- package/cjs/timeoutLatch.js +118 -0
- package/cjs/useCodeMirror.d.ts +1 -0
- package/cjs/useCodeMirror.js +51 -10
- package/dist/codemirror.js +255 -210
- package/dist/codemirror.min.js +1 -1
- package/esm/timeoutLatch.d.ts +22 -0
- package/esm/timeoutLatch.js +88 -0
- package/esm/useCodeMirror.d.ts +1 -0
- package/esm/useCodeMirror.js +46 -10
- package/package.json +2 -2
- package/src/timeoutLatch.ts +98 -0
- package/src/useCodeMirror.ts +40 -6
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare class TimeoutLatch {
|
|
2
|
+
private timeLeftMS;
|
|
3
|
+
private timeoutMS;
|
|
4
|
+
private isCancelled;
|
|
5
|
+
private isTimeExhausted;
|
|
6
|
+
private callbacks;
|
|
7
|
+
constructor(callback: Function, timeoutMS: number);
|
|
8
|
+
tick(): void;
|
|
9
|
+
cancel(): void;
|
|
10
|
+
reset(): void;
|
|
11
|
+
get isDone(): boolean;
|
|
12
|
+
}
|
|
13
|
+
declare class Scheduler {
|
|
14
|
+
private interval;
|
|
15
|
+
private latches;
|
|
16
|
+
add(latch: TimeoutLatch): void;
|
|
17
|
+
remove(latch: TimeoutLatch): void;
|
|
18
|
+
private start;
|
|
19
|
+
private stop;
|
|
20
|
+
}
|
|
21
|
+
export declare const getScheduler: () => Scheduler;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Setting / Unsetting timeouts for every keystroke was a significant overhead
|
|
2
|
+
// Inspired from https://github.com/iostreamer-X/timeout-latch
|
|
3
|
+
|
|
4
|
+
export class TimeoutLatch {
|
|
5
|
+
constructor(callback, timeoutMS) {
|
|
6
|
+
this.timeLeftMS = void 0;
|
|
7
|
+
this.timeoutMS = void 0;
|
|
8
|
+
this.isCancelled = false;
|
|
9
|
+
this.isTimeExhausted = false;
|
|
10
|
+
this.callbacks = [];
|
|
11
|
+
this.timeLeftMS = timeoutMS;
|
|
12
|
+
this.timeoutMS = timeoutMS;
|
|
13
|
+
this.callbacks.push(callback);
|
|
14
|
+
}
|
|
15
|
+
tick() {
|
|
16
|
+
if (!this.isCancelled && !this.isTimeExhausted) {
|
|
17
|
+
this.timeLeftMS--;
|
|
18
|
+
if (this.timeLeftMS <= 0) {
|
|
19
|
+
this.isTimeExhausted = true;
|
|
20
|
+
var callbacks = this.callbacks.slice();
|
|
21
|
+
this.callbacks.length = 0;
|
|
22
|
+
callbacks.forEach(callback => {
|
|
23
|
+
try {
|
|
24
|
+
callback();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('TimeoutLatch callback error:', error);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
cancel() {
|
|
33
|
+
this.isCancelled = true;
|
|
34
|
+
this.callbacks.length = 0;
|
|
35
|
+
}
|
|
36
|
+
reset() {
|
|
37
|
+
this.timeLeftMS = this.timeoutMS;
|
|
38
|
+
this.isCancelled = false;
|
|
39
|
+
this.isTimeExhausted = false;
|
|
40
|
+
}
|
|
41
|
+
get isDone() {
|
|
42
|
+
return this.isCancelled || this.isTimeExhausted;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class Scheduler {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.interval = null;
|
|
48
|
+
this.latches = new Set();
|
|
49
|
+
}
|
|
50
|
+
add(latch) {
|
|
51
|
+
this.latches.add(latch);
|
|
52
|
+
this.start();
|
|
53
|
+
}
|
|
54
|
+
remove(latch) {
|
|
55
|
+
this.latches.delete(latch);
|
|
56
|
+
if (this.latches.size === 0) {
|
|
57
|
+
this.stop();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
start() {
|
|
61
|
+
if (this.interval === null) {
|
|
62
|
+
this.interval = setInterval(() => {
|
|
63
|
+
this.latches.forEach(latch => {
|
|
64
|
+
latch.tick();
|
|
65
|
+
if (latch.isDone) {
|
|
66
|
+
this.remove(latch);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}, 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
stop() {
|
|
73
|
+
if (this.interval !== null) {
|
|
74
|
+
clearInterval(this.interval);
|
|
75
|
+
this.interval = null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
var globalScheduler = null;
|
|
80
|
+
export var getScheduler = () => {
|
|
81
|
+
if (typeof window === 'undefined') {
|
|
82
|
+
return new Scheduler();
|
|
83
|
+
}
|
|
84
|
+
if (!globalScheduler) {
|
|
85
|
+
globalScheduler = new Scheduler();
|
|
86
|
+
}
|
|
87
|
+
return globalScheduler;
|
|
88
|
+
};
|
package/esm/useCodeMirror.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EditorState } from '@codemirror/state';
|
|
2
2
|
import { EditorView } from '@codemirror/view';
|
|
3
3
|
import { type ReactCodeMirrorProps } from '.';
|
|
4
|
+
export declare const ExternalChange: import("@codemirror/state").AnnotationType<boolean>;
|
|
4
5
|
export interface UseCodeMirror extends ReactCodeMirrorProps {
|
|
5
6
|
container?: HTMLDivElement | null;
|
|
6
7
|
}
|
package/esm/useCodeMirror.js
CHANGED
|
@@ -3,7 +3,10 @@ import { Annotation, EditorState, StateEffect } from '@codemirror/state';
|
|
|
3
3
|
import { EditorView } from '@codemirror/view';
|
|
4
4
|
import { getDefaultExtensions } from "./getDefaultExtensions.js";
|
|
5
5
|
import { getStatistics } from "./utils.js";
|
|
6
|
-
|
|
6
|
+
import { TimeoutLatch, getScheduler } from "./timeoutLatch.js";
|
|
7
|
+
export var ExternalChange = Annotation.define();
|
|
8
|
+
var TYPING_TIMOUT = 200; // ms
|
|
9
|
+
|
|
7
10
|
var emptyExtensions = [];
|
|
8
11
|
export function useCodeMirror(props) {
|
|
9
12
|
var {
|
|
@@ -33,6 +36,12 @@ export function useCodeMirror(props) {
|
|
|
33
36
|
var [container, setContainer] = useState();
|
|
34
37
|
var [view, setView] = useState();
|
|
35
38
|
var [state, setState] = useState();
|
|
39
|
+
var typingLatch = useState(() => ({
|
|
40
|
+
current: null
|
|
41
|
+
}))[0];
|
|
42
|
+
var pendingUpdate = useState(() => ({
|
|
43
|
+
current: null
|
|
44
|
+
}))[0];
|
|
36
45
|
var defaultThemeOption = EditorView.theme({
|
|
37
46
|
'&': {
|
|
38
47
|
height,
|
|
@@ -50,7 +59,20 @@ export function useCodeMirror(props) {
|
|
|
50
59
|
if (vu.docChanged && typeof onChange === 'function' &&
|
|
51
60
|
// Fix echoing of the remote changes:
|
|
52
61
|
// If transaction is market as remote we don't have to call `onChange` handler again
|
|
53
|
-
!vu.transactions.some(tr => tr.annotation(
|
|
62
|
+
!vu.transactions.some(tr => tr.annotation(ExternalChange))) {
|
|
63
|
+
if (typingLatch.current) {
|
|
64
|
+
typingLatch.current.reset();
|
|
65
|
+
} else {
|
|
66
|
+
typingLatch.current = new TimeoutLatch(() => {
|
|
67
|
+
if (pendingUpdate.current) {
|
|
68
|
+
var forceUpdate = pendingUpdate.current;
|
|
69
|
+
pendingUpdate.current = null;
|
|
70
|
+
forceUpdate();
|
|
71
|
+
}
|
|
72
|
+
typingLatch.current = null;
|
|
73
|
+
}, TYPING_TIMOUT);
|
|
74
|
+
getScheduler().add(typingLatch.current);
|
|
75
|
+
}
|
|
54
76
|
var doc = vu.state.doc;
|
|
55
77
|
var _value = doc.toString();
|
|
56
78
|
onChange(_value, vu);
|
|
@@ -106,6 +128,10 @@ export function useCodeMirror(props) {
|
|
|
106
128
|
view.destroy();
|
|
107
129
|
setView(undefined);
|
|
108
130
|
}
|
|
131
|
+
if (typingLatch.current) {
|
|
132
|
+
typingLatch.current.cancel();
|
|
133
|
+
typingLatch.current = null;
|
|
134
|
+
}
|
|
109
135
|
}, [view]);
|
|
110
136
|
useEffect(() => {
|
|
111
137
|
if (autoFocus && view) {
|
|
@@ -126,14 +152,24 @@ export function useCodeMirror(props) {
|
|
|
126
152
|
}
|
|
127
153
|
var currentValue = view ? view.state.doc.toString() : '';
|
|
128
154
|
if (view && value !== currentValue) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
155
|
+
var isTyping = typingLatch.current && !typingLatch.current.isDone;
|
|
156
|
+
var forceUpdate = () => {
|
|
157
|
+
if (view && value !== view.state.doc.toString()) {
|
|
158
|
+
view.dispatch({
|
|
159
|
+
changes: {
|
|
160
|
+
from: 0,
|
|
161
|
+
to: view.state.doc.toString().length,
|
|
162
|
+
insert: value || ''
|
|
163
|
+
},
|
|
164
|
+
annotations: [ExternalChange.of(true)]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
if (!isTyping) {
|
|
169
|
+
forceUpdate();
|
|
170
|
+
} else {
|
|
171
|
+
pendingUpdate.current = forceUpdate;
|
|
172
|
+
}
|
|
137
173
|
}
|
|
138
174
|
}, [value, view]);
|
|
139
175
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uiw/react-codemirror",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.24.0",
|
|
4
4
|
"description": "CodeMirror component for React.",
|
|
5
5
|
"homepage": "https://uiwjs.github.io/react-codemirror",
|
|
6
6
|
"funding": "https://jaywcjlove.github.io/#/sponsor",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@codemirror/commands": "^6.1.0",
|
|
48
48
|
"@codemirror/state": "^6.1.1",
|
|
49
49
|
"@codemirror/theme-one-dark": "^6.0.0",
|
|
50
|
-
"@uiw/codemirror-extensions-basic-setup": "4.
|
|
50
|
+
"@uiw/codemirror-extensions-basic-setup": "4.24.0",
|
|
51
51
|
"codemirror": "^6.0.0"
|
|
52
52
|
},
|
|
53
53
|
"keywords": [
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Setting / Unsetting timeouts for every keystroke was a significant overhead
|
|
2
|
+
// Inspired from https://github.com/iostreamer-X/timeout-latch
|
|
3
|
+
|
|
4
|
+
export class TimeoutLatch {
|
|
5
|
+
private timeLeftMS: number;
|
|
6
|
+
private timeoutMS: number;
|
|
7
|
+
private isCancelled = false;
|
|
8
|
+
private isTimeExhausted = false;
|
|
9
|
+
private callbacks: Function[] = [];
|
|
10
|
+
|
|
11
|
+
constructor(callback: Function, timeoutMS: number) {
|
|
12
|
+
this.timeLeftMS = timeoutMS;
|
|
13
|
+
this.timeoutMS = timeoutMS;
|
|
14
|
+
this.callbacks.push(callback);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
tick(): void {
|
|
18
|
+
if (!this.isCancelled && !this.isTimeExhausted) {
|
|
19
|
+
this.timeLeftMS--;
|
|
20
|
+
if (this.timeLeftMS <= 0) {
|
|
21
|
+
this.isTimeExhausted = true;
|
|
22
|
+
const callbacks = this.callbacks.slice();
|
|
23
|
+
this.callbacks.length = 0;
|
|
24
|
+
callbacks.forEach((callback) => {
|
|
25
|
+
try {
|
|
26
|
+
callback();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('TimeoutLatch callback error:', error);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
cancel(): void {
|
|
36
|
+
this.isCancelled = true;
|
|
37
|
+
this.callbacks.length = 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
reset(): void {
|
|
41
|
+
this.timeLeftMS = this.timeoutMS;
|
|
42
|
+
this.isCancelled = false;
|
|
43
|
+
this.isTimeExhausted = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get isDone(): boolean {
|
|
47
|
+
return this.isCancelled || this.isTimeExhausted;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class Scheduler {
|
|
52
|
+
private interval: NodeJS.Timeout | null = null;
|
|
53
|
+
private latches = new Set<TimeoutLatch>();
|
|
54
|
+
|
|
55
|
+
add(latch: TimeoutLatch): void {
|
|
56
|
+
this.latches.add(latch);
|
|
57
|
+
this.start();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
remove(latch: TimeoutLatch): void {
|
|
61
|
+
this.latches.delete(latch);
|
|
62
|
+
if (this.latches.size === 0) {
|
|
63
|
+
this.stop();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private start(): void {
|
|
68
|
+
if (this.interval === null) {
|
|
69
|
+
this.interval = setInterval(() => {
|
|
70
|
+
this.latches.forEach((latch) => {
|
|
71
|
+
latch.tick();
|
|
72
|
+
if (latch.isDone) {
|
|
73
|
+
this.remove(latch);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}, 1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private stop(): void {
|
|
81
|
+
if (this.interval !== null) {
|
|
82
|
+
clearInterval(this.interval);
|
|
83
|
+
this.interval = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let globalScheduler: Scheduler | null = null;
|
|
89
|
+
|
|
90
|
+
export const getScheduler = (): Scheduler => {
|
|
91
|
+
if (typeof window === 'undefined') {
|
|
92
|
+
return new Scheduler();
|
|
93
|
+
}
|
|
94
|
+
if (!globalScheduler) {
|
|
95
|
+
globalScheduler = new Scheduler();
|
|
96
|
+
}
|
|
97
|
+
return globalScheduler;
|
|
98
|
+
};
|
package/src/useCodeMirror.ts
CHANGED
|
@@ -4,8 +4,10 @@ import { EditorView, type ViewUpdate } from '@codemirror/view';
|
|
|
4
4
|
import { getDefaultExtensions } from './getDefaultExtensions';
|
|
5
5
|
import { getStatistics } from './utils';
|
|
6
6
|
import { type ReactCodeMirrorProps } from '.';
|
|
7
|
+
import { TimeoutLatch, getScheduler } from './timeoutLatch';
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
+
export const ExternalChange = Annotation.define<boolean>();
|
|
10
|
+
const TYPING_TIMOUT = 200; // ms
|
|
9
11
|
|
|
10
12
|
export interface UseCodeMirror extends ReactCodeMirrorProps {
|
|
11
13
|
container?: HTMLDivElement | null;
|
|
@@ -41,6 +43,8 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|
|
41
43
|
const [container, setContainer] = useState<HTMLDivElement | null>();
|
|
42
44
|
const [view, setView] = useState<EditorView>();
|
|
43
45
|
const [state, setState] = useState<EditorState>();
|
|
46
|
+
const typingLatch = useState<{ current: TimeoutLatch | null }>(() => ({ current: null }))[0];
|
|
47
|
+
const pendingUpdate = useState<{ current: (() => void) | null }>(() => ({ current: null }))[0];
|
|
44
48
|
const defaultThemeOption = EditorView.theme({
|
|
45
49
|
'&': {
|
|
46
50
|
height,
|
|
@@ -60,8 +64,22 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|
|
60
64
|
typeof onChange === 'function' &&
|
|
61
65
|
// Fix echoing of the remote changes:
|
|
62
66
|
// If transaction is market as remote we don't have to call `onChange` handler again
|
|
63
|
-
!vu.transactions.some((tr) => tr.annotation(
|
|
67
|
+
!vu.transactions.some((tr) => tr.annotation(ExternalChange))
|
|
64
68
|
) {
|
|
69
|
+
if (typingLatch.current) {
|
|
70
|
+
typingLatch.current.reset();
|
|
71
|
+
} else {
|
|
72
|
+
typingLatch.current = new TimeoutLatch(() => {
|
|
73
|
+
if (pendingUpdate.current) {
|
|
74
|
+
const forceUpdate = pendingUpdate.current;
|
|
75
|
+
pendingUpdate.current = null;
|
|
76
|
+
forceUpdate();
|
|
77
|
+
}
|
|
78
|
+
typingLatch.current = null;
|
|
79
|
+
}, TYPING_TIMOUT);
|
|
80
|
+
getScheduler().add(typingLatch.current);
|
|
81
|
+
}
|
|
82
|
+
|
|
65
83
|
const doc = vu.state.doc;
|
|
66
84
|
const value = doc.toString();
|
|
67
85
|
onChange(value, vu);
|
|
@@ -126,6 +144,10 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|
|
126
144
|
view.destroy();
|
|
127
145
|
setView(undefined);
|
|
128
146
|
}
|
|
147
|
+
if (typingLatch.current) {
|
|
148
|
+
typingLatch.current.cancel();
|
|
149
|
+
typingLatch.current = null;
|
|
150
|
+
}
|
|
129
151
|
},
|
|
130
152
|
[view],
|
|
131
153
|
);
|
|
@@ -165,10 +187,22 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|
|
165
187
|
}
|
|
166
188
|
const currentValue = view ? view.state.doc.toString() : '';
|
|
167
189
|
if (view && value !== currentValue) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
190
|
+
const isTyping = typingLatch.current && !typingLatch.current.isDone;
|
|
191
|
+
|
|
192
|
+
const forceUpdate = () => {
|
|
193
|
+
if (view && value !== view.state.doc.toString()) {
|
|
194
|
+
view.dispatch({
|
|
195
|
+
changes: { from: 0, to: view.state.doc.toString().length, insert: value || '' },
|
|
196
|
+
annotations: [ExternalChange.of(true)],
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (!isTyping) {
|
|
202
|
+
forceUpdate();
|
|
203
|
+
} else {
|
|
204
|
+
pendingUpdate.current = forceUpdate;
|
|
205
|
+
}
|
|
172
206
|
}
|
|
173
207
|
}, [value, view]);
|
|
174
208
|
|