@codemirror/view 6.25.1 → 6.26.1
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/CHANGELOG.md +32 -0
- package/dist/index.cjs +123 -42
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +123 -42
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
## 6.26.1 (2024-03-28)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Fix the editor getting stuck in composition when Safari fails to fire a compositionend event for a dead key composition.
|
|
6
|
+
|
|
7
|
+
Fix an issue where, with IME systems that kept the cursor at the start of the composed text, the editor misidentified the target node and disrupted composition.
|
|
8
|
+
|
|
9
|
+
Fix a bug where in a line-wrapped editor, with some content, the initial scroll position would be off from the top of the document.
|
|
10
|
+
|
|
11
|
+
## 6.26.0 (2024-03-14)
|
|
12
|
+
|
|
13
|
+
### Bug fixes
|
|
14
|
+
|
|
15
|
+
Avoid the editor getting confused when iOS autocorrects on pressing Enter and does the correction and the break insertion in two different events.
|
|
16
|
+
|
|
17
|
+
Fix the pasting of copied URIs in iOS.
|
|
18
|
+
|
|
19
|
+
Fix a bug where a scaled editor could keep performing unnecessary updates due to tiny differences in geometry values returned by the browser.
|
|
20
|
+
|
|
21
|
+
Fix a bug where, on iOS with a physical keyboard, the modifiers for some keys weren't being passed to the keymaps.
|
|
22
|
+
|
|
23
|
+
Work around the fact that Mobile Safari makes DOM changes before firing a key event when typing ctrl-d on an external keyboard.
|
|
24
|
+
|
|
25
|
+
Fix an issue where some commands didn't properly scroll the cursor into view on Mobile Safari.
|
|
26
|
+
|
|
27
|
+
Re-measure the document when print settings are changed on Chrome.
|
|
28
|
+
|
|
29
|
+
### New features
|
|
30
|
+
|
|
31
|
+
The `EditorView.scrollHandler` facet can be used to override or extend the behavior of the editor when things are scrolled into view.
|
|
32
|
+
|
|
1
33
|
## 6.25.1 (2024-03-06)
|
|
2
34
|
|
|
3
35
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -61,6 +61,9 @@ function domIndex(node) {
|
|
|
61
61
|
return index;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
function isBlockElement(node) {
|
|
65
|
+
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
|
|
66
|
+
}
|
|
64
67
|
function scanFor(node, off, targetNode, targetOff, dir) {
|
|
65
68
|
for (;;) {
|
|
66
69
|
if (node == targetNode && off == targetOff)
|
|
@@ -93,6 +96,12 @@ function flattenRect(rect, left) {
|
|
|
93
96
|
return { left: x, right: x, top: rect.top, bottom: rect.bottom };
|
|
94
97
|
}
|
|
95
98
|
function windowRect(win) {
|
|
99
|
+
let vp = win.visualViewport;
|
|
100
|
+
if (vp)
|
|
101
|
+
return {
|
|
102
|
+
left: 0, right: vp.width,
|
|
103
|
+
top: 0, bottom: vp.height
|
|
104
|
+
};
|
|
96
105
|
return { left: 0, right: win.innerWidth,
|
|
97
106
|
top: 0, bottom: win.innerHeight };
|
|
98
107
|
}
|
|
@@ -282,8 +291,10 @@ function textRange(node, from, to = from) {
|
|
|
282
291
|
range.setStart(node, from);
|
|
283
292
|
return range;
|
|
284
293
|
}
|
|
285
|
-
function dispatchKey(elt, name, code) {
|
|
294
|
+
function dispatchKey(elt, name, code, mods) {
|
|
286
295
|
let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
|
|
296
|
+
if (mods)
|
|
297
|
+
({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
|
|
287
298
|
let down = new KeyboardEvent("keydown", options);
|
|
288
299
|
down.synthetic = true;
|
|
289
300
|
elt.dispatchEvent(down);
|
|
@@ -334,6 +345,46 @@ function atElementStart(doc, selection) {
|
|
|
334
345
|
function isScrolledToBottom(elt) {
|
|
335
346
|
return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
|
|
336
347
|
}
|
|
348
|
+
function textNodeBefore(startNode, startOffset) {
|
|
349
|
+
for (let node = startNode, offset = startOffset;;) {
|
|
350
|
+
if (node.nodeType == 3 && offset > 0) {
|
|
351
|
+
return { node: node, offset: offset };
|
|
352
|
+
}
|
|
353
|
+
else if (node.nodeType == 1 && offset > 0) {
|
|
354
|
+
if (node.contentEditable == "false")
|
|
355
|
+
return null;
|
|
356
|
+
node = node.childNodes[offset - 1];
|
|
357
|
+
offset = maxOffset(node);
|
|
358
|
+
}
|
|
359
|
+
else if (node.parentNode && !isBlockElement(node)) {
|
|
360
|
+
offset = domIndex(node);
|
|
361
|
+
node = node.parentNode;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function textNodeAfter(startNode, startOffset) {
|
|
369
|
+
for (let node = startNode, offset = startOffset;;) {
|
|
370
|
+
if (node.nodeType == 3 && offset < node.nodeValue.length) {
|
|
371
|
+
return { node: node, offset: offset };
|
|
372
|
+
}
|
|
373
|
+
else if (node.nodeType == 1 && offset < node.childNodes.length) {
|
|
374
|
+
if (node.contentEditable == "false")
|
|
375
|
+
return null;
|
|
376
|
+
node = node.childNodes[offset];
|
|
377
|
+
offset = 0;
|
|
378
|
+
}
|
|
379
|
+
else if (node.parentNode && !isBlockElement(node)) {
|
|
380
|
+
offset = domIndex(node) + 1;
|
|
381
|
+
node = node.parentNode;
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
337
388
|
|
|
338
389
|
class DOMPos {
|
|
339
390
|
constructor(node, offset, precise = true) {
|
|
@@ -2290,6 +2341,7 @@ const perLineTextDirection = state.Facet.define({
|
|
|
2290
2341
|
const nativeSelectionHidden = state.Facet.define({
|
|
2291
2342
|
combine: values => values.some(x => x)
|
|
2292
2343
|
});
|
|
2344
|
+
const scrollHandler = state.Facet.define();
|
|
2293
2345
|
class ScrollTarget {
|
|
2294
2346
|
constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
|
|
2295
2347
|
// This data structure is abused to also store precise scroll
|
|
@@ -2657,6 +2709,7 @@ class DocView extends ContentView {
|
|
|
2657
2709
|
this.hasComposition = null;
|
|
2658
2710
|
this.markedForComposition = new Set;
|
|
2659
2711
|
this.compositionBarrier = Decoration.none;
|
|
2712
|
+
this.lastCompositionAfterCursor = false;
|
|
2660
2713
|
// Track a minimum width for the editor. When measuring sizes in
|
|
2661
2714
|
// measureVisibleLineHeights, this is updated to point at the width
|
|
2662
2715
|
// of a given element and its extent in the document. When a change
|
|
@@ -2873,7 +2926,7 @@ class DocView extends ContentView {
|
|
|
2873
2926
|
if (browser.gecko) {
|
|
2874
2927
|
let nextTo = nextToUneditable(anchor.node, anchor.offset);
|
|
2875
2928
|
if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
|
|
2876
|
-
let text =
|
|
2929
|
+
let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
|
|
2877
2930
|
if (text)
|
|
2878
2931
|
anchor = new DOMPos(text.node, text.offset);
|
|
2879
2932
|
}
|
|
@@ -3188,6 +3241,15 @@ class DocView extends ContentView {
|
|
|
3188
3241
|
this.view.scrollDOM.scrollLeft = target.xMargin;
|
|
3189
3242
|
return;
|
|
3190
3243
|
}
|
|
3244
|
+
for (let handler of this.view.state.facet(scrollHandler)) {
|
|
3245
|
+
try {
|
|
3246
|
+
if (handler(this.view, target.range, target))
|
|
3247
|
+
return true;
|
|
3248
|
+
}
|
|
3249
|
+
catch (e) {
|
|
3250
|
+
logException(this.view.state, e, "scroll handler");
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3191
3253
|
let { range } = target;
|
|
3192
3254
|
let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
|
|
3193
3255
|
if (!rect)
|
|
@@ -3232,7 +3294,23 @@ class BlockGapWidget extends WidgetType {
|
|
|
3232
3294
|
}
|
|
3233
3295
|
function findCompositionNode(view, headPos) {
|
|
3234
3296
|
let sel = view.observer.selectionRange;
|
|
3235
|
-
|
|
3297
|
+
if (!sel.focusNode)
|
|
3298
|
+
return null;
|
|
3299
|
+
let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset);
|
|
3300
|
+
let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset);
|
|
3301
|
+
let textNode = textBefore || textAfter;
|
|
3302
|
+
if (textAfter && textBefore && textAfter.node != textBefore.node) {
|
|
3303
|
+
let descAfter = ContentView.get(textAfter.node);
|
|
3304
|
+
if (!descAfter || descAfter instanceof TextView && descAfter.text != textAfter.node.nodeValue) {
|
|
3305
|
+
textNode = textAfter;
|
|
3306
|
+
}
|
|
3307
|
+
else if (view.docView.lastCompositionAfterCursor) {
|
|
3308
|
+
let descBefore = ContentView.get(textBefore.node);
|
|
3309
|
+
if (!(!descBefore || descBefore instanceof TextView && descBefore.text != textBefore.node.nodeValue))
|
|
3310
|
+
textNode = textAfter;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
view.docView.lastCompositionAfterCursor = textNode != textBefore;
|
|
3236
3314
|
if (!textNode)
|
|
3237
3315
|
return null;
|
|
3238
3316
|
let from = headPos - textNode.offset;
|
|
@@ -3267,33 +3345,6 @@ function findCompositionRange(view, changes, headPos) {
|
|
|
3267
3345
|
return null;
|
|
3268
3346
|
}
|
|
3269
3347
|
}
|
|
3270
|
-
function nearbyTextNode(startNode, startOffset, side) {
|
|
3271
|
-
if (side <= 0)
|
|
3272
|
-
for (let node = startNode, offset = startOffset;;) {
|
|
3273
|
-
if (node.nodeType == 3)
|
|
3274
|
-
return { node: node, offset: offset };
|
|
3275
|
-
if (node.nodeType == 1 && offset > 0) {
|
|
3276
|
-
node = node.childNodes[offset - 1];
|
|
3277
|
-
offset = maxOffset(node);
|
|
3278
|
-
}
|
|
3279
|
-
else {
|
|
3280
|
-
break;
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
if (side >= 0)
|
|
3284
|
-
for (let node = startNode, offset = startOffset;;) {
|
|
3285
|
-
if (node.nodeType == 3)
|
|
3286
|
-
return { node: node, offset: offset };
|
|
3287
|
-
if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
|
|
3288
|
-
node = node.childNodes[offset];
|
|
3289
|
-
offset = 0;
|
|
3290
|
-
}
|
|
3291
|
-
else {
|
|
3292
|
-
break;
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
return null;
|
|
3296
|
-
}
|
|
3297
3348
|
function nextToUneditable(node, offset) {
|
|
3298
3349
|
if (node.nodeType != 1)
|
|
3299
3350
|
return 0;
|
|
@@ -3829,12 +3880,15 @@ class InputState {
|
|
|
3829
3880
|
this.view.observer.forceFlush();
|
|
3830
3881
|
return false;
|
|
3831
3882
|
}
|
|
3832
|
-
flushIOSKey() {
|
|
3883
|
+
flushIOSKey(change) {
|
|
3833
3884
|
let key = this.pendingIOSKey;
|
|
3834
3885
|
if (!key)
|
|
3835
3886
|
return false;
|
|
3887
|
+
// This looks like an autocorrection before Enter
|
|
3888
|
+
if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
|
|
3889
|
+
return false;
|
|
3836
3890
|
this.pendingIOSKey = undefined;
|
|
3837
|
-
return dispatchKey(this.view.contentDOM, key.key, key.keyCode);
|
|
3891
|
+
return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
|
|
3838
3892
|
}
|
|
3839
3893
|
ignoreDuringComposition(event) {
|
|
3840
3894
|
if (!/^key/.test(event.type))
|
|
@@ -4343,7 +4397,7 @@ handlers.paste = (view, event) => {
|
|
|
4343
4397
|
view.observer.flush();
|
|
4344
4398
|
let data = brokenClipboardAPI ? null : event.clipboardData;
|
|
4345
4399
|
if (data) {
|
|
4346
|
-
doPaste(view, data.getData("text/plain") || data.getData("text/uri-
|
|
4400
|
+
doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
|
|
4347
4401
|
return true;
|
|
4348
4402
|
}
|
|
4349
4403
|
else {
|
|
@@ -4513,6 +4567,16 @@ handlers.beforeinput = (view, event) => {
|
|
|
4513
4567
|
}, 100);
|
|
4514
4568
|
}
|
|
4515
4569
|
}
|
|
4570
|
+
if (browser.ios && event.inputType == "deleteContentForward") {
|
|
4571
|
+
// For some reason, DOM changes (and beforeinput) happen _before_
|
|
4572
|
+
// the key event for ctrl-d on iOS when using an external
|
|
4573
|
+
// keyboard.
|
|
4574
|
+
view.observer.flushSoon();
|
|
4575
|
+
}
|
|
4576
|
+
// Safari will occasionally forget to fire compositionend at the end of a dead-key composition
|
|
4577
|
+
if (browser.safari && event.inputType == "insertText" && view.inputState.composing >= 0) {
|
|
4578
|
+
setTimeout(() => observers.compositionend(view, event), 20);
|
|
4579
|
+
}
|
|
4516
4580
|
return false;
|
|
4517
4581
|
};
|
|
4518
4582
|
const appliedFirefoxHack = new Set;
|
|
@@ -4875,7 +4939,8 @@ class HeightMapGap extends HeightMap {
|
|
|
4875
4939
|
blockAt(height, oracle, top, offset) {
|
|
4876
4940
|
let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
|
|
4877
4941
|
if (oracle.lineWrapping) {
|
|
4878
|
-
let guess = offset +
|
|
4942
|
+
let guess = offset + (height < oracle.lineHeight ? 0
|
|
4943
|
+
: Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
|
|
4879
4944
|
let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
|
|
4880
4945
|
let lineTop = Math.max(top, height - lineHeight / 2);
|
|
4881
4946
|
return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
|
|
@@ -5447,7 +5512,8 @@ class ViewState {
|
|
|
5447
5512
|
let result = 0, bias = 0;
|
|
5448
5513
|
if (domRect.width && domRect.height) {
|
|
5449
5514
|
let { scaleX, scaleY } = getScale(dom, domRect);
|
|
5450
|
-
if (
|
|
5515
|
+
if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
|
|
5516
|
+
scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
|
|
5451
5517
|
this.scaleX = scaleX;
|
|
5452
5518
|
this.scaleY = scaleY;
|
|
5453
5519
|
result |= 8 /* UpdateFlag.Geometry */;
|
|
@@ -6210,9 +6276,6 @@ function isAtEnd(parent, node, offset) {
|
|
|
6210
6276
|
node = node.parentNode;
|
|
6211
6277
|
}
|
|
6212
6278
|
}
|
|
6213
|
-
function isBlockElement(node) {
|
|
6214
|
-
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
|
|
6215
|
-
}
|
|
6216
6279
|
class DOMPoint {
|
|
6217
6280
|
constructor(node, offset) {
|
|
6218
6281
|
this.node = node;
|
|
@@ -6328,7 +6391,7 @@ function applyDOMChange(view, domChange) {
|
|
|
6328
6391
|
change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
|
|
6329
6392
|
}
|
|
6330
6393
|
if (change) {
|
|
6331
|
-
if (browser.ios && view.inputState.flushIOSKey())
|
|
6394
|
+
if (browser.ios && view.inputState.flushIOSKey(change))
|
|
6332
6395
|
return true;
|
|
6333
6396
|
// Android browsers don't fire reasonable key events for enter,
|
|
6334
6397
|
// backspace, or delete. So this detects changes that look like
|
|
@@ -6519,6 +6582,7 @@ class DOMObserver {
|
|
|
6519
6582
|
this.intersecting = false;
|
|
6520
6583
|
this.gapIntersection = null;
|
|
6521
6584
|
this.gaps = [];
|
|
6585
|
+
this.printQuery = null;
|
|
6522
6586
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
6523
6587
|
this.parentCheck = -1;
|
|
6524
6588
|
this.dom = view.contentDOM;
|
|
@@ -6552,6 +6616,8 @@ class DOMObserver {
|
|
|
6552
6616
|
this.onResize = this.onResize.bind(this);
|
|
6553
6617
|
this.onPrint = this.onPrint.bind(this);
|
|
6554
6618
|
this.onScroll = this.onScroll.bind(this);
|
|
6619
|
+
if (window.matchMedia)
|
|
6620
|
+
this.printQuery = window.matchMedia("print");
|
|
6555
6621
|
if (typeof ResizeObserver == "function") {
|
|
6556
6622
|
this.resizeScroll = new ResizeObserver(() => {
|
|
6557
6623
|
var _a;
|
|
@@ -6598,7 +6664,9 @@ class DOMObserver {
|
|
|
6598
6664
|
this.view.requestMeasure();
|
|
6599
6665
|
}, 50);
|
|
6600
6666
|
}
|
|
6601
|
-
onPrint() {
|
|
6667
|
+
onPrint(event) {
|
|
6668
|
+
if (event.type == "change" && !event.matches)
|
|
6669
|
+
return;
|
|
6602
6670
|
this.view.viewState.printing = true;
|
|
6603
6671
|
this.view.measure();
|
|
6604
6672
|
setTimeout(() => {
|
|
@@ -6876,14 +6944,20 @@ class DOMObserver {
|
|
|
6876
6944
|
}
|
|
6877
6945
|
addWindowListeners(win) {
|
|
6878
6946
|
win.addEventListener("resize", this.onResize);
|
|
6879
|
-
|
|
6947
|
+
if (this.printQuery)
|
|
6948
|
+
this.printQuery.addEventListener("change", this.onPrint);
|
|
6949
|
+
else
|
|
6950
|
+
win.addEventListener("beforeprint", this.onPrint);
|
|
6880
6951
|
win.addEventListener("scroll", this.onScroll);
|
|
6881
6952
|
win.document.addEventListener("selectionchange", this.onSelectionChange);
|
|
6882
6953
|
}
|
|
6883
6954
|
removeWindowListeners(win) {
|
|
6884
6955
|
win.removeEventListener("scroll", this.onScroll);
|
|
6885
6956
|
win.removeEventListener("resize", this.onResize);
|
|
6886
|
-
|
|
6957
|
+
if (this.printQuery)
|
|
6958
|
+
this.printQuery.removeEventListener("change", this.onPrint);
|
|
6959
|
+
else
|
|
6960
|
+
win.removeEventListener("beforeprint", this.onPrint);
|
|
6887
6961
|
win.document.removeEventListener("selectionchange", this.onSelectionChange);
|
|
6888
6962
|
}
|
|
6889
6963
|
destroy() {
|
|
@@ -7890,6 +7964,13 @@ dispatching the custom behavior as a separate transaction.
|
|
|
7890
7964
|
*/
|
|
7891
7965
|
EditorView.inputHandler = inputHandler;
|
|
7892
7966
|
/**
|
|
7967
|
+
Scroll handlers can override how things are scrolled into view.
|
|
7968
|
+
If they return `true`, no further handling happens for the
|
|
7969
|
+
scrolling. If they return false, the default scroll behavior is
|
|
7970
|
+
applied. Scroll handlers should never initiate editor updates.
|
|
7971
|
+
*/
|
|
7972
|
+
EditorView.scrollHandler = scrollHandler;
|
|
7973
|
+
/**
|
|
7893
7974
|
This facet can be used to provide functions that create effects
|
|
7894
7975
|
to be dispatched when the editor's focus state changes.
|
|
7895
7976
|
*/
|
package/dist/index.d.cts
CHANGED
|
@@ -1134,6 +1134,23 @@ declare class EditorView {
|
|
|
1134
1134
|
*/
|
|
1135
1135
|
static inputHandler: Facet<(view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean, readonly ((view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean)[]>;
|
|
1136
1136
|
/**
|
|
1137
|
+
Scroll handlers can override how things are scrolled into view.
|
|
1138
|
+
If they return `true`, no further handling happens for the
|
|
1139
|
+
scrolling. If they return false, the default scroll behavior is
|
|
1140
|
+
applied. Scroll handlers should never initiate editor updates.
|
|
1141
|
+
*/
|
|
1142
|
+
static scrollHandler: Facet<(view: EditorView, range: SelectionRange, options: {
|
|
1143
|
+
x: ScrollStrategy;
|
|
1144
|
+
y: ScrollStrategy;
|
|
1145
|
+
xMargin: number;
|
|
1146
|
+
yMargin: number;
|
|
1147
|
+
}) => boolean, readonly ((view: EditorView, range: SelectionRange, options: {
|
|
1148
|
+
x: ScrollStrategy;
|
|
1149
|
+
y: ScrollStrategy;
|
|
1150
|
+
xMargin: number;
|
|
1151
|
+
yMargin: number;
|
|
1152
|
+
}) => boolean)[]>;
|
|
1153
|
+
/**
|
|
1137
1154
|
This facet can be used to provide functions that create effects
|
|
1138
1155
|
to be dispatched when the editor's focus state changes.
|
|
1139
1156
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1134,6 +1134,23 @@ declare class EditorView {
|
|
|
1134
1134
|
*/
|
|
1135
1135
|
static inputHandler: Facet<(view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean, readonly ((view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean)[]>;
|
|
1136
1136
|
/**
|
|
1137
|
+
Scroll handlers can override how things are scrolled into view.
|
|
1138
|
+
If they return `true`, no further handling happens for the
|
|
1139
|
+
scrolling. If they return false, the default scroll behavior is
|
|
1140
|
+
applied. Scroll handlers should never initiate editor updates.
|
|
1141
|
+
*/
|
|
1142
|
+
static scrollHandler: Facet<(view: EditorView, range: SelectionRange, options: {
|
|
1143
|
+
x: ScrollStrategy;
|
|
1144
|
+
y: ScrollStrategy;
|
|
1145
|
+
xMargin: number;
|
|
1146
|
+
yMargin: number;
|
|
1147
|
+
}) => boolean, readonly ((view: EditorView, range: SelectionRange, options: {
|
|
1148
|
+
x: ScrollStrategy;
|
|
1149
|
+
y: ScrollStrategy;
|
|
1150
|
+
xMargin: number;
|
|
1151
|
+
yMargin: number;
|
|
1152
|
+
}) => boolean)[]>;
|
|
1153
|
+
/**
|
|
1137
1154
|
This facet can be used to provide functions that create effects
|
|
1138
1155
|
to be dispatched when the editor's focus state changes.
|
|
1139
1156
|
*/
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,9 @@ function domIndex(node) {
|
|
|
59
59
|
return index;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
function isBlockElement(node) {
|
|
63
|
+
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
|
|
64
|
+
}
|
|
62
65
|
function scanFor(node, off, targetNode, targetOff, dir) {
|
|
63
66
|
for (;;) {
|
|
64
67
|
if (node == targetNode && off == targetOff)
|
|
@@ -91,6 +94,12 @@ function flattenRect(rect, left) {
|
|
|
91
94
|
return { left: x, right: x, top: rect.top, bottom: rect.bottom };
|
|
92
95
|
}
|
|
93
96
|
function windowRect(win) {
|
|
97
|
+
let vp = win.visualViewport;
|
|
98
|
+
if (vp)
|
|
99
|
+
return {
|
|
100
|
+
left: 0, right: vp.width,
|
|
101
|
+
top: 0, bottom: vp.height
|
|
102
|
+
};
|
|
94
103
|
return { left: 0, right: win.innerWidth,
|
|
95
104
|
top: 0, bottom: win.innerHeight };
|
|
96
105
|
}
|
|
@@ -280,8 +289,10 @@ function textRange(node, from, to = from) {
|
|
|
280
289
|
range.setStart(node, from);
|
|
281
290
|
return range;
|
|
282
291
|
}
|
|
283
|
-
function dispatchKey(elt, name, code) {
|
|
292
|
+
function dispatchKey(elt, name, code, mods) {
|
|
284
293
|
let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
|
|
294
|
+
if (mods)
|
|
295
|
+
({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
|
|
285
296
|
let down = new KeyboardEvent("keydown", options);
|
|
286
297
|
down.synthetic = true;
|
|
287
298
|
elt.dispatchEvent(down);
|
|
@@ -332,6 +343,46 @@ function atElementStart(doc, selection) {
|
|
|
332
343
|
function isScrolledToBottom(elt) {
|
|
333
344
|
return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
|
|
334
345
|
}
|
|
346
|
+
function textNodeBefore(startNode, startOffset) {
|
|
347
|
+
for (let node = startNode, offset = startOffset;;) {
|
|
348
|
+
if (node.nodeType == 3 && offset > 0) {
|
|
349
|
+
return { node: node, offset: offset };
|
|
350
|
+
}
|
|
351
|
+
else if (node.nodeType == 1 && offset > 0) {
|
|
352
|
+
if (node.contentEditable == "false")
|
|
353
|
+
return null;
|
|
354
|
+
node = node.childNodes[offset - 1];
|
|
355
|
+
offset = maxOffset(node);
|
|
356
|
+
}
|
|
357
|
+
else if (node.parentNode && !isBlockElement(node)) {
|
|
358
|
+
offset = domIndex(node);
|
|
359
|
+
node = node.parentNode;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function textNodeAfter(startNode, startOffset) {
|
|
367
|
+
for (let node = startNode, offset = startOffset;;) {
|
|
368
|
+
if (node.nodeType == 3 && offset < node.nodeValue.length) {
|
|
369
|
+
return { node: node, offset: offset };
|
|
370
|
+
}
|
|
371
|
+
else if (node.nodeType == 1 && offset < node.childNodes.length) {
|
|
372
|
+
if (node.contentEditable == "false")
|
|
373
|
+
return null;
|
|
374
|
+
node = node.childNodes[offset];
|
|
375
|
+
offset = 0;
|
|
376
|
+
}
|
|
377
|
+
else if (node.parentNode && !isBlockElement(node)) {
|
|
378
|
+
offset = domIndex(node) + 1;
|
|
379
|
+
node = node.parentNode;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
335
386
|
|
|
336
387
|
class DOMPos {
|
|
337
388
|
constructor(node, offset, precise = true) {
|
|
@@ -2286,6 +2337,7 @@ const perLineTextDirection = /*@__PURE__*/Facet.define({
|
|
|
2286
2337
|
const nativeSelectionHidden = /*@__PURE__*/Facet.define({
|
|
2287
2338
|
combine: values => values.some(x => x)
|
|
2288
2339
|
});
|
|
2340
|
+
const scrollHandler = /*@__PURE__*/Facet.define();
|
|
2289
2341
|
class ScrollTarget {
|
|
2290
2342
|
constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
|
|
2291
2343
|
// This data structure is abused to also store precise scroll
|
|
@@ -2653,6 +2705,7 @@ class DocView extends ContentView {
|
|
|
2653
2705
|
this.hasComposition = null;
|
|
2654
2706
|
this.markedForComposition = new Set;
|
|
2655
2707
|
this.compositionBarrier = Decoration.none;
|
|
2708
|
+
this.lastCompositionAfterCursor = false;
|
|
2656
2709
|
// Track a minimum width for the editor. When measuring sizes in
|
|
2657
2710
|
// measureVisibleLineHeights, this is updated to point at the width
|
|
2658
2711
|
// of a given element and its extent in the document. When a change
|
|
@@ -2869,7 +2922,7 @@ class DocView extends ContentView {
|
|
|
2869
2922
|
if (browser.gecko) {
|
|
2870
2923
|
let nextTo = nextToUneditable(anchor.node, anchor.offset);
|
|
2871
2924
|
if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
|
|
2872
|
-
let text =
|
|
2925
|
+
let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
|
|
2873
2926
|
if (text)
|
|
2874
2927
|
anchor = new DOMPos(text.node, text.offset);
|
|
2875
2928
|
}
|
|
@@ -3184,6 +3237,15 @@ class DocView extends ContentView {
|
|
|
3184
3237
|
this.view.scrollDOM.scrollLeft = target.xMargin;
|
|
3185
3238
|
return;
|
|
3186
3239
|
}
|
|
3240
|
+
for (let handler of this.view.state.facet(scrollHandler)) {
|
|
3241
|
+
try {
|
|
3242
|
+
if (handler(this.view, target.range, target))
|
|
3243
|
+
return true;
|
|
3244
|
+
}
|
|
3245
|
+
catch (e) {
|
|
3246
|
+
logException(this.view.state, e, "scroll handler");
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3187
3249
|
let { range } = target;
|
|
3188
3250
|
let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
|
|
3189
3251
|
if (!rect)
|
|
@@ -3228,7 +3290,23 @@ class BlockGapWidget extends WidgetType {
|
|
|
3228
3290
|
}
|
|
3229
3291
|
function findCompositionNode(view, headPos) {
|
|
3230
3292
|
let sel = view.observer.selectionRange;
|
|
3231
|
-
|
|
3293
|
+
if (!sel.focusNode)
|
|
3294
|
+
return null;
|
|
3295
|
+
let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset);
|
|
3296
|
+
let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset);
|
|
3297
|
+
let textNode = textBefore || textAfter;
|
|
3298
|
+
if (textAfter && textBefore && textAfter.node != textBefore.node) {
|
|
3299
|
+
let descAfter = ContentView.get(textAfter.node);
|
|
3300
|
+
if (!descAfter || descAfter instanceof TextView && descAfter.text != textAfter.node.nodeValue) {
|
|
3301
|
+
textNode = textAfter;
|
|
3302
|
+
}
|
|
3303
|
+
else if (view.docView.lastCompositionAfterCursor) {
|
|
3304
|
+
let descBefore = ContentView.get(textBefore.node);
|
|
3305
|
+
if (!(!descBefore || descBefore instanceof TextView && descBefore.text != textBefore.node.nodeValue))
|
|
3306
|
+
textNode = textAfter;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
view.docView.lastCompositionAfterCursor = textNode != textBefore;
|
|
3232
3310
|
if (!textNode)
|
|
3233
3311
|
return null;
|
|
3234
3312
|
let from = headPos - textNode.offset;
|
|
@@ -3263,33 +3341,6 @@ function findCompositionRange(view, changes, headPos) {
|
|
|
3263
3341
|
return null;
|
|
3264
3342
|
}
|
|
3265
3343
|
}
|
|
3266
|
-
function nearbyTextNode(startNode, startOffset, side) {
|
|
3267
|
-
if (side <= 0)
|
|
3268
|
-
for (let node = startNode, offset = startOffset;;) {
|
|
3269
|
-
if (node.nodeType == 3)
|
|
3270
|
-
return { node: node, offset: offset };
|
|
3271
|
-
if (node.nodeType == 1 && offset > 0) {
|
|
3272
|
-
node = node.childNodes[offset - 1];
|
|
3273
|
-
offset = maxOffset(node);
|
|
3274
|
-
}
|
|
3275
|
-
else {
|
|
3276
|
-
break;
|
|
3277
|
-
}
|
|
3278
|
-
}
|
|
3279
|
-
if (side >= 0)
|
|
3280
|
-
for (let node = startNode, offset = startOffset;;) {
|
|
3281
|
-
if (node.nodeType == 3)
|
|
3282
|
-
return { node: node, offset: offset };
|
|
3283
|
-
if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
|
|
3284
|
-
node = node.childNodes[offset];
|
|
3285
|
-
offset = 0;
|
|
3286
|
-
}
|
|
3287
|
-
else {
|
|
3288
|
-
break;
|
|
3289
|
-
}
|
|
3290
|
-
}
|
|
3291
|
-
return null;
|
|
3292
|
-
}
|
|
3293
3344
|
function nextToUneditable(node, offset) {
|
|
3294
3345
|
if (node.nodeType != 1)
|
|
3295
3346
|
return 0;
|
|
@@ -3825,12 +3876,15 @@ class InputState {
|
|
|
3825
3876
|
this.view.observer.forceFlush();
|
|
3826
3877
|
return false;
|
|
3827
3878
|
}
|
|
3828
|
-
flushIOSKey() {
|
|
3879
|
+
flushIOSKey(change) {
|
|
3829
3880
|
let key = this.pendingIOSKey;
|
|
3830
3881
|
if (!key)
|
|
3831
3882
|
return false;
|
|
3883
|
+
// This looks like an autocorrection before Enter
|
|
3884
|
+
if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
|
|
3885
|
+
return false;
|
|
3832
3886
|
this.pendingIOSKey = undefined;
|
|
3833
|
-
return dispatchKey(this.view.contentDOM, key.key, key.keyCode);
|
|
3887
|
+
return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
|
|
3834
3888
|
}
|
|
3835
3889
|
ignoreDuringComposition(event) {
|
|
3836
3890
|
if (!/^key/.test(event.type))
|
|
@@ -4339,7 +4393,7 @@ handlers.paste = (view, event) => {
|
|
|
4339
4393
|
view.observer.flush();
|
|
4340
4394
|
let data = brokenClipboardAPI ? null : event.clipboardData;
|
|
4341
4395
|
if (data) {
|
|
4342
|
-
doPaste(view, data.getData("text/plain") || data.getData("text/uri-
|
|
4396
|
+
doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
|
|
4343
4397
|
return true;
|
|
4344
4398
|
}
|
|
4345
4399
|
else {
|
|
@@ -4509,6 +4563,16 @@ handlers.beforeinput = (view, event) => {
|
|
|
4509
4563
|
}, 100);
|
|
4510
4564
|
}
|
|
4511
4565
|
}
|
|
4566
|
+
if (browser.ios && event.inputType == "deleteContentForward") {
|
|
4567
|
+
// For some reason, DOM changes (and beforeinput) happen _before_
|
|
4568
|
+
// the key event for ctrl-d on iOS when using an external
|
|
4569
|
+
// keyboard.
|
|
4570
|
+
view.observer.flushSoon();
|
|
4571
|
+
}
|
|
4572
|
+
// Safari will occasionally forget to fire compositionend at the end of a dead-key composition
|
|
4573
|
+
if (browser.safari && event.inputType == "insertText" && view.inputState.composing >= 0) {
|
|
4574
|
+
setTimeout(() => observers.compositionend(view, event), 20);
|
|
4575
|
+
}
|
|
4512
4576
|
return false;
|
|
4513
4577
|
};
|
|
4514
4578
|
const appliedFirefoxHack = /*@__PURE__*/new Set;
|
|
@@ -4870,7 +4934,8 @@ class HeightMapGap extends HeightMap {
|
|
|
4870
4934
|
blockAt(height, oracle, top, offset) {
|
|
4871
4935
|
let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
|
|
4872
4936
|
if (oracle.lineWrapping) {
|
|
4873
|
-
let guess = offset +
|
|
4937
|
+
let guess = offset + (height < oracle.lineHeight ? 0
|
|
4938
|
+
: Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
|
|
4874
4939
|
let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
|
|
4875
4940
|
let lineTop = Math.max(top, height - lineHeight / 2);
|
|
4876
4941
|
return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
|
|
@@ -5442,7 +5507,8 @@ class ViewState {
|
|
|
5442
5507
|
let result = 0, bias = 0;
|
|
5443
5508
|
if (domRect.width && domRect.height) {
|
|
5444
5509
|
let { scaleX, scaleY } = getScale(dom, domRect);
|
|
5445
|
-
if (
|
|
5510
|
+
if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
|
|
5511
|
+
scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
|
|
5446
5512
|
this.scaleX = scaleX;
|
|
5447
5513
|
this.scaleY = scaleY;
|
|
5448
5514
|
result |= 8 /* UpdateFlag.Geometry */;
|
|
@@ -6205,9 +6271,6 @@ function isAtEnd(parent, node, offset) {
|
|
|
6205
6271
|
node = node.parentNode;
|
|
6206
6272
|
}
|
|
6207
6273
|
}
|
|
6208
|
-
function isBlockElement(node) {
|
|
6209
|
-
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
|
|
6210
|
-
}
|
|
6211
6274
|
class DOMPoint {
|
|
6212
6275
|
constructor(node, offset) {
|
|
6213
6276
|
this.node = node;
|
|
@@ -6323,7 +6386,7 @@ function applyDOMChange(view, domChange) {
|
|
|
6323
6386
|
change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
|
|
6324
6387
|
}
|
|
6325
6388
|
if (change) {
|
|
6326
|
-
if (browser.ios && view.inputState.flushIOSKey())
|
|
6389
|
+
if (browser.ios && view.inputState.flushIOSKey(change))
|
|
6327
6390
|
return true;
|
|
6328
6391
|
// Android browsers don't fire reasonable key events for enter,
|
|
6329
6392
|
// backspace, or delete. So this detects changes that look like
|
|
@@ -6514,6 +6577,7 @@ class DOMObserver {
|
|
|
6514
6577
|
this.intersecting = false;
|
|
6515
6578
|
this.gapIntersection = null;
|
|
6516
6579
|
this.gaps = [];
|
|
6580
|
+
this.printQuery = null;
|
|
6517
6581
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
6518
6582
|
this.parentCheck = -1;
|
|
6519
6583
|
this.dom = view.contentDOM;
|
|
@@ -6547,6 +6611,8 @@ class DOMObserver {
|
|
|
6547
6611
|
this.onResize = this.onResize.bind(this);
|
|
6548
6612
|
this.onPrint = this.onPrint.bind(this);
|
|
6549
6613
|
this.onScroll = this.onScroll.bind(this);
|
|
6614
|
+
if (window.matchMedia)
|
|
6615
|
+
this.printQuery = window.matchMedia("print");
|
|
6550
6616
|
if (typeof ResizeObserver == "function") {
|
|
6551
6617
|
this.resizeScroll = new ResizeObserver(() => {
|
|
6552
6618
|
var _a;
|
|
@@ -6593,7 +6659,9 @@ class DOMObserver {
|
|
|
6593
6659
|
this.view.requestMeasure();
|
|
6594
6660
|
}, 50);
|
|
6595
6661
|
}
|
|
6596
|
-
onPrint() {
|
|
6662
|
+
onPrint(event) {
|
|
6663
|
+
if (event.type == "change" && !event.matches)
|
|
6664
|
+
return;
|
|
6597
6665
|
this.view.viewState.printing = true;
|
|
6598
6666
|
this.view.measure();
|
|
6599
6667
|
setTimeout(() => {
|
|
@@ -6871,14 +6939,20 @@ class DOMObserver {
|
|
|
6871
6939
|
}
|
|
6872
6940
|
addWindowListeners(win) {
|
|
6873
6941
|
win.addEventListener("resize", this.onResize);
|
|
6874
|
-
|
|
6942
|
+
if (this.printQuery)
|
|
6943
|
+
this.printQuery.addEventListener("change", this.onPrint);
|
|
6944
|
+
else
|
|
6945
|
+
win.addEventListener("beforeprint", this.onPrint);
|
|
6875
6946
|
win.addEventListener("scroll", this.onScroll);
|
|
6876
6947
|
win.document.addEventListener("selectionchange", this.onSelectionChange);
|
|
6877
6948
|
}
|
|
6878
6949
|
removeWindowListeners(win) {
|
|
6879
6950
|
win.removeEventListener("scroll", this.onScroll);
|
|
6880
6951
|
win.removeEventListener("resize", this.onResize);
|
|
6881
|
-
|
|
6952
|
+
if (this.printQuery)
|
|
6953
|
+
this.printQuery.removeEventListener("change", this.onPrint);
|
|
6954
|
+
else
|
|
6955
|
+
win.removeEventListener("beforeprint", this.onPrint);
|
|
6882
6956
|
win.document.removeEventListener("selectionchange", this.onSelectionChange);
|
|
6883
6957
|
}
|
|
6884
6958
|
destroy() {
|
|
@@ -7885,6 +7959,13 @@ dispatching the custom behavior as a separate transaction.
|
|
|
7885
7959
|
*/
|
|
7886
7960
|
EditorView.inputHandler = inputHandler;
|
|
7887
7961
|
/**
|
|
7962
|
+
Scroll handlers can override how things are scrolled into view.
|
|
7963
|
+
If they return `true`, no further handling happens for the
|
|
7964
|
+
scrolling. If they return false, the default scroll behavior is
|
|
7965
|
+
applied. Scroll handlers should never initiate editor updates.
|
|
7966
|
+
*/
|
|
7967
|
+
EditorView.scrollHandler = scrollHandler;
|
|
7968
|
+
/**
|
|
7888
7969
|
This facet can be used to provide functions that create effects
|
|
7889
7970
|
to be dispatched when the editor's focus state changes.
|
|
7890
7971
|
*/
|