@codemirror/view 0.19.27 → 0.19.28

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/dist/index.js CHANGED
@@ -842,6 +842,9 @@ class CompositionView extends WidgetView {
842
842
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
843
843
  get isEditable() { return true; }
844
844
  }
845
+ // Use two characters on Android, to prevent Chrome from closing the
846
+ // virtual keyboard when backspacing after a widget (#602).
847
+ const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
845
848
  // These are drawn around uneditable widgets to avoid a number of
846
849
  // browser bugs that show up when the cursor is directly next to
847
850
  // uneditable inline content.
@@ -858,12 +861,13 @@ class WidgetBufferView extends ContentView {
858
861
  split() { return new WidgetBufferView(this.side); }
859
862
  sync() {
860
863
  if (!this.dom)
861
- this.setDOM(document.createTextNode("\u200b"));
862
- else if (this.dirty && this.dom.nodeValue != "\u200b")
863
- this.dom.nodeValue = "\u200b";
864
+ this.setDOM(document.createTextNode(ZeroWidthSpace));
865
+ else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
866
+ this.dom.nodeValue = ZeroWidthSpace;
864
867
  }
865
868
  getSide() { return this.side; }
866
869
  domAtPos(pos) { return DOMPos.before(this.dom); }
870
+ localPosFromDOM() { return 0; }
867
871
  domBoundsAround() { return null; }
868
872
  coordsAt(pos) {
869
873
  let rects = clientRectsFor(this.dom);
@@ -1934,488 +1938,6 @@ class ViewUpdate {
1934
1938
  get empty() { return this.flags == 0 && this.transactions.length == 0; }
1935
1939
  }
1936
1940
 
1937
- class DocView extends ContentView {
1938
- constructor(view) {
1939
- super();
1940
- this.view = view;
1941
- this.compositionDeco = Decoration.none;
1942
- this.decorations = [];
1943
- // Track a minimum width for the editor. When measuring sizes in
1944
- // measureVisibleLineHeights, this is updated to point at the width
1945
- // of a given element and its extent in the document. When a change
1946
- // happens in that range, these are reset. That way, once we've seen
1947
- // a line/element of a given length, we keep the editor wide enough
1948
- // to fit at least that element, until it is changed, at which point
1949
- // we forget it again.
1950
- this.minWidth = 0;
1951
- this.minWidthFrom = 0;
1952
- this.minWidthTo = 0;
1953
- // Track whether the DOM selection was set in a lossy way, so that
1954
- // we don't mess it up when reading it back it
1955
- this.impreciseAnchor = null;
1956
- this.impreciseHead = null;
1957
- this.forceSelection = false;
1958
- // Used by the resize observer to ignore resizes that we caused
1959
- // ourselves
1960
- this.lastUpdate = Date.now();
1961
- this.setDOM(view.contentDOM);
1962
- this.children = [new LineView];
1963
- this.children[0].setParent(this);
1964
- this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
1965
- }
1966
- get root() { return this.view.root; }
1967
- get editorView() { return this.view; }
1968
- get length() { return this.view.state.doc.length; }
1969
- // Update the document view to a given state. scrollIntoView can be
1970
- // used as a hint to compute a new viewport that includes that
1971
- // position, if we know the editor is going to scroll that position
1972
- // into view.
1973
- update(update) {
1974
- let changedRanges = update.changedRanges;
1975
- if (this.minWidth > 0 && changedRanges.length) {
1976
- if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
1977
- this.minWidth = 0;
1978
- }
1979
- else {
1980
- this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
1981
- this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
1982
- }
1983
- }
1984
- if (this.view.inputState.composing < 0)
1985
- this.compositionDeco = Decoration.none;
1986
- else if (update.transactions.length)
1987
- this.compositionDeco = computeCompositionDeco(this.view, update.changes);
1988
- // When the DOM nodes around the selection are moved to another
1989
- // parent, Chrome sometimes reports a different selection through
1990
- // getSelection than the one that it actually shows to the user.
1991
- // This forces a selection update when lines are joined to work
1992
- // around that. Issue #54
1993
- if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1994
- update.state.doc.lines != update.startState.doc.lines)
1995
- this.forceSelection = true;
1996
- let prevDeco = this.decorations, deco = this.updateDeco();
1997
- let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1998
- changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1999
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
2000
- return false;
2001
- }
2002
- else {
2003
- this.updateInner(changedRanges, deco, update.startState.doc.length);
2004
- if (update.transactions.length)
2005
- this.lastUpdate = Date.now();
2006
- return true;
2007
- }
2008
- }
2009
- reset(sel) {
2010
- if (this.dirty) {
2011
- this.view.observer.ignore(() => this.view.docView.sync());
2012
- this.dirty = 0 /* Not */;
2013
- this.updateSelection(true);
2014
- }
2015
- else {
2016
- this.updateSelection();
2017
- }
2018
- }
2019
- // Used by update and the constructor do perform the actual DOM
2020
- // update
2021
- updateInner(changes, deco, oldLength) {
2022
- this.view.viewState.mustMeasureContent = true;
2023
- this.updateChildren(changes, deco, oldLength);
2024
- let { observer } = this.view;
2025
- observer.ignore(() => {
2026
- // Lock the height during redrawing, since Chrome sometimes
2027
- // messes with the scroll position during DOM mutation (though
2028
- // no relayout is triggered and I cannot imagine how it can
2029
- // recompute the scroll position without a layout)
2030
- this.dom.style.height = this.view.viewState.contentHeight + "px";
2031
- this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2032
- // Chrome will sometimes, when DOM mutations occur directly
2033
- // around the selection, get confused and report a different
2034
- // selection from the one it displays (issue #218). This tries
2035
- // to detect that situation.
2036
- let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2037
- this.sync(track);
2038
- this.dirty = 0 /* Not */;
2039
- if (track && (track.written || observer.selectionRange.focusNode != track.node))
2040
- this.forceSelection = true;
2041
- this.dom.style.height = "";
2042
- });
2043
- let gaps = [];
2044
- if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2045
- for (let child of this.children)
2046
- if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2047
- gaps.push(child.dom);
2048
- observer.updateGaps(gaps);
2049
- }
2050
- updateChildren(changes, deco, oldLength) {
2051
- let cursor = this.childCursor(oldLength);
2052
- for (let i = changes.length - 1;; i--) {
2053
- let next = i >= 0 ? changes[i] : null;
2054
- if (!next)
2055
- break;
2056
- let { fromA, toA, fromB, toB } = next;
2057
- let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2058
- let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2059
- let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2060
- replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2061
- }
2062
- }
2063
- // Sync the DOM selection to this.state.selection
2064
- updateSelection(mustRead = false, fromPointer = false) {
2065
- if (mustRead)
2066
- this.view.observer.readSelectionRange();
2067
- if (!(fromPointer || this.mayControlSelection()) ||
2068
- browser.ios && this.view.inputState.rapidCompositionStart)
2069
- return;
2070
- let force = this.forceSelection;
2071
- this.forceSelection = false;
2072
- let main = this.view.state.selection.main;
2073
- // FIXME need to handle the case where the selection falls inside a block range
2074
- let anchor = this.domAtPos(main.anchor);
2075
- let head = main.empty ? anchor : this.domAtPos(main.head);
2076
- // Always reset on Firefox when next to an uneditable node to
2077
- // avoid invisible cursor bugs (#111)
2078
- if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2079
- let dummy = document.createTextNode("");
2080
- this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2081
- anchor = head = new DOMPos(dummy, 0);
2082
- force = true;
2083
- }
2084
- let domSel = this.view.observer.selectionRange;
2085
- // If the selection is already here, or in an equivalent position, don't touch it
2086
- if (force || !domSel.focusNode ||
2087
- !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2088
- !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2089
- this.view.observer.ignore(() => {
2090
- // Chrome Android will hide the virtual keyboard when tapping
2091
- // inside an uneditable node, and not bring it back when we
2092
- // move the cursor to its proper position. This tries to
2093
- // restore the keyboard by cycling focus.
2094
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2095
- this.dom.blur();
2096
- this.dom.focus({ preventScroll: true });
2097
- }
2098
- let rawSel = getSelection(this.root);
2099
- if (main.empty) {
2100
- // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2101
- if (browser.gecko) {
2102
- let nextTo = nextToUneditable(anchor.node, anchor.offset);
2103
- if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2104
- let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2105
- if (text)
2106
- anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2107
- }
2108
- }
2109
- rawSel.collapse(anchor.node, anchor.offset);
2110
- if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2111
- domSel.cursorBidiLevel = main.bidiLevel;
2112
- }
2113
- else if (rawSel.extend) {
2114
- // Selection.extend can be used to create an 'inverted' selection
2115
- // (one where the focus is before the anchor), but not all
2116
- // browsers support it yet.
2117
- rawSel.collapse(anchor.node, anchor.offset);
2118
- rawSel.extend(head.node, head.offset);
2119
- }
2120
- else {
2121
- // Primitive (IE) way
2122
- let range = document.createRange();
2123
- if (main.anchor > main.head)
2124
- [anchor, head] = [head, anchor];
2125
- range.setEnd(head.node, head.offset);
2126
- range.setStart(anchor.node, anchor.offset);
2127
- rawSel.removeAllRanges();
2128
- rawSel.addRange(range);
2129
- }
2130
- });
2131
- this.view.observer.setSelectionRange(anchor, head);
2132
- }
2133
- this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2134
- this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2135
- }
2136
- enforceCursorAssoc() {
2137
- if (this.view.composing)
2138
- return;
2139
- let cursor = this.view.state.selection.main;
2140
- let sel = getSelection(this.root);
2141
- if (!cursor.empty || !cursor.assoc || !sel.modify)
2142
- return;
2143
- let line = LineView.find(this, cursor.head);
2144
- if (!line)
2145
- return;
2146
- let lineStart = line.posAtStart;
2147
- if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2148
- return;
2149
- let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2150
- if (!before || !after || before.bottom > after.top)
2151
- return;
2152
- let dom = this.domAtPos(cursor.head + cursor.assoc);
2153
- sel.collapse(dom.node, dom.offset);
2154
- sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2155
- }
2156
- mayControlSelection() {
2157
- return this.view.state.facet(editable) ? this.root.activeElement == this.dom
2158
- : hasSelection(this.dom, this.view.observer.selectionRange);
2159
- }
2160
- nearest(dom) {
2161
- for (let cur = dom; cur;) {
2162
- let domView = ContentView.get(cur);
2163
- if (domView && domView.rootView == this)
2164
- return domView;
2165
- cur = cur.parentNode;
2166
- }
2167
- return null;
2168
- }
2169
- posFromDOM(node, offset) {
2170
- let view = this.nearest(node);
2171
- if (!view)
2172
- throw new RangeError("Trying to find position for a DOM position outside of the document");
2173
- return view.localPosFromDOM(node, offset) + view.posAtStart;
2174
- }
2175
- domAtPos(pos) {
2176
- let { i, off } = this.childCursor().findPos(pos, -1);
2177
- for (; i < this.children.length - 1;) {
2178
- let child = this.children[i];
2179
- if (off < child.length || child instanceof LineView)
2180
- break;
2181
- i++;
2182
- off = 0;
2183
- }
2184
- return this.children[i].domAtPos(off);
2185
- }
2186
- coordsAt(pos, side) {
2187
- for (let off = this.length, i = this.children.length - 1;; i--) {
2188
- let child = this.children[i], start = off - child.breakAfter - child.length;
2189
- if (pos > start ||
2190
- (pos == start && child.type != BlockType.WidgetBefore && child.type != BlockType.WidgetAfter &&
2191
- (!i || side == 2 || this.children[i - 1].breakAfter ||
2192
- (this.children[i - 1].type == BlockType.WidgetBefore && side > -2))))
2193
- return child.coordsAt(pos - start, side);
2194
- off = start;
2195
- }
2196
- }
2197
- measureVisibleLineHeights() {
2198
- let result = [], { from, to } = this.view.viewState.viewport;
2199
- let minWidth = Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2200
- for (let pos = 0, i = 0; i < this.children.length; i++) {
2201
- let child = this.children[i], end = pos + child.length;
2202
- if (end > to)
2203
- break;
2204
- if (pos >= from) {
2205
- result.push(child.dom.getBoundingClientRect().height);
2206
- let width = child.dom.scrollWidth;
2207
- if (width > minWidth) {
2208
- this.minWidth = minWidth = width;
2209
- this.minWidthFrom = pos;
2210
- this.minWidthTo = end;
2211
- }
2212
- }
2213
- pos = end + child.breakAfter;
2214
- }
2215
- return result;
2216
- }
2217
- measureTextSize() {
2218
- for (let child of this.children) {
2219
- if (child instanceof LineView) {
2220
- let measure = child.measureTextSize();
2221
- if (measure)
2222
- return measure;
2223
- }
2224
- }
2225
- // If no workable line exists, force a layout of a measurable element
2226
- let dummy = document.createElement("div"), lineHeight, charWidth;
2227
- dummy.className = "cm-line";
2228
- dummy.textContent = "abc def ghi jkl mno pqr stu";
2229
- this.view.observer.ignore(() => {
2230
- this.dom.appendChild(dummy);
2231
- let rect = clientRectsFor(dummy.firstChild)[0];
2232
- lineHeight = dummy.getBoundingClientRect().height;
2233
- charWidth = rect ? rect.width / 27 : 7;
2234
- dummy.remove();
2235
- });
2236
- return { lineHeight, charWidth };
2237
- }
2238
- childCursor(pos = this.length) {
2239
- // Move back to start of last element when possible, so that
2240
- // `ChildCursor.findPos` doesn't have to deal with the edge case
2241
- // of being after the last element.
2242
- let i = this.children.length;
2243
- if (i)
2244
- pos -= this.children[--i].length;
2245
- return new ChildCursor(this.children, pos, i);
2246
- }
2247
- computeBlockGapDeco() {
2248
- let deco = [], vs = this.view.viewState;
2249
- for (let pos = 0, i = 0;; i++) {
2250
- let next = i == vs.viewports.length ? null : vs.viewports[i];
2251
- let end = next ? next.from - 1 : this.length;
2252
- if (end > pos) {
2253
- let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2254
- deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2255
- }
2256
- if (!next)
2257
- break;
2258
- pos = next.to + 1;
2259
- }
2260
- return Decoration.set(deco);
2261
- }
2262
- updateDeco() {
2263
- return this.decorations = [
2264
- ...this.view.pluginField(PluginField.decorations),
2265
- ...this.view.state.facet(decorations),
2266
- this.compositionDeco,
2267
- this.computeBlockGapDeco(),
2268
- this.view.viewState.lineGapDeco
2269
- ];
2270
- }
2271
- scrollIntoView({ range, center }) {
2272
- let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2273
- if (!rect)
2274
- return;
2275
- if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2276
- rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2277
- right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2278
- let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2279
- for (let margins of this.view.pluginField(PluginField.scrollMargins))
2280
- if (margins) {
2281
- let { left, right, top, bottom } = margins;
2282
- if (left != null)
2283
- mLeft = Math.max(mLeft, left);
2284
- if (right != null)
2285
- mRight = Math.max(mRight, right);
2286
- if (top != null)
2287
- mTop = Math.max(mTop, top);
2288
- if (bottom != null)
2289
- mBottom = Math.max(mBottom, bottom);
2290
- }
2291
- scrollRectIntoView(this.view.scrollDOM, {
2292
- left: rect.left - mLeft, top: rect.top - mTop,
2293
- right: rect.right + mRight, bottom: rect.bottom + mBottom
2294
- }, range.head < range.anchor ? -1 : 1, center);
2295
- }
2296
- }
2297
- function betweenUneditable(pos) {
2298
- return pos.node.nodeType == 1 && pos.node.firstChild &&
2299
- (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2300
- (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2301
- }
2302
- class BlockGapWidget extends WidgetType {
2303
- constructor(height) {
2304
- super();
2305
- this.height = height;
2306
- }
2307
- toDOM() {
2308
- let elt = document.createElement("div");
2309
- this.updateDOM(elt);
2310
- return elt;
2311
- }
2312
- eq(other) { return other.height == this.height; }
2313
- updateDOM(elt) {
2314
- elt.style.height = this.height + "px";
2315
- return true;
2316
- }
2317
- get estimatedHeight() { return this.height; }
2318
- }
2319
- function computeCompositionDeco(view, changes) {
2320
- let sel = view.observer.selectionRange;
2321
- let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2322
- if (!textNode)
2323
- return Decoration.none;
2324
- let cView = view.docView.nearest(textNode);
2325
- if (!cView)
2326
- return Decoration.none;
2327
- let from, to, topNode = textNode;
2328
- if (cView instanceof LineView) {
2329
- while (topNode.parentNode != cView.dom)
2330
- topNode = topNode.parentNode;
2331
- let prev = topNode.previousSibling;
2332
- while (prev && !ContentView.get(prev))
2333
- prev = prev.previousSibling;
2334
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2335
- }
2336
- else {
2337
- for (;;) {
2338
- let { parent } = cView;
2339
- if (!parent)
2340
- return Decoration.none;
2341
- if (parent instanceof LineView)
2342
- break;
2343
- cView = parent;
2344
- }
2345
- from = cView.posAtStart;
2346
- to = from + cView.length;
2347
- topNode = cView.dom;
2348
- }
2349
- let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2350
- let text = textNode.nodeValue, { state } = view;
2351
- if (newTo - newFrom < text.length) {
2352
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2353
- newTo = newFrom + text.length;
2354
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2355
- newFrom = newTo - text.length;
2356
- else
2357
- return Decoration.none;
2358
- }
2359
- else if (state.sliceDoc(newFrom, newTo) != text) {
2360
- return Decoration.none;
2361
- }
2362
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2363
- }
2364
- class CompositionWidget extends WidgetType {
2365
- constructor(top, text) {
2366
- super();
2367
- this.top = top;
2368
- this.text = text;
2369
- }
2370
- eq(other) { return this.top == other.top && this.text == other.text; }
2371
- toDOM() { return this.top; }
2372
- ignoreEvent() { return false; }
2373
- get customView() { return CompositionView; }
2374
- }
2375
- function nearbyTextNode(node, offset, side) {
2376
- for (;;) {
2377
- if (node.nodeType == 3)
2378
- return node;
2379
- if (node.nodeType == 1 && offset > 0 && side <= 0) {
2380
- node = node.childNodes[offset - 1];
2381
- offset = maxOffset(node);
2382
- }
2383
- else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2384
- node = node.childNodes[offset];
2385
- offset = 0;
2386
- }
2387
- else {
2388
- return null;
2389
- }
2390
- }
2391
- }
2392
- function nextToUneditable(node, offset) {
2393
- if (node.nodeType != 1)
2394
- return 0;
2395
- return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2396
- (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2397
- }
2398
- class DecorationComparator$1 {
2399
- constructor() {
2400
- this.changes = [];
2401
- }
2402
- compareRange(from, to) { addRange(from, to, this.changes); }
2403
- comparePoint(from, to) { addRange(from, to, this.changes); }
2404
- }
2405
- function findChangedDeco(a, b, diff) {
2406
- let comp = new DecorationComparator$1;
2407
- RangeSet.compare(a, b, diff, comp);
2408
- return comp.changes;
2409
- }
2410
- function inUneditable(node, inside) {
2411
- for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2412
- if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2413
- return true;
2414
- }
2415
- }
2416
- return false;
2417
- }
2418
-
2419
1941
  /**
2420
1942
  Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
2421
1943
  */
