diff --git a/src/js/editor/editor.ts b/src/js/editor/editor.ts index 793a7f530..822c3a389 100644 --- a/src/js/editor/editor.ts +++ b/src/js/editor/editor.ts @@ -1105,7 +1105,7 @@ export default class Editor implements EditorOptions { * @return {Boolean} */ _hasFocus(): boolean { - return document.activeElement === this.element + return this.root.activeElement === this.element } /** @@ -1347,4 +1347,18 @@ export default class Editor implements EditorOptions { } } } + + get root() { + const root = this.element.getRootNode(); + + // COMPAT: Only Chrome implements the DocumentOrShadowRoot mixin for + // ShadowRoot; other browsers still implement it on the Document + // interface. (2020/08/08) + // https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot#Properties + if (root.getSelection === undefined && this.element.ownerDocument !== null) { + return this.element.ownerDocument + } + + return root + } } diff --git a/src/js/editor/selection-change-observer.ts b/src/js/editor/selection-change-observer.ts index c5f6b0c52..99f470168 100644 --- a/src/js/editor/selection-change-observer.ts +++ b/src/js/editor/selection-change-observer.ts @@ -11,21 +11,22 @@ class SelectionChangeObserver { listeners: SelectionChangeListener[] selection: PartialSelection - constructor() { + constructor({ editor }) { this.started = false this.listeners = [] this.selection = {} as PartialSelection + this.editor = editor } - static getInstance() { + static getInstance({ editor }) { if (!instance) { - instance = new SelectionChangeObserver() + instance = new SelectionChangeObserver({ editor }) } return instance } static addListener(listener: SelectionChangeListener) { - SelectionChangeObserver.getInstance().addListener(listener) + SelectionChangeObserver.getInstance({ editor: listener.editor }).addListener(listener) } addListener(listener: SelectionChangeListener) { @@ -74,8 +75,8 @@ class SelectionChangeObserver { this.listeners = [] } - getSelection(): PartialSelection { - let selection = window.getSelection()! + getSelection(root = window): PartialSelection { + let selection = root.getSelection()! let { anchorNode, focusNode, anchorOffset, focusOffset } = selection return { anchorNode, focusNode, anchorOffset, focusOffset } } @@ -93,7 +94,7 @@ class SelectionChangeObserver { update() { let prevSelection = this.selection - let curSelection = this.getSelection()! + let curSelection = this.getSelection(this.editor.root)! if (!this.selectionIsEqual(prevSelection, curSelection)) { this.selection = curSelection this.notifyListeners(curSelection, prevSelection) diff --git a/src/js/utils/cursor.ts b/src/js/utils/cursor.ts index dd8f5def1..0c0b9ccd8 100644 --- a/src/js/utils/cursor.ts +++ b/src/js/utils/cursor.ts @@ -11,6 +11,7 @@ import { unwrap, assertNotNull, expect } from './assert' import { isCardSection } from '../models/card' import Section from '../models/_section' import { Type } from '../models/types' +import Browser from './browser'; export { Position, Range } @@ -26,7 +27,7 @@ class Cursor { } clearSelection() { - clearSelection() + clearSelection(this.editor.root) } /** @@ -138,7 +139,7 @@ class Cursor { } get selection() { - return expect(window.getSelection(), 'expected window selection to not be null') + return expect(this.editor.root.getSelection(), 'expected editor.root selection to not be null') } selectedText() { @@ -175,7 +176,28 @@ class Cursor { _hasSelection() { const element = unwrap(this.editor.element) const { _selectionRange } = this - if (!_selectionRange || _selectionRange.collapsed) { + + // COMPAT: There's a bug in chrome that always returns `true` for + // `isCollapsed` for a Selection that comes from a ShadowRoot. + // (2020/08/08) + // https://bugs.chromium.org/p/chromium/issues/detail?id=447523 + let isCollapsed; + const hasShadowRoot = () => { + return !!( + window.document.activeElement && window.document.activeElement.shadowRoot + ) + } + if (_selectionRange) { + if (Browser.isChrome() && hasShadowRoot()) { + isCollapsed = + _selectionRange.anchorNode === _selectionRange.focusNode && + _selectionRange.anchorOffset === _selectionRange.focusOffset + } else { + isCollapsed = _selectionRange.isCollapsed + } + } + + if (!_selectionRange || isCollapsed) { return false } diff --git a/src/js/utils/selection-utils.ts b/src/js/utils/selection-utils.ts index 9dc338e8c..8844f4f4f 100644 --- a/src/js/utils/selection-utils.ts +++ b/src/js/utils/selection-utils.ts @@ -2,8 +2,8 @@ import { Direction } from './key' import { isTextNode, isElementNode } from './dom-utils' import { assertNotNull } from './assert' -export function clearSelection() { - const selection = window.getSelection() +export function clearSelection(root = window) { + const selection = root.getSelection() selection && selection.removeAllRanges() }