@codemirror/view 6.26.0 → 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 CHANGED
@@ -1,3 +1,13 @@
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
+
1
11
  ## 6.26.0 (2024-03-14)
2
12
 
3
13
  ### 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)
@@ -342,6 +345,46 @@ function atElementStart(doc, selection) {
342
345
  function isScrolledToBottom(elt) {
343
346
  return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
344
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
+ }
345
388
 
346
389
  class DOMPos {
347
390
  constructor(node, offset, precise = true) {
@@ -2666,6 +2709,7 @@ class DocView extends ContentView {
2666
2709
  this.hasComposition = null;
2667
2710
  this.markedForComposition = new Set;
2668
2711
  this.compositionBarrier = Decoration.none;
2712
+ this.lastCompositionAfterCursor = false;
2669
2713
  // Track a minimum width for the editor. When measuring sizes in
2670
2714
  // measureVisibleLineHeights, this is updated to point at the width
2671
2715
  // of a given element and its extent in the document. When a change
@@ -2882,7 +2926,7 @@ class DocView extends ContentView {
2882
2926
  if (browser.gecko) {
2883
2927
  let nextTo = nextToUneditable(anchor.node, anchor.offset);
2884
2928
  if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
2885
- let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* NextTo.Before */ ? 1 : -1);
2929
+ let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
2886
2930
  if (text)
2887
2931
  anchor = new DOMPos(text.node, text.offset);
2888
2932
  }
@@ -3250,7 +3294,23 @@ class BlockGapWidget extends WidgetType {
3250
3294
  }
3251
3295
  function findCompositionNode(view, headPos) {
3252
3296
  let sel = view.observer.selectionRange;
3253
- let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
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;
3254
3314
  if (!textNode)
3255
3315
  return null;
3256
3316
  let from = headPos - textNode.offset;
@@ -3285,33 +3345,6 @@ function findCompositionRange(view, changes, headPos) {
3285
3345
  return null;
3286
3346
  }
3287
3347
  }
3288
- function nearbyTextNode(startNode, startOffset, side) {
3289
- if (side <= 0)
3290
- for (let node = startNode, offset = startOffset;;) {
3291
- if (node.nodeType == 3)
3292
- return { node: node, offset: offset };
3293
- if (node.nodeType == 1 && offset > 0) {
3294
- node = node.childNodes[offset - 1];
3295
- offset = maxOffset(node);
3296
- }
3297
- else {
3298
- break;
3299
- }
3300
- }
3301
- if (side >= 0)
3302
- for (let node = startNode, offset = startOffset;;) {
3303
- if (node.nodeType == 3)
3304
- return { node: node, offset: offset };
3305
- if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
3306
- node = node.childNodes[offset];
3307
- offset = 0;
3308
- }
3309
- else {
3310
- break;
3311
- }
3312
- }
3313
- return null;
3314
- }
3315
3348
  function nextToUneditable(node, offset) {
3316
3349
  if (node.nodeType != 1)
3317
3350
  return 0;
@@ -4540,6 +4573,10 @@ handlers.beforeinput = (view, event) => {
4540
4573
  // keyboard.
4541
4574
  view.observer.flushSoon();
4542
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
+ }
4543
4580
  return false;
4544
4581
  };
4545
4582
  const appliedFirefoxHack = new Set;
@@ -4902,7 +4939,8 @@ class HeightMapGap extends HeightMap {
4902
4939
  blockAt(height, oracle, top, offset) {
4903
4940
  let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
4904
4941
  if (oracle.lineWrapping) {
4905
- let guess = offset + Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length);
4942
+ let guess = offset + (height < oracle.lineHeight ? 0
4943
+ : Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
4906
4944
  let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
4907
4945
  let lineTop = Math.max(top, height - lineHeight / 2);
4908
4946
  return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
@@ -6238,9 +6276,6 @@ function isAtEnd(parent, node, offset) {
6238
6276
  node = node.parentNode;
6239
6277
  }
6240
6278
  }
6241
- function isBlockElement(node) {
6242
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
6243
- }
6244
6279
  class DOMPoint {
6245
6280
  constructor(node, offset) {
6246
6281
  this.node = node;
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)
@@ -340,6 +343,46 @@ function atElementStart(doc, selection) {
340
343
  function isScrolledToBottom(elt) {
341
344
  return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
342
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
+ }
343
386
 
344
387
  class DOMPos {
345
388
  constructor(node, offset, precise = true) {
@@ -2662,6 +2705,7 @@ class DocView extends ContentView {
2662
2705
  this.hasComposition = null;
2663
2706
  this.markedForComposition = new Set;
2664
2707
  this.compositionBarrier = Decoration.none;
2708
+ this.lastCompositionAfterCursor = false;
2665
2709
  // Track a minimum width for the editor. When measuring sizes in
2666
2710
  // measureVisibleLineHeights, this is updated to point at the width
2667
2711
  // of a given element and its extent in the document. When a change
@@ -2878,7 +2922,7 @@ class DocView extends ContentView {
2878
2922
  if (browser.gecko) {
2879
2923
  let nextTo = nextToUneditable(anchor.node, anchor.offset);
2880
2924
  if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
2881
- let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* NextTo.Before */ ? 1 : -1);
2925
+ let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
2882
2926
  if (text)
2883
2927
  anchor = new DOMPos(text.node, text.offset);
2884
2928
  }
@@ -3246,7 +3290,23 @@ class BlockGapWidget extends WidgetType {
3246
3290
  }
3247
3291
  function findCompositionNode(view, headPos) {
3248
3292
  let sel = view.observer.selectionRange;
3249
- let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
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;
3250
3310
  if (!textNode)
3251
3311
  return null;
3252
3312
  let from = headPos - textNode.offset;
@@ -3281,33 +3341,6 @@ function findCompositionRange(view, changes, headPos) {
3281
3341
  return null;
3282
3342
  }
3283
3343
  }
3284
- function nearbyTextNode(startNode, startOffset, side) {
3285
- if (side <= 0)
3286
- for (let node = startNode, offset = startOffset;;) {
3287
- if (node.nodeType == 3)
3288
- return { node: node, offset: offset };
3289
- if (node.nodeType == 1 && offset > 0) {
3290
- node = node.childNodes[offset - 1];
3291
- offset = maxOffset(node);
3292
- }
3293
- else {
3294
- break;
3295
- }
3296
- }
3297
- if (side >= 0)
3298
- for (let node = startNode, offset = startOffset;;) {
3299
- if (node.nodeType == 3)
3300
- return { node: node, offset: offset };
3301
- if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
3302
- node = node.childNodes[offset];
3303
- offset = 0;
3304
- }
3305
- else {
3306
- break;
3307
- }
3308
- }
3309
- return null;
3310
- }
3311
3344
  function nextToUneditable(node, offset) {
3312
3345
  if (node.nodeType != 1)
3313
3346
  return 0;
@@ -4536,6 +4569,10 @@ handlers.beforeinput = (view, event) => {
4536
4569
  // keyboard.
4537
4570
  view.observer.flushSoon();
4538
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
+ }
4539
4576
  return false;
4540
4577
  };
4541
4578
  const appliedFirefoxHack = /*@__PURE__*/new Set;
@@ -4897,7 +4934,8 @@ class HeightMapGap extends HeightMap {
4897
4934
  blockAt(height, oracle, top, offset) {
4898
4935
  let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
4899
4936
  if (oracle.lineWrapping) {
4900
- let guess = offset + Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length);
4937
+ let guess = offset + (height < oracle.lineHeight ? 0
4938
+ : Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
4901
4939
  let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
4902
4940
  let lineTop = Math.max(top, height - lineHeight / 2);
4903
4941
  return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
@@ -6233,9 +6271,6 @@ function isAtEnd(parent, node, offset) {
6233
6271
  node = node.parentNode;
6234
6272
  }
6235
6273
  }
6236
- function isBlockElement(node) {
6237
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
6238
- }
6239
6274
  class DOMPoint {
6240
6275
  constructor(node, offset) {
6241
6276
  this.node = node;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.26.0",
3
+ "version": "6.26.1",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",