@@ -2563,168 +2085,662 @@ function computeOrder(line, direction) {
2563
2085
  else
2564
2086
  types[i] = 256 /* NI */;
2565
2087
  }
2566
- else if (type == 64 /* ET */) {
2567
- let end = i + 1;
2568
- while (end < len && types[end] == 64 /* ET */)
2569
- end++;
2570
- let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2571
- for (let j = i; j < end; j++)
2572
- types[j] = replace;
2573
- i = end - 1;
2088
+ else if (type == 64 /* ET */) {
2089
+ let end = i + 1;
2090
+ while (end < len && types[end] == 64 /* ET */)
2091
+ end++;
2092
+ let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2093
+ for (let j = i; j < end; j++)
2094
+ types[j] = replace;
2095
+ i = end - 1;
2096
+ }
2097
+ else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2098
+ types[i] = 1 /* L */;
2099
+ }
2100
+ prev = type;
2101
+ if (type & 7 /* Strong */)
2102
+ prevStrong = type;
2103
+ }
2104
+ // N0. Process bracket pairs in an isolating run sequence
2105
+ // sequentially in the logical order of the text positions of the
2106
+ // opening paired brackets using the logic given below. Within this
2107
+ // scope, bidirectional types EN and AN are treated as R.
2108
+ for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2109
+ // Keeps [startIndex, type, strongSeen] triples for each open
2110
+ // bracket on BracketStack.
2111
+ if (br = Brackets[ch = line.charCodeAt(i)]) {
2112
+ if (br < 0) { // Closing bracket
2113
+ for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2114
+ if (BracketStack[sJ + 1] == -br) {
2115
+ let flags = BracketStack[sJ + 2];
2116
+ let type = (flags & 2 /* EmbedInside */) ? outerType :
2117
+ !(flags & 4 /* OppositeInside */) ? 0 :
2118
+ (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2119
+ if (type)
2120
+ types[i] = types[BracketStack[sJ]] = type;
2121
+ sI = sJ;
2122
+ break;
2123
+ }
2124
+ }
2125
+ }
2126
+ else if (BracketStack.length == 189 /* MaxDepth */) {
2127
+ break;
2128
+ }
2129
+ else {
2130
+ BracketStack[sI++] = i;
2131
+ BracketStack[sI++] = ch;
2132
+ BracketStack[sI++] = context;
2133
+ }
2134
+ }
2135
+ else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2136
+ let embed = type == outerType;
2137
+ context = embed ? 0 : 1 /* OppositeBefore */;
2138
+ for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2139
+ let cur = BracketStack[sJ + 2];
2140
+ if (cur & 2 /* EmbedInside */)
2141
+ break;
2142
+ if (embed) {
2143
+ BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2144
+ }
2145
+ else {
2146
+ if (cur & 4 /* OppositeInside */)
2147
+ break;
2148
+ BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2149
+ }
2150
+ }
2151
+ }
2152
+ }
2153
+ // N1. A sequence of neutrals takes the direction of the
2154
+ // surrounding strong text if the text on both sides has the same
2155
+ // direction. European and Arabic numbers act as if they were R in
2156
+ // terms of their influence on neutrals. Start-of-level-run (sor)
2157
+ // and end-of-level-run (eor) are used at level run boundaries.
2158
+ // N2. Any remaining neutrals take the embedding direction.
2159
+ // (Left after this: L, R, EN+AN)
2160
+ for (let i = 0; i < len; i++) {
2161
+ if (types[i] == 256 /* NI */) {
2162
+ let end = i + 1;
2163
+ while (end < len && types[end] == 256 /* NI */)
2164
+ end++;
2165
+ let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2166
+ let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2167
+ let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2168
+ for (let j = i; j < end; j++)
2169
+ types[j] = replace;
2170
+ i = end - 1;
2171
+ }
2172
+ }
2173
+ // Here we depart from the documented algorithm, in order to avoid
2174
+ // building up an actual levels array. Since there are only three
2175
+ // levels (0, 1, 2) in an implementation that doesn't take
2176
+ // explicit embedding into account, we can build up the order on
2177
+ // the fly, without following the level-based algorithm.
2178
+ let order = [];
2179
+ if (outerType == 1 /* L */) {
2180
+ for (let i = 0; i < len;) {
2181
+ let start = i, rtl = types[i++] != 1 /* L */;
2182
+ while (i < len && rtl == (types[i] != 1 /* L */))
2183
+ i++;
2184
+ if (rtl) {
2185
+ for (let j = i; j > start;) {
2186
+ let end = j, l = types[--j] != 2 /* R */;
2187
+ while (j > start && l == (types[j - 1] != 2 /* R */))
2188
+ j--;
2189
+ order.push(new BidiSpan(j, end, l ? 2 : 1));
2190
+ }
2191
+ }
2192
+ else {
2193
+ order.push(new BidiSpan(start, i, 0));
2194
+ }
2195
+ }
2196
+ }
2197
+ else {
2198
+ for (let i = 0; i < len;) {
2199
+ let start = i, rtl = types[i++] == 2 /* R */;
2200
+ while (i < len && rtl == (types[i] == 2 /* R */))
2201
+ i++;
2202
+ order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2203
+ }
2204
+ }
2205
+ return order;
2206
+ }
2207
+ function trivialOrder(length) {
2208
+ return [new BidiSpan(0, length, 0)];
2209
+ }
2210
+ let movedOver = "";
2211
+ function moveVisually(line, order, dir, start, forward) {
2212
+ var _a;
2213
+ let startIndex = start.head - line.from, spanI = -1;
2214
+ if (startIndex == 0) {
2215
+ if (!forward || !line.length)
2216
+ return null;
2217
+ if (order[0].level != dir) {
2218
+ startIndex = order[0].side(false, dir);
2219
+ spanI = 0;
2220
+ }
2221
+ }
2222
+ else if (startIndex == line.length) {
2223
+ if (forward)
2224
+ return null;
2225
+ let last = order[order.length - 1];
2226
+ if (last.level != dir) {
2227
+ startIndex = last.side(true, dir);
2228
+ spanI = order.length - 1;
2229
+ }
2230
+ }
2231
+ if (spanI < 0)
2232
+ spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2233
+ let span = order[spanI];
2234
+ // End of span. (But not end of line--that was checked for above.)
2235
+ if (startIndex == span.side(forward, dir)) {
2236
+ span = order[spanI += forward ? 1 : -1];
2237
+ startIndex = span.side(!forward, dir);
2238
+ }
2239
+ let indexForward = forward == (span.dir == dir);
2240
+ let nextIndex = findClusterBreak(line.text, startIndex, indexForward);
2241
+ movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2242
+ if (nextIndex != span.side(forward, dir))
2243
+ return EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2244
+ let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2245
+ if (!nextSpan && span.level != dir)
2246
+ return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2247
+ if (nextSpan && nextSpan.level < span.level)
2248
+ return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2249
+ return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2250
+ }
2251
+
2252
+ class DocView extends ContentView {
2253
+ constructor(view) {
2254
+ super();
2255
+ this.view = view;
2256
+ this.compositionDeco = Decoration.none;
2257
+ this.decorations = [];
2258
+ // Track a minimum width for the editor. When measuring sizes in
2259
+ // measureVisibleLineHeights, this is updated to point at the width
2260
+ // of a given element and its extent in the document. When a change
2261
+ // happens in that range, these are reset. That way, once we've seen
2262
+ // a line/element of a given length, we keep the editor wide enough
2263
+ // to fit at least that element, until it is changed, at which point
2264
+ // we forget it again.
2265
+ this.minWidth = 0;
2266
+ this.minWidthFrom = 0;
2267
+ this.minWidthTo = 0;
2268
+ // Track whether the DOM selection was set in a lossy way, so that
2269
+ // we don't mess it up when reading it back it
2270
+ this.impreciseAnchor = null;
2271
+ this.impreciseHead = null;
2272
+ this.forceSelection = false;
2273
+ // Used by the resize observer to ignore resizes that we caused
2274
+ // ourselves
2275
+ this.lastUpdate = Date.now();
2276
+ this.setDOM(view.contentDOM);
2277
+ this.children = [new LineView];
2278
+ this.children[0].setParent(this);
2279
+ this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
2280
+ }
2281
+ get root() { return this.view.root; }
2282
+ get editorView() { return this.view; }
2283
+ get length() { return this.view.state.doc.length; }
2284
+ // Update the document view to a given state. scrollIntoView can be
2285
+ // used as a hint to compute a new viewport that includes that
2286
+ // position, if we know the editor is going to scroll that position
2287
+ // into view.
2288
+ update(update) {
2289
+ let changedRanges = update.changedRanges;
2290
+ if (this.minWidth > 0 && changedRanges.length) {
2291
+ if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
2292
+ this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
2293
+ }
2294
+ else {
2295
+ this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
2296
+ this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2297
+ }
2298
+ }
2299
+ if (this.view.inputState.composing < 0)
2300
+ this.compositionDeco = Decoration.none;
2301
+ else if (update.transactions.length)
2302
+ this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2303
+ // When the DOM nodes around the selection are moved to another
2304
+ // parent, Chrome sometimes reports a different selection through
2305
+ // getSelection than the one that it actually shows to the user.
2306
+ // This forces a selection update when lines are joined to work
2307
+ // around that. Issue #54
2308
+ if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
2309
+ update.state.doc.lines != update.startState.doc.lines)
2310
+ this.forceSelection = true;
2311
+ let prevDeco = this.decorations, deco = this.updateDeco();
2312
+ let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
2313
+ changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
2314
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
2315
+ return false;
2316
+ }
2317
+ else {
2318
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
2319
+ if (update.transactions.length)
2320
+ this.lastUpdate = Date.now();
2321
+ return true;
2322
+ }
2323
+ }
2324
+ reset(sel) {
2325
+ if (this.dirty) {
2326
+ this.view.observer.ignore(() => this.view.docView.sync());
2327
+ this.dirty = 0 /* Not */;
2328
+ this.updateSelection(true);
2329
+ }
2330
+ else {
2331
+ this.updateSelection();
2574
2332
  }
2575
- else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2576
- types[i] = 1 /* L */;
2333
+ }
2334
+ // Used by update and the constructor do perform the actual DOM
2335
+ // update
2336
+ updateInner(changes, deco, oldLength) {
2337
+ this.view.viewState.mustMeasureContent = true;
2338
+ this.updateChildren(changes, deco, oldLength);
2339
+ let { observer } = this.view;
2340
+ observer.ignore(() => {
2341
+ // Lock the height during redrawing, since Chrome sometimes
2342
+ // messes with the scroll position during DOM mutation (though
2343
+ // no relayout is triggered and I cannot imagine how it can
2344
+ // recompute the scroll position without a layout)
2345
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2346
+ this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2347
+ // Chrome will sometimes, when DOM mutations occur directly
2348
+ // around the selection, get confused and report a different
2349
+ // selection from the one it displays (issue #218). This tries
2350
+ // to detect that situation.
2351
+ let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2352
+ this.sync(track);
2353
+ this.dirty = 0 /* Not */;
2354
+ if (track && (track.written || observer.selectionRange.focusNode != track.node))
2355
+ this.forceSelection = true;
2356
+ this.dom.style.height = "";
2357
+ });
2358
+ let gaps = [];
2359
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2360
+ for (let child of this.children)
2361
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2362
+ gaps.push(child.dom);
2363
+ observer.updateGaps(gaps);
2364
+ }
2365
+ updateChildren(changes, deco, oldLength) {
2366
+ let cursor = this.childCursor(oldLength);
2367
+ for (let i = changes.length - 1;; i--) {
2368
+ let next = i >= 0 ? changes[i] : null;
2369
+ if (!next)
2370
+ break;
2371
+ let { fromA, toA, fromB, toB } = next;
2372
+ let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2373
+ let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2374
+ let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2375
+ replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2577
2376
  }
2578
- prev = type;
2579
- if (type & 7 /* Strong */)
2580
- prevStrong = type;
2581
2377
  }
