Skip to content

Commit

Permalink
Shadow DOM support and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan committed Oct 2, 2018
1 parent 96e38e9 commit 2186896
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 12 deletions.
29 changes: 23 additions & 6 deletions core/emitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import logger from './logger';
let debug = logger('quill:events');

const EVENTS = ['selectionchange', 'mousedown', 'mouseup', 'click'];
const EMITTERS = [];
const supportsRootNode = ('getRootNode' in document);

EVENTS.forEach(function(eventName) {
document.addEventListener(eventName, (...args) => {
[].slice.call(document.querySelectorAll('.ql-container')).forEach((node) => {
// TODO use WeakMap
if (node.__quill && node.__quill.emitter) {
node.__quill.emitter.handleDOM(...args);
}
EMITTERS.forEach((em) => {
em.handleDOM(...args);
});
});
});
Expand All @@ -21,6 +20,7 @@ class Emitter extends EventEmitter {
constructor() {
super();
this.listeners = {};
EMITTERS.push(this);
this.on('error', debug.error);
}

Expand All @@ -30,8 +30,25 @@ class Emitter extends EventEmitter {
}

handleDOM(event, ...args) {
const target = (event.composedPath ? event.composedPath()[0] : event.target);
const containsNode = (node, target) => {
if (!supportsRootNode || target.getRootNode() === document) {
return node.contains(target);
}

while (!node.contains(target)) {
const root = target.getRootNode();
if (!root || !root.host) {
return false;
}
target = root.host;
}

return true;
};

(this.listeners[event.type] || []).forEach(function({ node, handler }) {
if (event.target === node || node.contains(event.target)) {
if (target === node || containsNode(node, target)) {
handler(event, ...args);
}
});
Expand Down
9 changes: 5 additions & 4 deletions core/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ class Selection {
this.composing = false;
this.mouseDown = false;
this.root = this.scroll.domNode;
this.rootDocument = (this.root.getRootNode ? this.root.getRootNode() : document);
this.cursor = Parchment.create('cursor', this);
// savedRange is last non-null range
this.lastRange = this.savedRange = new Range(0, 0);
this.handleComposition();
this.handleDragging();
this.emitter.listenDOM('selectionchange', document, () => {
this.emitter.listenDOM('selectionchange', this.rootDocument, () => {
if (!this.mouseDown) {
setTimeout(this.update.bind(this, Emitter.sources.USER), 1);
}
Expand Down Expand Up @@ -157,7 +158,7 @@ class Selection {
}

getNativeRange() {
let selection = document.getSelection();
let selection = this.rootDocument.getSelection();
if (selection == null || selection.rangeCount <= 0) return null;
let nativeRange = selection.getRangeAt(0);
if (nativeRange == null) return null;
Expand All @@ -174,7 +175,7 @@ class Selection {
}

hasFocus() {
return document.activeElement === this.root;
return this.rootDocument.activeElement === this.root;
}

normalizedToRange(range) {
Expand Down Expand Up @@ -268,7 +269,7 @@ class Selection {
if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
return;
}
let selection = document.getSelection();
let selection = this.rootDocument.getSelection();
if (selection == null) return;
if (startNode != null) {
if (!this.hasFocus()) this.root.focus();
Expand Down
5 changes: 3 additions & 2 deletions modules/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

const supportsRootNode = ('getRootNode' in document);
let debug = logger('quill:toolbar');


class Toolbar extends Module {
constructor(quill, options) {
super(quill, options);
Expand All @@ -16,7 +16,8 @@ class Toolbar extends Module {
quill.container.parentNode.insertBefore(container, quill.container);
this.container = container;
} else if (typeof this.options.container === 'string') {
this.container = document.querySelector(this.options.container);
const rootDocument = (supportsRootNode ? quill.container.getRootNode() : document);
this.container = rootDocument.querySelector(this.options.container);
} else {
this.container = this.options.container;
}
Expand Down
41 changes: 41 additions & 0 deletions test/unit/core/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,47 @@ describe('Selection', function() {
});
});

describe('shadow root', function() {
// Some browsers don't support shadow DOM
if (!document.head.attachShadow) {
return;
}

let container;
let root;

beforeEach(function() {
root = document.createElement('div');
root.attachShadow({ mode: 'open' });
root.shadowRoot.innerHTML = '<div></div>';

document.body.appendChild(root);

container = root.shadowRoot.firstChild;
});

afterEach(function() {
document.body.removeChild(root);
});

it('getRange()', function() {
let selection = this.initialize(Selection, '<p>0123</p>', container);
selection.setNativeRange(container.firstChild.firstChild, 1);
let [range, ] = selection.getRange();
expect(range.index).toEqual(1);
expect(range.length).toEqual(0);
});

it('setRange()', function() {
let selection = this.initialize(Selection, '', container);
let expected = new Range(0);
selection.setRange(expected);
let [range, ] = selection.getRange();
expect(range).toEqual(expected);
expect(selection.hasFocus()).toBe(true);
});
});

describe('getRange()', function() {
it('empty document', function() {
let selection = this.initialize(Selection, '');
Expand Down
32 changes: 32 additions & 0 deletions test/unit/modules/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,38 @@ describe('Toolbar', function() {
});
});

describe('shadow dom', function() {
// Some browsers don't support shadow DOM
if (!document.head.attachShadow) {
return;
}

let container;
let editor;

beforeEach(function() {
container = document.createElement('div');
container.attachShadow({ mode: 'open' });
container.shadowRoot.innerHTML = `
<div class="editor"></div>
<div class="toolbar"></div>`;

editor = new Quill(container.shadowRoot.querySelector('.editor'), {
modules: {
toolbar: '.toolbar'
}
});
});

it('should initialise', function() {
const editorDiv = container.shadowRoot.querySelector('.editor');
const toolbarDiv = container.shadowRoot.querySelector('.toolbar');
expect(editorDiv.className).toBe('editor ql-container');
expect(toolbarDiv.className).toBe('toolbar ql-toolbar');
expect(editor.container).toBe(editorDiv);
});
});

describe('active', function() {
beforeEach(function() {
let container = this.initialize(HTMLElement, `
Expand Down

0 comments on commit 2186896

Please sign in to comment.