@codemirror/view 0.19.47 → 0.19.48

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,15 @@
1
+ ## 0.19.48 (2022-03-30)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where DOM syncing could crash when a DOM node was moved from a parent to a child node (via widgets reusing existing nodes).
6
+
7
+ To avoid interfering with things like a vim mode too much, the editor will now only activate the tab-to-move-focus escape hatch after an escape press that wasn't handled by an event handler.
8
+
9
+ Make sure the view measures itself before the page is printed.
10
+
11
+ Tweak types of view plugin defining functions to avoid TypeScript errors when the plugin value doesn't have any of the interface's properties.
12
+
1
13
  ## 0.19.47 (2022-03-08)
2
14
 
3
15
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -318,32 +318,34 @@ class ContentView {
318
318
  sync(track) {
319
319
  if (this.dirty & 2 /* Node */) {
320
320
  let parent = this.dom;
321
- let pos = parent.firstChild;
321
+ let prev = null, next;
322
322
  for (let child of this.children) {
323
323
  if (child.dirty) {
324
- if (!child.dom && pos) {
325
- let contentView = ContentView.get(pos);
324
+ if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
325
+ let contentView = ContentView.get(next);
326
326
  if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
327
- child.reuseDOM(pos);
327
+ child.reuseDOM(next);
328
328
  }
329
329
  child.sync(track);
330
330
  child.dirty = 0 /* Not */;
331
331
  }
332
- if (track && !track.written && track.node == parent && pos != child.dom)
332
+ next = prev ? prev.nextSibling : parent.firstChild;
333
+ if (track && !track.written && track.node == parent && next != child.dom)
333
334
  track.written = true;
334
335
  if (child.dom.parentNode == parent) {
335
- while (pos && pos != child.dom)
336
- pos = rm(pos);
337
- pos = child.dom.nextSibling;
336
+ while (next && next != child.dom)
337
+ next = rm(next);
338
338
  }
339
339
  else {
340
- parent.insertBefore(child.dom, pos);
340
+ parent.insertBefore(child.dom, next);
341
341
  }
342
+ prev = child.dom;
342
343
  }
343
- if (pos && track && track.node == parent)
344
+ next = prev ? prev.nextSibling : parent.firstChild;
345
+ if (next && track && track.node == parent)
344
346
  track.written = true;
345
- while (pos)
346
- pos = rm(pos);
347
+ while (next)
348
+ next = rm(next);
347
349
  }
348
350
  else if (this.dirty & 1 /* Child */) {
349
351
  for (let child of this.children)
@@ -3373,10 +3375,10 @@ class InputState {
3373
3375
  for (let type in handlers) {
3374
3376
  let handler = handlers[type];
3375
3377
  view.contentDOM.addEventListener(type, (event) => {
3376
- if (type == "keydown" && this.keydown(view, event))
3377
- return;
3378
3378
  if (!eventBelongsToEditor(view, event) || this.ignoreDuringComposition(event))
3379
3379
  return;
3380
+ if (type == "keydown" && this.keydown(view, event))
3381
+ return;
3380
3382
  if (this.mustFlushObserver(event))
3381
3383
  view.observer.forceFlush();
3382
3384
  if (this.runCustomHandlers(type, view, event))
@@ -3444,7 +3446,7 @@ class InputState {
3444
3446
  // Must always run, even if a custom handler handled the event
3445
3447
  this.lastKeyCode = event.keyCode;
3446
3448
  this.lastKeyTime = Date.now();
3447
- if (this.screenKeyEvent(view, event))
3449
+ if (event.keyCode == 9 && Date.now() < this.lastEscPress + 2000)
3448
3450
  return true;
3449
3451
  // Chrome for Android usually doesn't fire proper key events, but
3450
3452
  // occasionally does, usually surrounded by a bunch of complicated
@@ -3494,14 +3496,6 @@ class InputState {
3494
3496
  }
3495
3497
  return false;
3496
3498
  }
3497
- screenKeyEvent(view, event) {
3498
- let protectedTab = event.keyCode == 9 && Date.now() < this.lastEscPress + 2000;
3499
- if (event.keyCode == 27)
3500
- this.lastEscPress = Date.now();
3501
- else if (modifierCodes.indexOf(event.keyCode) < 0)
3502
- this.lastEscPress = 0;
3503
- return protectedTab;
3504
- }
3505
3499
  mustFlushObserver(event) {
3506
3500
  return (event.type == "keydown" && event.keyCode != 229) ||
3507
3501
  event.type == "compositionend" && !browser.ios;
@@ -3675,6 +3669,10 @@ function doPaste(view, input) {
3675
3669
  }
3676
3670
  handlers.keydown = (view, event) => {
3677
3671
  view.inputState.setSelectionOrigin("select");
3672
+ if (event.keyCode == 27)
3673
+ view.inputState.lastEscPress = Date.now();
3674
+ else if (modifierCodes.indexOf(event.keyCode) < 0)
3675
+ view.inputState.lastEscPress = 0;
3678
3676
  };
3679
3677
  let lastTouch = 0;
3680
3678
  handlers.touchstart = (view, e) => {
@@ -3932,14 +3930,6 @@ handlers.focus = handlers.blur = view => {
3932
3930
  view.update([]);
3933
3931
  }, 10);
3934
3932
  };
3935
- handlers.beforeprint = view => {
3936
- view.viewState.printing = true;
3937
- view.requestMeasure();
3938
- setTimeout(() => {
3939
- view.viewState.printing = false;
3940
- view.requestMeasure();
3941
- }, 2000);
3942
- };
3943
3933
  function forceClearComposition(view, rapid) {
3944
3934
  if (view.docView.compositionDeco.size) {
3945
3935
  view.inputState.rapidCompositionStart = rapid;
@@ -4695,6 +4685,11 @@ function visiblePixelRange(dom, paddingTop) {
4695
4685
  return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4696
4686
  top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4697
4687
  }
4688
+ function fullPixelRange(dom, paddingTop) {
4689
+ let rect = dom.getBoundingClientRect();
4690
+ return { left: 0, right: rect.right - rect.left,
4691
+ top: paddingTop, bottom: rect.bottom - (rect.top + paddingTop) };
4692
+ }
4698
4693
  // Line gaps are placeholder widgets used to hide pieces of overlong
4699
4694
  // lines within the viewport, as a kludge to keep the editor
4700
4695
  // responsive when a ridiculously long line is loaded into it.
@@ -4851,8 +4846,7 @@ class ViewState {
4851
4846
  }
4852
4847
  }
4853
4848
  // Pixel viewport
4854
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4855
- : visiblePixelRange(dom, this.paddingTop);
4849
+ let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
4856
4850
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4857
4851
  this.pixelViewport = pixelViewport;
4858
4852
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -5429,6 +5423,7 @@ class DOMObserver {
5429
5423
  });
5430
5424
  this.resize.observe(view.scrollDOM);
5431
5425
  }
5426
+ window.addEventListener("beforeprint", this.onPrint = this.onPrint.bind(this));
5432
5427
  this.start();
5433
5428
  window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5434
5429
  if (typeof IntersectionObserver == "function") {
@@ -5463,6 +5458,14 @@ class DOMObserver {
5463
5458
  this.view.requestMeasure();
5464
5459
  }, 50);
5465
5460
  }
5461
+ onPrint() {
5462
+ this.view.viewState.printing = true;
5463
+ this.view.measure();
5464
+ setTimeout(() => {
5465
+ this.view.viewState.printing = false;
5466
+ this.view.requestMeasure();
5467
+ }, 500);
5468
+ }
5466
5469
  updateGaps(gaps) {
5467
5470
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5468
5471
  this.gapIntersection.disconnect();
@@ -5680,6 +5683,7 @@ class DOMObserver {
5680
5683
  dom.removeEventListener("scroll", this.onScroll);
5681
5684
  window.removeEventListener("scroll", this.onScroll);
5682
5685
  window.removeEventListener("resize", this.onResize);
5686
+ window.removeEventListener("beforeprint", this.onPrint);
5683
5687
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5684
5688
  clearTimeout(this.parentCheck);
5685
5689
  clearTimeout(this.resizeTimeout);
package/dist/index.d.ts CHANGED
@@ -413,12 +413,12 @@ declare class ViewPlugin<V extends PluginValue> {
413
413
  Define a plugin from a constructor function that creates the
414
414
  plugin's value, given an editor view.
415
415
  */
416
- static define<V extends PluginValue>(create: (view: EditorView) => V, spec?: PluginSpec<V>): ViewPlugin<V>;
416
+ static define<V extends PluginValue & object>(create: (view: EditorView) => V, spec?: PluginSpec<V>): ViewPlugin<V>;
417
417
  /**
418
418
  Create a plugin for a class whose constructor takes a single
419
419
  editor view as argument.
420
420
  */
421
- static fromClass<V extends PluginValue>(cls: {
421
+ static fromClass<V extends PluginValue & object>(cls: {
422
422
  new (view: EditorView): V;
423
423
  }, spec?: PluginSpec<V>): ViewPlugin<V>;
424
424
  }
package/dist/index.js CHANGED
@@ -315,32 +315,34 @@ class ContentView {
315
315
  sync(track) {
316
316
  if (this.dirty & 2 /* Node */) {
317
317
  let parent = this.dom;
318
- let pos = parent.firstChild;
318
+ let prev = null, next;
319
319
  for (let child of this.children) {
320
320
  if (child.dirty) {
321
- if (!child.dom && pos) {
322
- let contentView = ContentView.get(pos);
321
+ if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
322
+ let contentView = ContentView.get(next);
323
323
  if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
324
- child.reuseDOM(pos);
324
+ child.reuseDOM(next);
325
325
  }
326
326
  child.sync(track);
327
327
  child.dirty = 0 /* Not */;
328
328
  }
329
- if (track && !track.written && track.node == parent && pos != child.dom)
329
+ next = prev ? prev.nextSibling : parent.firstChild;
330
+ if (track && !track.written && track.node == parent && next != child.dom)
330
331
  track.written = true;
331
332
  if (child.dom.parentNode == parent) {
332
- while (pos && pos != child.dom)
333
- pos = rm(pos);
334
- pos = child.dom.nextSibling;
333
+ while (next && next != child.dom)
334
+ next = rm(next);
335
335
  }
336
336
  else {
337
- parent.insertBefore(child.dom, pos);
337
+ parent.insertBefore(child.dom, next);
338
338
  }
339
+ prev = child.dom;
339
340
  }
340
- if (pos && track && track.node == parent)
341
+ next = prev ? prev.nextSibling : parent.firstChild;
342
+ if (next && track && track.node == parent)
341
343
  track.written = true;
342
- while (pos)
343
- pos = rm(pos);
344
+ while (next)
345
+ next = rm(next);
344
346
  }
345
347
  else if (this.dirty & 1 /* Child */) {
346
348
  for (let child of this.children)
@@ -3368,10 +3370,10 @@ class InputState {
3368
3370
  for (let type in handlers) {
3369
3371
  let handler = handlers[type];
3370
3372
  view.contentDOM.addEventListener(type, (event) => {
3371
- if (type == "keydown" && this.keydown(view, event))
3372
- return;
3373
3373
  if (!eventBelongsToEditor(view, event) || this.ignoreDuringComposition(event))
3374
3374
  return;
3375
+ if (type == "keydown" && this.keydown(view, event))
3376
+ return;
3375
3377
  if (this.mustFlushObserver(event))
3376
3378
  view.observer.forceFlush();
3377
3379
  if (this.runCustomHandlers(type, view, event))
@@ -3439,7 +3441,7 @@ class InputState {
3439
3441
  // Must always run, even if a custom handler handled the event
3440
3442
  this.lastKeyCode = event.keyCode;
3441
3443
  this.lastKeyTime = Date.now();
3442
- if (this.screenKeyEvent(view, event))
3444
+ if (event.keyCode == 9 && Date.now() < this.lastEscPress + 2000)
3443
3445
  return true;
3444
3446
  // Chrome for Android usually doesn't fire proper key events, but
3445
3447
  // occasionally does, usually surrounded by a bunch of complicated
@@ -3489,14 +3491,6 @@ class InputState {
3489
3491
  }
3490
3492
  return false;
3491
3493
  }
3492
- screenKeyEvent(view, event) {
3493
- let protectedTab = event.keyCode == 9 && Date.now() < this.lastEscPress + 2000;
3494
- if (event.keyCode == 27)
3495
- this.lastEscPress = Date.now();
3496
- else if (modifierCodes.indexOf(event.keyCode) < 0)
3497
- this.lastEscPress = 0;
3498
- return protectedTab;
3499
- }
3500
3494
  mustFlushObserver(event) {
3501
3495
  return (event.type == "keydown" && event.keyCode != 229) ||
3502
3496
  event.type == "compositionend" && !browser.ios;
@@ -3670,6 +3664,10 @@ function doPaste(view, input) {
3670
3664
  }
3671
3665
  handlers.keydown = (view, event) => {
3672
3666
  view.inputState.setSelectionOrigin("select");
3667
+ if (event.keyCode == 27)
3668
+ view.inputState.lastEscPress = Date.now();
3669
+ else if (modifierCodes.indexOf(event.keyCode) < 0)
3670
+ view.inputState.lastEscPress = 0;
3673
3671
  };
3674
3672
  let lastTouch = 0;
3675
3673
  handlers.touchstart = (view, e) => {
@@ -3927,14 +3925,6 @@ handlers.focus = handlers.blur = view => {
3927
3925
  view.update([]);
3928
3926
  }, 10);
3929
3927
  };
3930
- handlers.beforeprint = view => {
3931
- view.viewState.printing = true;
3932
- view.requestMeasure();
3933
- setTimeout(() => {
3934
- view.viewState.printing = false;
3935
- view.requestMeasure();
3936
- }, 2000);
3937
- };
3938
3928
  function forceClearComposition(view, rapid) {
3939
3929
  if (view.docView.compositionDeco.size) {
3940
3930
  view.inputState.rapidCompositionStart = rapid;
@@ -4689,6 +4679,11 @@ function visiblePixelRange(dom, paddingTop) {
4689
4679
  return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4690
4680
  top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4691
4681
  }
4682
+ function fullPixelRange(dom, paddingTop) {
4683
+ let rect = dom.getBoundingClientRect();
4684
+ return { left: 0, right: rect.right - rect.left,
4685
+ top: paddingTop, bottom: rect.bottom - (rect.top + paddingTop) };
4686
+ }
4692
4687
  // Line gaps are placeholder widgets used to hide pieces of overlong
4693
4688
  // lines within the viewport, as a kludge to keep the editor
4694
4689
  // responsive when a ridiculously long line is loaded into it.
@@ -4845,8 +4840,7 @@ class ViewState {
4845
4840
  }
4846
4841
  }
4847
4842
  // Pixel viewport
4848
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4849
- : visiblePixelRange(dom, this.paddingTop);
4843
+ let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
4850
4844
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4851
4845
  this.pixelViewport = pixelViewport;
4852
4846
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -5423,6 +5417,7 @@ class DOMObserver {
5423
5417
  });
5424
5418
  this.resize.observe(view.scrollDOM);
5425
5419
  }
5420
+ window.addEventListener("beforeprint", this.onPrint = this.onPrint.bind(this));
5426
5421
  this.start();
5427
5422
  window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5428
5423
  if (typeof IntersectionObserver == "function") {
@@ -5457,6 +5452,14 @@ class DOMObserver {
5457
5452
  this.view.requestMeasure();
5458
5453
  }, 50);
5459
5454
  }
5455
+ onPrint() {
5456
+ this.view.viewState.printing = true;
5457
+ this.view.measure();
5458
+ setTimeout(() => {
5459
+ this.view.viewState.printing = false;
5460
+ this.view.requestMeasure();
5461
+ }, 500);
5462
+ }
5460
5463
  updateGaps(gaps) {
5461
5464
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5462
5465
  this.gapIntersection.disconnect();
@@ -5674,6 +5677,7 @@ class DOMObserver {
5674
5677
  dom.removeEventListener("scroll", this.onScroll);
5675
5678
  window.removeEventListener("scroll", this.onScroll);
5676
5679
  window.removeEventListener("resize", this.onResize);
5680
+ window.removeEventListener("beforeprint", this.onPrint);
5677
5681
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5678
5682
  clearTimeout(this.parentCheck);
5679
5683
  clearTimeout(this.resizeTimeout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.47",
3
+ "version": "0.19.48",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",