2582
- // N0. Process bracket pairs in an isolating run sequence
2583
- // sequentially in the logical order of the text positions of the
2584
- // opening paired brackets using the logic given below. Within this
2585
- // scope, bidirectional types EN and AN are treated as R.
2586
- for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2587
- // Keeps [startIndex, type, strongSeen] triples for each open
2588
- // bracket on BracketStack.
2589
- if (br = Brackets[ch = line.charCodeAt(i)]) {
2590
- if (br < 0) { // Closing bracket
2591
- for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2592
- if (BracketStack[sJ + 1] == -br) {
2593
- let flags = BracketStack[sJ + 2];
2594
- let type = (flags & 2 /* EmbedInside */) ? outerType :
2595
- !(flags & 4 /* OppositeInside */) ? 0 :
2596
- (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2597
- if (type)
2598
- types[i] = types[BracketStack[sJ]] = type;
2599
- sI = sJ;
2600
- break;
2378
+ // Sync the DOM selection to this.state.selection
2379
+ updateSelection(mustRead = false, fromPointer = false) {
2380
+ if (mustRead)
2381
+ this.view.observer.readSelectionRange();
2382
+ if (!(fromPointer || this.mayControlSelection()) ||
2383
+ browser.ios && this.view.inputState.rapidCompositionStart)
2384
+ return;
2385
+ let force = this.forceSelection;
2386
+ this.forceSelection = false;
2387
+ let main = this.view.state.selection.main;
2388
+ // FIXME need to handle the case where the selection falls inside a block range
2389
+ let anchor = this.domAtPos(main.anchor);
2390
+ let head = main.empty ? anchor : this.domAtPos(main.head);
2391
+ // Always reset on Firefox when next to an uneditable node to
2392
+ // avoid invisible cursor bugs (#111)
2393
+ if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2394
+ let dummy = document.createTextNode("");
2395
+ this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2396
+ anchor = head = new DOMPos(dummy, 0);
2397
+ force = true;
2398
+ }
2399
+ let domSel = this.view.observer.selectionRange;
2400
+ // If the selection is already here, or in an equivalent position, don't touch it
2401
+ if (force || !domSel.focusNode ||
2402
+ !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2403
+ !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2404
+ this.view.observer.ignore(() => {
2405
+ // Chrome Android will hide the virtual keyboard when tapping
2406
+ // inside an uneditable node, and not bring it back when we
2407
+ // move the cursor to its proper position. This tries to
2408
+ // restore the keyboard by cycling focus.
2409
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2410
+ this.dom.blur();
2411
+ this.dom.focus({ preventScroll: true });
2412
+ }
2413
+ let rawSel = getSelection(this.root);
2414
+ if (main.empty) {
2415
+ // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2416
+ if (browser.gecko) {
2417
+ let nextTo = nextToUneditable(anchor.node, anchor.offset);
2418
+ if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2419
+ let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2420
+ if (text)
2421
+ anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2422
+ }
2601
2423
  }
2424
+ rawSel.collapse(anchor.node, anchor.offset);
2425
+ if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2426
+ domSel.cursorBidiLevel = main.bidiLevel;
2602
2427
  }
2603
- }
2604
- else if (BracketStack.length == 189 /* MaxDepth */) {
2605
- break;
2606
- }
2607
- else {
2608
- BracketStack[sI++] = i;
2609
- BracketStack[sI++] = ch;
2610
- BracketStack[sI++] = context;
2611
- }
2612
- }
2613
- else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2614
- let embed = type == outerType;
2615
- context = embed ? 0 : 1 /* OppositeBefore */;
2616
- for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2617
- let cur = BracketStack[sJ + 2];
2618
- if (cur & 2 /* EmbedInside */)
2619
- break;
2620
- if (embed) {
2621
- BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2428
+ else if (rawSel.extend) {
2429
+ // Selection.extend can be used to create an 'inverted' selection
2430
+ // (one where the focus is before the anchor), but not all
2431
+ // browsers support it yet.
2432
+ rawSel.collapse(anchor.node, anchor.offset);
2433
+ rawSel.extend(head.node, head.offset);
2622
2434
  }
2623
2435
  else {
2624
- if (cur & 4 /* OppositeInside */)
2625
- break;
2626
- BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2436
+ // Primitive (IE) way
2437
+ let range = document.createRange();
2438
+ if (main.anchor > main.head)
2439
+ [anchor, head] = [head, anchor];
2440
+ range.setEnd(head.node, head.offset);
2441
+ range.setStart(anchor.node, anchor.offset);
2442
+ rawSel.removeAllRanges();
2443
+ rawSel.addRange(range);
2627
2444
  }
2628
- }
2445
+ });
2446
+ this.view.observer.setSelectionRange(anchor, head);
2629
2447
  }
