@codemirror/view 6.0.1 → 6.1.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/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## 6.1.0 (2022-07-19)
2
+
3
+ ### New features
4
+
5
+ `MatchDecorator` now supports a `decorate` option that can be used to customize the way decorations are added for each match.
6
+
7
+ ## 6.0.3 (2022-07-08)
8
+
9
+ ### Bug fixes
10
+
11
+ Fix a problem where `posAtCoords` could incorrectly return the start of the next line when querying positions between lines.
12
+
13
+ Fix an issue where registering a high-precedence keymap made keymap handling take precedence over other keydown event handlers.
14
+
15
+ Ctrl/Cmd-clicking can now remove ranges from a multi-range selection.
16
+
17
+ ## 6.0.2 (2022-06-23)
18
+
19
+ ### Bug fixes
20
+
21
+ Fix a CSS issue that broke horizontal scroll width stabilization.
22
+
23
+ Fix a bug where `defaultLineHeight` could get an incorrect value in very narrow editors.
24
+
1
25
  ## 6.0.1 (2022-06-17)
2
26
 
3
27
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -2726,6 +2726,7 @@ class DocView extends ContentView {
2726
2726
  // If no workable line exists, force a layout of a measurable element
2727
2727
  let dummy = document.createElement("div"), lineHeight, charWidth;
2728
2728
  dummy.className = "cm-line";
2729
+ dummy.style.width = "99999px";
2729
2730
  dummy.textContent = "abc def ghi jkl mno pqr stu";
2730
2731
  this.view.observer.ignore(() => {
2731
2732
  this.dom.appendChild(dummy);
@@ -3136,7 +3137,8 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
3136
3137
  let range = doc.caretRangeFromPoint(x, y);
3137
3138
  if (range) {
3138
3139
  ({ startContainer: node, startOffset: offset } = range);
3139
- if (browser.safari && isSuspiciousCaretResult(node, offset, x))
3140
+ if (browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
3141
+ browser.chrome && isSuspiciousChromeCaretResult(node, offset, x))
3140
3142
  node = undefined;
3141
3143
  }
3142
3144
  }
@@ -3163,7 +3165,7 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
3163
3165
  // the space between lines as belonging to the last character of the
3164
3166
  // line before. This is used to detect such a result so that it can be
3165
3167
  // ignored (issue #401).
3166
- function isSuspiciousCaretResult(node, offset, x) {
3168
+ function isSuspiciousSafariCaretResult(node, offset, x) {
3167
3169
  let len;
3168
3170
  if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
3169
3171
  return false;
@@ -3172,6 +3174,22 @@ function isSuspiciousCaretResult(node, offset, x) {
3172
3174
  return false;
3173
3175
  return textRange(node, len - 1, len).getBoundingClientRect().left > x;
3174
3176
  }
3177
+ // Chrome will move positions between lines to the start of the next line
3178
+ function isSuspiciousChromeCaretResult(node, offset, x) {
3179
+ if (offset != 0)
3180
+ return false;
3181
+ for (let cur = node;;) {
3182
+ let parent = cur.parentNode;
3183
+ if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
3184
+ return false;
3185
+ if (parent.classList.contains("cm-line"))
3186
+ break;
3187
+ cur = parent;
3188
+ }
3189
+ let rect = node.nodeType == 1 ? node.getBoundingClientRect()
3190
+ : textRange(node, 0, Math.max(node.nodeValue.length, 1)).getBoundingClientRect();
3191
+ return x - rect.left > 5;
3192
+ }
3175
3193
  function moveToLineBoundary(view, start, forward, includeWrap) {
3176
3194
  let line = view.state.doc.lineAt(start.head);
3177
3195
  let coords = !includeWrap || !view.lineWrapping ? null
@@ -3730,6 +3748,8 @@ function basicMouseSelection(view, event) {
3730
3748
  }
3731
3749
  if (extend)
3732
3750
  return startSel.replaceRange(startSel.main.extend(range.from, range.to));
3751
+ else if (multiple && startSel.ranges.length > 1 && startSel.ranges.some(r => r.eq(range)))
3752
+ return removeRange(startSel, range);
3733
3753
  else if (multiple)
3734
3754
  return startSel.addRange(range);
3735
3755
  else
@@ -3737,6 +3757,12 @@ function basicMouseSelection(view, event) {
3737
3757
  }
3738
3758
  };
3739
3759
  }
3760
+ function removeRange(sel, range) {
3761
+ for (let i = 0;; i++) {
3762
+ if (sel.ranges[i].eq(range))
3763
+ return state.EditorSelection.create(sel.ranges.slice(0, i).concat(sel.ranges.slice(i + 1)), sel.mainIndex == i ? 0 : sel.mainIndex - (sel.mainIndex > i ? 1 : 0));
3764
+ }
3765
+ }
3740
3766
  handlers.dragstart = (view, event) => {
3741
3767
  let { selection: { main } } = view.state;
3742
3768
  let { mouseSelection } = view.inputState;
@@ -5166,6 +5192,7 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5166
5192
  ".cm-content": {
5167
5193
  margin: 0,
5168
5194
  flexGrow: 2,
5195
+ flexShrink: 0,
5169
5196
  minHeight: "100%",
5170
5197
  display: "block",
5171
5198
  whiteSpace: "pre",
@@ -5181,7 +5208,8 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5181
5208
  whiteSpace_fallback: "pre-wrap",
5182
5209
  whiteSpace: "break-spaces",
5183
5210
  wordBreak: "break-word",
5184
- overflowWrap: "anywhere"
5211
+ overflowWrap: "anywhere",
5212
+ flexShrink: 1
5185
5213
  },
5186
5214
  "&light .cm-content": { caretColor: "black" },
5187
5215
  "&dark .cm-content": { caretColor: "white" },
@@ -5219,8 +5247,8 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5219
5247
  // Two animations defined so that we can switch between them to
5220
5248
  // restart the animation without forcing another style
5221
5249
  // recomputation.
5222
- "@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5223
- "@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5250
+ "@keyframes cm-blink": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
5251
+ "@keyframes cm-blink2": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
5224
5252
  ".cm-cursor, .cm-dropCursor": {
5225
5253
  position: "absolute",
5226
5254
  borderLeft: "1.2px solid black",
@@ -6887,11 +6915,11 @@ function modifiers(name, event, shift) {
6887
6915
  name = "Shift-" + name;
6888
6916
  return name;
6889
6917
  }
6890
- const handleKeyEvents = EditorView.domEventHandlers({
6918
+ const handleKeyEvents = state.Prec.default(EditorView.domEventHandlers({
6891
6919
  keydown(event, view) {
6892
6920
  return runHandlers(getKeymap(view.state), event, view, "editor");
6893
6921
  }
6894
- });
6922
+ }));
6895
6923
  /**
6896
6924
  Facet used for registering keymaps.
6897
6925
 
@@ -7353,7 +7381,7 @@ function iterMatches(doc, re, from, to, f) {
7353
7381
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
7354
7382
  if (!cursor.lineBreak)
7355
7383
  while (m = re.exec(cursor.value))
7356
- f(pos + m.index, pos + m.index + m[0].length, m);
7384
+ f(pos + m.index, m);
7357
7385
  }
7358
7386
  }
7359
7387
  function matchRanges(view, maxLength) {
@@ -7383,11 +7411,20 @@ class MatchDecorator {
7383
7411
  Create a decorator.
7384
7412
  */
7385
7413
  constructor(config) {
7386
- let { regexp, decoration, boundary, maxLength = 1000 } = config;
7414
+ const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
7387
7415
  if (!regexp.global)
7388
7416
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7389
7417
  this.regexp = regexp;
7390
- this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7418
+ if (decorate) {
7419
+ this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
7420
+ }
7421
+ else if (decoration) {
7422
+ let getDeco = typeof decoration == "function" ? decoration : () => decoration;
7423
+ this.addMatch = (match, view, from, add) => add(from, from + match[0].length, getDeco(match, view, from));
7424
+ }
7425
+ else {
7426
+ throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
7427
+ }
7391
7428
  this.boundary = boundary;
7392
7429
  this.maxLength = maxLength;
7393
7430
  }
@@ -7397,9 +7434,9 @@ class MatchDecorator {
7397
7434
  plugin.
7398
7435
  */
7399
7436
  createDeco(view) {
7400
- let build = new state.RangeSetBuilder();
7437
+ let build = new state.RangeSetBuilder(), add = build.add.bind(build);
7401
7438
  for (let { from, to } of matchRanges(view, this.maxLength))
7402
- iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7439
+ iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
7403
7440
  return build.finish();
7404
7441
  }
7405
7442
  /**
@@ -7441,15 +7478,14 @@ class MatchDecorator {
7441
7478
  }
7442
7479
  }
7443
7480
  let ranges = [], m;
7481
+ let add = (from, to, deco) => ranges.push(deco.range(from, to));
7444
7482
  if (fromLine == toLine) {
7445
7483
  this.regexp.lastIndex = start - fromLine.from;
7446
- while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from) {
7447
- let pos = m.index + fromLine.from;
7448
- ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
7449
- }
7484
+ while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
7485
+ this.addMatch(m, view, m.index + fromLine.from, add);
7450
7486
  }
7451
7487
  else {
7452
- iterMatches(view.state.doc, this.regexp, start, end, (from, to, m) => ranges.push(this.getDeco(m, view, from).range(from, to)));
7488
+ iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
7453
7489
  }
7454
7490
  deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
7455
7491
  }
package/dist/index.d.ts CHANGED
@@ -1357,7 +1357,7 @@ represent a matching configuration.
1357
1357
  */
1358
1358
  declare class MatchDecorator {
1359
1359
  private regexp;
1360
- private getDeco;
1360
+ private addMatch;
1361
1361
  private boundary;
1362
1362
  private maxLength;
1363
1363
  /**
@@ -1374,7 +1374,18 @@ declare class MatchDecorator {
1374
1374
  The decoration to apply to matches, either directly or as a
1375
1375
  function of the match.
1376
1376
  */
1377
- decoration: Decoration | ((match: RegExpExecArray, view: EditorView, pos: number) => Decoration);
1377
+ decoration?: Decoration | ((match: RegExpExecArray, view: EditorView, pos: number) => Decoration);
1378
+ /**
1379
+ Customize the way decorations are added for matches. This
1380
+ function, when given, will be called for matches and should
1381
+ call `add` to create decorations for them. Note that the
1382
+ decorations should appear *in* the given range, and the
1383
+ function should have no side effects beyond calling `add`.
1384
+
1385
+ The `decoration` option is ignored when `decorate` is
1386
+ provided.
1387
+ */
1388
+ decorate?: (add: (from: number, to: number, decoration: Decoration) => void, from: number, to: number, match: RegExpExecArray, view: EditorView) => void;
1378
1389
  /**
1379
1390
  By default, changed lines are re-matched entirely. You can
1380
1391
  provide a boundary expression, which should match single
package/dist/index.js CHANGED
@@ -2720,6 +2720,7 @@ class DocView extends ContentView {
2720
2720
  // If no workable line exists, force a layout of a measurable element
2721
2721
  let dummy = document.createElement("div"), lineHeight, charWidth;
2722
2722
  dummy.className = "cm-line";
2723
+ dummy.style.width = "99999px";
2723
2724
  dummy.textContent = "abc def ghi jkl mno pqr stu";
2724
2725
  this.view.observer.ignore(() => {
2725
2726
  this.dom.appendChild(dummy);
@@ -3130,7 +3131,8 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
3130
3131
  let range = doc.caretRangeFromPoint(x, y);
3131
3132
  if (range) {
3132
3133
  ({ startContainer: node, startOffset: offset } = range);
3133
- if (browser.safari && isSuspiciousCaretResult(node, offset, x))
3134
+ if (browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
3135
+ browser.chrome && isSuspiciousChromeCaretResult(node, offset, x))
3134
3136
  node = undefined;
3135
3137
  }
3136
3138
  }
@@ -3157,7 +3159,7 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
3157
3159
  // the space between lines as belonging to the last character of the
3158
3160
  // line before. This is used to detect such a result so that it can be
3159
3161
  // ignored (issue #401).
3160
- function isSuspiciousCaretResult(node, offset, x) {
3162
+ function isSuspiciousSafariCaretResult(node, offset, x) {
3161
3163
  let len;
3162
3164
  if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
3163
3165
  return false;
@@ -3166,6 +3168,22 @@ function isSuspiciousCaretResult(node, offset, x) {
3166
3168
  return false;
3167
3169
  return textRange(node, len - 1, len).getBoundingClientRect().left > x;
3168
3170
  }
3171
+ // Chrome will move positions between lines to the start of the next line
3172
+ function isSuspiciousChromeCaretResult(node, offset, x) {
3173
+ if (offset != 0)
3174
+ return false;
3175
+ for (let cur = node;;) {
3176
+ let parent = cur.parentNode;
3177
+ if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
3178
+ return false;
3179
+ if (parent.classList.contains("cm-line"))
3180
+ break;
3181
+ cur = parent;
3182
+ }
3183
+ let rect = node.nodeType == 1 ? node.getBoundingClientRect()
3184
+ : textRange(node, 0, Math.max(node.nodeValue.length, 1)).getBoundingClientRect();
3185
+ return x - rect.left > 5;
3186
+ }
3169
3187
  function moveToLineBoundary(view, start, forward, includeWrap) {
3170
3188
  let line = view.state.doc.lineAt(start.head);
3171
3189
  let coords = !includeWrap || !view.lineWrapping ? null
@@ -3724,6 +3742,8 @@ function basicMouseSelection(view, event) {
3724
3742
  }
3725
3743
  if (extend)
3726
3744
  return startSel.replaceRange(startSel.main.extend(range.from, range.to));
3745
+ else if (multiple && startSel.ranges.length > 1 && startSel.ranges.some(r => r.eq(range)))
3746
+ return removeRange(startSel, range);
3727
3747
  else if (multiple)
3728
3748
  return startSel.addRange(range);
3729
3749
  else
@@ -3731,6 +3751,12 @@ function basicMouseSelection(view, event) {
3731
3751
  }
3732
3752
  };
3733
3753
  }
3754
+ function removeRange(sel, range) {
3755
+ for (let i = 0;; i++) {
3756
+ if (sel.ranges[i].eq(range))
3757
+ return EditorSelection.create(sel.ranges.slice(0, i).concat(sel.ranges.slice(i + 1)), sel.mainIndex == i ? 0 : sel.mainIndex - (sel.mainIndex > i ? 1 : 0));
3758
+ }
3759
+ }
3734
3760
  handlers.dragstart = (view, event) => {
3735
3761
  let { selection: { main } } = view.state;
3736
3762
  let { mouseSelection } = view.inputState;
@@ -5159,6 +5185,7 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5159
5185
  ".cm-content": {
5160
5186
  margin: 0,
5161
5187
  flexGrow: 2,
5188
+ flexShrink: 0,
5162
5189
  minHeight: "100%",
5163
5190
  display: "block",
5164
5191
  whiteSpace: "pre",
@@ -5174,7 +5201,8 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5174
5201
  whiteSpace_fallback: "pre-wrap",
5175
5202
  whiteSpace: "break-spaces",
5176
5203
  wordBreak: "break-word",
5177
- overflowWrap: "anywhere"
5204
+ overflowWrap: "anywhere",
5205
+ flexShrink: 1
5178
5206
  },
5179
5207
  "&light .cm-content": { caretColor: "black" },
5180
5208
  "&dark .cm-content": { caretColor: "white" },
@@ -5212,8 +5240,8 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5212
5240
  // Two animations defined so that we can switch between them to
5213
5241
  // restart the animation without forcing another style
5214
5242
  // recomputation.
5215
- "@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5216
- "@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5243
+ "@keyframes cm-blink": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
5244
+ "@keyframes cm-blink2": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
5217
5245
  ".cm-cursor, .cm-dropCursor": {
5218
5246
  position: "absolute",
5219
5247
  borderLeft: "1.2px solid black",
@@ -6880,11 +6908,11 @@ function modifiers(name, event, shift) {
6880
6908
  name = "Shift-" + name;
6881
6909
  return name;
6882
6910
  }
6883
- const handleKeyEvents = /*@__PURE__*/EditorView.domEventHandlers({
6911
+ const handleKeyEvents = /*@__PURE__*/Prec.default(/*@__PURE__*/EditorView.domEventHandlers({
6884
6912
  keydown(event, view) {
6885
6913
  return runHandlers(getKeymap(view.state), event, view, "editor");
6886
6914
  }
6887
- });
6915
+ }));
6888
6916
  /**
6889
6917
  Facet used for registering keymaps.
6890
6918
 
@@ -7346,7 +7374,7 @@ function iterMatches(doc, re, from, to, f) {
7346
7374
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
7347
7375
  if (!cursor.lineBreak)
7348
7376
  while (m = re.exec(cursor.value))
7349
- f(pos + m.index, pos + m.index + m[0].length, m);
7377
+ f(pos + m.index, m);
7350
7378
  }
7351
7379
  }
7352
7380
  function matchRanges(view, maxLength) {
@@ -7376,11 +7404,20 @@ class MatchDecorator {
7376
7404
  Create a decorator.
7377
7405
  */
7378
7406
  constructor(config) {
7379
- let { regexp, decoration, boundary, maxLength = 1000 } = config;
7407
+ const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
7380
7408
  if (!regexp.global)
7381
7409
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7382
7410
  this.regexp = regexp;
7383
- this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7411
+ if (decorate) {
7412
+ this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
7413
+ }
7414
+ else if (decoration) {
7415
+ let getDeco = typeof decoration == "function" ? decoration : () => decoration;
7416
+ this.addMatch = (match, view, from, add) => add(from, from + match[0].length, getDeco(match, view, from));
7417
+ }
7418
+ else {
7419
+ throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
7420
+ }
7384
7421
  this.boundary = boundary;
7385
7422
  this.maxLength = maxLength;
7386
7423
  }
@@ -7390,9 +7427,9 @@ class MatchDecorator {
7390
7427
  plugin.
7391
7428
  */
7392
7429
  createDeco(view) {
7393
- let build = new RangeSetBuilder();
7430
+ let build = new RangeSetBuilder(), add = build.add.bind(build);
7394
7431
  for (let { from, to } of matchRanges(view, this.maxLength))
7395
- iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7432
+ iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
7396
7433
  return build.finish();
7397
7434
  }
7398
7435
  /**
@@ -7434,15 +7471,14 @@ class MatchDecorator {
7434
7471
  }
7435
7472
  }
7436
7473
  let ranges = [], m;
7474
+ let add = (from, to, deco) => ranges.push(deco.range(from, to));
7437
7475
  if (fromLine == toLine) {
7438
7476
  this.regexp.lastIndex = start - fromLine.from;
7439
- while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from) {
7440
- let pos = m.index + fromLine.from;
7441
- ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
7442
- }
7477
+ while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
7478
+ this.addMatch(m, view, m.index + fromLine.from, add);
7443
7479
  }
7444
7480
  else {
7445
- iterMatches(view.state.doc, this.regexp, start, end, (from, to, m) => ranges.push(this.getDeco(m, view, from).range(from, to)));
7481
+ iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
7446
7482
  }
7447
7483
  deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
7448
7484
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",