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