2448
+ this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2449
+ this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2630
2450
  }
2631
- // N1. A sequence of neutrals takes the direction of the
2632
- // surrounding strong text if the text on both sides has the same
2633
- // direction. European and Arabic numbers act as if they were R in
2634
- // terms of their influence on neutrals. Start-of-level-run (sor)
2635
- // and end-of-level-run (eor) are used at level run boundaries.
2636
- // N2. Any remaining neutrals take the embedding direction.
2637
- // (Left after this: L, R, EN+AN)
2638
- for (let i = 0; i < len; i++) {
2639
- if (types[i] == 256 /* NI */) {
2640
- let end = i + 1;
2641
- while (end < len && types[end] == 256 /* NI */)
2642
- end++;
2643
- let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2644
- let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2645
- let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2646
- for (let j = i; j < end; j++)
2647
- types[j] = replace;
2648
- i = end - 1;
2451
+ enforceCursorAssoc() {
2452
+ if (this.view.composing)
2453
+ return;
2454
+ let cursor = this.view.state.selection.main;
2455
+ let sel = getSelection(this.root);
2456
+ if (!cursor.empty || !cursor.assoc || !sel.modify)
2457
+ return;
2458
+ let line = LineView.find(this, cursor.head);
2459
+ if (!line)
2460
+ return;
2461
+ let lineStart = line.posAtStart;
2462
+ if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2463
+ return;
2464
+ let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2465
+ if (!before || !after || before.bottom > after.top)
2466
+ return;
2467
+ let dom = this.domAtPos(cursor.head + cursor.assoc);
2468
+ sel.collapse(dom.node, dom.offset);
2469
+ sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2470
+ }
2471
+ mayControlSelection() {
2472
+ return this.view.state.facet(editable) ? this.root.activeElement == this.dom
2473
+ : hasSelection(this.dom, this.view.observer.selectionRange);
2474
+ }
2475
+ nearest(dom) {
2476
+ for (let cur = dom; cur;) {
2477
+ let domView = ContentView.get(cur);
2478
+ if (domView && domView.rootView == this)
2479
+ return domView;
2480
+ cur = cur.parentNode;
2481
+ }
2482
+ return null;
2483
+ }
2484
+ posFromDOM(node, offset) {
2485
+ let view = this.nearest(node);
2486
+ if (!view)
2487
+ throw new RangeError("Trying to find position for a DOM position outside of the document");
2488
+ return view.localPosFromDOM(node, offset) + view.posAtStart;
2489
+ }
2490
+ domAtPos(pos) {
2491
+ let { i, off } = this.childCursor().findPos(pos, -1);
2492
+ for (; i < this.children.length - 1;) {
2493
+ let child = this.children[i];
2494
+ if (off < child.length || child instanceof LineView)
2495
+ break;
2496
+ i++;
2497
+ off = 0;
2498
+ }
2499
+ return this.children[i].domAtPos(off);
2500
+ }
2501
+ coordsAt(pos, side) {
2502
+ for (let off = this.length, i = this.children.length - 1;; i--) {
2503
+ let child = this.children[i], start = off - child.breakAfter - child.length;
2504
+ if (pos > start ||
2505
+ (pos == start && child.type != BlockType.WidgetBefore && child.type != BlockType.WidgetAfter &&
2506
+ (!i || side == 2 || this.children[i - 1].breakAfter ||
2507
+ (this.children[i - 1].type == BlockType.WidgetBefore && side > -2))))
2508
+ return child.coordsAt(pos - start, side);
2509
+ off = start;
2510
+ }
2511
+ }
2512
+ measureVisibleLineHeights() {
2513
+ let result = [], { from, to } = this.view.viewState.viewport;
2514
+ let contentWidth = this.view.contentDOM.clientWidth;
2515
+ let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2516
+ let widest = -1;
2517
+ for (let pos = 0, i = 0; i < this.children.length; i++) {
2518
+ let child = this.children[i], end = pos + child.length;
2519
+ if (end > to)
2520
+ break;
2521
+ if (pos >= from) {
2522
+ let childRect = child.dom.getBoundingClientRect();
2523
+ result.push(childRect.height);
2524
+ if (isWider) {
2525
+ let last = child.dom.lastChild;
2526
+ let rects = last ? clientRectsFor(last) : [];
2527
+ if (rects.length) {
2528
+ let rect = rects[rects.length - 1];
2529
+ let width = this.view.textDirection == Direction.LTR ? rect.right - childRect.left
2530
+ : childRect.right - rect.left;
2531
+ if (width > widest) {
2532
+ widest = width;
2533
+ this.minWidth = contentWidth;
2534
+ this.minWidthFrom = pos;
2535
+ this.minWidthTo = end;
2536
+ }
2537
+ }
2538
+ }
2539
+ }
2540
+ pos = end + child.breakAfter;
2649
2541
  }
2542
+ return result;
2650
2543
  }
2651
- // Here we depart from the documented algorithm, in order to avoid
2652
- // building up an actual levels array. Since there are only three
2653
- // levels (0, 1, 2) in an implementation that doesn't take
2654
- // explicit embedding into account, we can build up the order on
2655
- // the fly, without following the level-based algorithm.
2656
- let order = [];
2657
- if (outerType == 1 /* L */) {
2658
- for (let i = 0; i < len;) {
2659
- let start = i, rtl = types[i++] != 1 /* L */;
2660
- while (i < len && rtl == (types[i] != 1 /* L */))
2661
- i++;
2662
- if (rtl) {
2663
- for (let j = i; j > start;) {
2664
- let end = j, l = types[--j] != 2 /* R */;
2665
- while (j > start && l == (types[j - 1] != 2 /* R */))
2666
- j--;
2667
- order.push(new BidiSpan(j, end, l ? 2 : 1));
2668
- }
2544
+ measureTextSize() {
2545
+ for (let child of this.children) {
2546
+ if (child instanceof LineView) {
2547
+ let measure = child.measureTextSize();
2548
+ if (measure)
2549
+ return measure;
2669
2550
  }
2670
- else {
2671
- order.push(new BidiSpan(start, i, 0));
2551
+ }
2552
+ // If no workable line exists, force a layout of a measurable element
2553
+ let dummy = document.createElement("div"), lineHeight, charWidth;
2554
+ dummy.className = "cm-line";
2555
+ dummy.textContent = "abc def ghi jkl mno pqr stu";
2556
+ this.view.observer.ignore(() => {
2557
+ this.dom.appendChild(dummy);
2558
+ let rect = clientRectsFor(dummy.firstChild)[0];
2559
+ lineHeight = dummy.getBoundingClientRect().height;
2560
+ charWidth = rect ? rect.width / 27 : 7;
2561
+ dummy.remove();
2562
+ });
2563
+ return { lineHeight, charWidth };
2564
+ }
2565
+ childCursor(pos = this.length) {
2566
+ // Move back to start of last element when possible, so that
2567
+ // `ChildCursor.findPos` doesn't have to deal with the edge case
2568
+ // of being after the last element.
2569
+ let i = this.children.length;
2570
+ if (i)
2571
+ pos -= this.children[--i].length;
2572
+ return new ChildCursor(this.children, pos, i);
2573
+ }
2574
+ computeBlockGapDeco() {
2575
+ let deco = [], vs = this.view.viewState;
2576
+ for (let pos = 0, i = 0;; i++) {
2577
+ let next = i == vs.viewports.length ? null : vs.viewports[i];
2578
+ let end = next ? next.from - 1 : this.length;
2579
+ if (end > pos) {
2580
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2581
+ deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2672
2582
  }
2583
+ if (!next)
2584
+ break;
2585
+ pos = next.to + 1;
2673
2586
  }
2587
+ return Decoration.set(deco);
2588
+ }
2589
+ updateDeco() {
2590
+ return this.decorations = [
2591
+ ...this.view.pluginField(PluginField.decorations),
2592
+ ...this.view.state.facet(decorations),
2593
+ this.compositionDeco,
2594
+ this.computeBlockGapDeco(),
2595
+ this.view.viewState.lineGapDeco
2596
+ ];
2597
+ }
2598
+ scrollIntoView({ range, center }) {
2599
+ let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2600
+ if (!rect)
2601
+ return;
2602
+ if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2603
+ rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2604
+ right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2605
+ let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2606
+ for (let margins of this.view.pluginField(PluginField.scrollMargins))
2607
+ if (margins) {
2608
+ let { left, right, top, bottom } = margins;
2609
+ if (left != null)
2610
+ mLeft = Math.max(mLeft, left);
2611
+ if (right != null)
2612
+ mRight = Math.max(mRight, right);
2613
+ if (top != null)
2614
+ mTop = Math.max(mTop, top);
2615
+ if (bottom != null)
2616
+ mBottom = Math.max(mBottom, bottom);
2617
+ }
2618
+ scrollRectIntoView(this.view.scrollDOM, {
2619
+ left: rect.left - mLeft, top: rect.top - mTop,
2620
+ right: rect.right + mRight, bottom: rect.bottom + mBottom
2621
+ }, range.head < range.anchor ? -1 : 1, center);
2622
+ }
2623
+ }
2624
+ function betweenUneditable(pos) {
2625
+ return pos.node.nodeType == 1 && pos.node.firstChild &&
2626
+ (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2627
+ (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2628
+ }
2629
+ class BlockGapWidget extends WidgetType {
2630
+ constructor(height) {
2631
+ super();
2632
+ this.height = height;
2633
+ }
2634
+ toDOM() {
2635
+ let elt = document.createElement("div");
2636
+ this.updateDOM(elt);
2637
+ return elt;
2638
+ }
2639
+ eq(other) { return other.height == this.height; }
2640
+ updateDOM(elt) {
2641
+ elt.style.height = this.height + "px";
2642
+ return true;
2643
+ }
2644
+ get estimatedHeight() { return this.height; }
2645
+ }
2646
+ function computeCompositionDeco(view, changes) {
2647
+ let sel = view.observer.selectionRange;
2648
+ let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2649
+ if (!textNode)
2650
+ return Decoration.none;
2651
+ let cView = view.docView.nearest(textNode);
2652
+ if (!cView)
2653
+ return Decoration.none;
2654
+ let from, to, topNode = textNode;
2655
+ if (cView instanceof LineView) {
2656
+ while (topNode.parentNode != cView.dom)
2657
+ topNode = topNode.parentNode;
2658
+ let prev = topNode.previousSibling;
2659
+ while (prev && !ContentView.get(prev))
2660
+ prev = prev.previousSibling;
2661
+ from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2674
2662
  }
2675
2663
  else {
2676
- for (let i = 0; i < len;) {
2677
- let start = i, rtl = types[i++] == 2 /* R */;
2678
- while (i < len && rtl == (types[i] == 2 /* R */))
2679
- i++;
2680
- order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2664
+ for (;;) {
2665
+ let { parent } = cView;
2666
+ if (!parent)
2667
+ return Decoration.none;
2668
+ if (parent instanceof LineView)
2669
+ break;
2670
+ cView = parent;
2681
2671
  }
2672
+ from = cView.posAtStart;
2673
+ to = from + cView.length;
2674
+ topNode = cView.dom;
2682
2675
  }
2683
- return order;
2676
+ let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2677
+ let text = textNode.nodeValue, { state } = view;
2678
+ if (newTo - newFrom < text.length) {
2679
+ if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2680
+ newTo = newFrom + text.length;
2681
+ else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2682
+ newFrom = newTo - text.length;
2683
+ else
2684
+ return Decoration.none;
2685
+ }
2686
+ else if (state.sliceDoc(newFrom, newTo) != text) {
2687
+ return Decoration.none;
2688
+ }
2689
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2684
2690
  }
2685
- function trivialOrder(length) {
2686
- return [new BidiSpan(0, length, 0)];
2691
+ class CompositionWidget extends WidgetType {
2692
+ constructor(top, text) {
2693
+ super();
2694
+ this.top = top;
2695
+ this.text = text;
2696
+ }
2697
+ eq(other) { return this.top == other.top && this.text == other.text; }
2698
+ toDOM() { return this.top; }
2699
+ ignoreEvent() { return false; }
2700
+ get customView() { return CompositionView; }
2687
2701
  }
2688
- let movedOver = "";
2689
- function moveVisually(line, order, dir, start, forward) {
2690
- var _a;
2691
- let startIndex = start.head - line.from, spanI = -1;
2692
- if (startIndex == 0) {
2693
- if (!forward || !line.length)
2694
- return null;
2695
- if (order[0].level != dir) {
2696
- startIndex = order[0].side(false, dir);
2697
- spanI = 0;
2702
+ function nearbyTextNode(node, offset, side) {
2703
+ for (;;) {
2704
+ if (node.nodeType == 3)
2705
+ return node;
2706
+ if (node.nodeType == 1 && offset > 0 && side <= 0) {
2707
+ node = node.childNodes[offset - 1];
2708
+ offset = maxOffset(node);
2698
2709
  }
2699
- }
2700
- else if (startIndex == line.length) {
2701
- if (forward)
2710
+ else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2711
+ node = node.childNodes[offset];
2712
+ offset = 0;
2713
+ }
2714
+ else {
2702
2715
  return null;
2703
- let last = order[order.length - 1];
2704
- if (last.level != dir) {
2705
- startIndex = last.side(true, dir);
2706
- spanI = order.length - 1;
2707
2716
  }
2708
2717
  }
2709
- if (spanI < 0)
2710
- spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2711
- let span = order[spanI];
2712
- // End of span. (But not end of line--that was checked for above.)
2713
- if (startIndex == span.side(forward, dir)) {
2714
- span = order[spanI += forward ? 1 : -1];
2715
- startIndex = span.side(!forward, dir);
2718
+ }
2719
+ function nextToUneditable(node, offset) {
2720
+ if (node.nodeType != 1)
2721
+ return 0;
2722
+ return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2723
+ (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2724
+ }
2725
+ class DecorationComparator$1 {
2726
+ constructor() {
2727
+ this.changes = [];
2716
2728
  }
2717
- let indexForward = forward == (span.dir == dir);
2718
- let nextIndex = findClusterBreak(line.text, startIndex, indexForward);
2719
- movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2720
- if (nextIndex != span.side(forward, dir))
2721
- return EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2722
- let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2723
- if (!nextSpan && span.level != dir)
2724
- return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2725
- if (nextSpan && nextSpan.level < span.level)
2726
- return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2727
- return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2729
+ compareRange(from, to) { addRange(from, to, this.changes); }
2730
+ comparePoint(from, to) { addRange(from, to, this.changes); }
2731
+ }
2732
+ function findChangedDeco(a, b, diff) {
2733
+ let comp = new DecorationComparator$1;
2734
+ RangeSet.compare(a, b, diff, comp);
2735
+ return comp.changes;
2736
+ }
2737
+ function inUneditable(node, inside) {
2738
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2739
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2740
+ return true;
2741
+ }
2742
+ }
2743
+ return false;
2728
2744
  }
2729
2745
 
2730
2746
  function groupAt(state, pos, bias = 1) {
@@ -5009,7 +5025,8 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5009
5025
  },
5010
5026
  ".cm-placeholder": {
5011
5027
  color: "#888",
5012
- display: "inline-block"
5028
+ display: "inline-block",
5029
+ verticalAlign: "top",
5013
5030
  },
5014
5031
  ".cm-button": {
5015
5032
  verticalAlign: "middle",
@@ -5438,11 +5455,22 @@ function applyDOMChange(view, start, end, typeOver) {
5438
5455
  };
5439
5456
  if (change) {
5440
5457
  let startState = view.state;
5458
+ if (browser.ios && view.inputState.flushIOSKey(view))
5459
+ return;
5441
5460
  // Android browsers don't fire reasonable key events for enter,
5442
5461
  // backspace, or delete. So this detects changes that look like
5443
5462
  // they're caused by those keys, and reinterprets them as key
5444
- // events.
5445
- if (browser.ios && view.inputState.flushIOSKey(view))
5463
+ // events. (Some of these keys are also handled by beforeinput
5464
+ // events and the pendingAndroidKey mechanism, but that's not
5465
+ // reliable in all situations.)
5466
+ if (browser.android &&
5467
+ ((change.from == sel.from && change.to == sel.to &&
5468
+ change.insert.length == 1 && change.insert.lines == 2 &&
5469
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
5470
+ (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5471
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
5472
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5473
+ dispatchKey(view.contentDOM, "Delete", 46))))
5446
5474
  return;
5447
5475
  let text = change.insert.toString();
5448
5476
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))