106 lines
2.8 KiB
JavaScript
106 lines
2.8 KiB
JavaScript
import { trimRange, rangeToSelection, enableTouch } from './SelectionUtils';
|
|
import EventEmitter from 'tiny-emitter';
|
|
|
|
const IS_TOUCH = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
|
|
export default class SelectionHandler extends EventEmitter {
|
|
|
|
constructor(element, highlighter, readOnly) {
|
|
super();
|
|
|
|
this.el = element;
|
|
this.highlighter = highlighter;
|
|
this.readOnly = readOnly;
|
|
|
|
this.isEnabled = true;
|
|
|
|
element.addEventListener('mousedown', this._onMouseDown);
|
|
element.addEventListener('mouseup', this._onMouseUp);
|
|
|
|
if (IS_TOUCH)
|
|
enableTouch(element, this._onMouseUp);
|
|
}
|
|
|
|
get enabled() {
|
|
return this.isEnabled;
|
|
}
|
|
|
|
set enabled(enabled) {
|
|
this.isEnabled = enabled;
|
|
}
|
|
|
|
_onMouseDown = evt => {
|
|
this.clearSelection();
|
|
}
|
|
|
|
_onMouseUp = evt => {
|
|
if (this.isEnabled) {
|
|
const selection = getSelection();
|
|
|
|
if (selection.isCollapsed) {
|
|
const annotationSpan = evt.target.closest('.r6o-annotation');
|
|
if (annotationSpan) {
|
|
this.emit('select', {
|
|
selection: this.highlighter.getAnnotationsAt(annotationSpan)[0],
|
|
element: annotationSpan
|
|
});
|
|
} else {
|
|
// De-select
|
|
this.emit('select', {});
|
|
}
|
|
} else if (!this.readOnly) {
|
|
const selectedRange = trimRange(selection.getRangeAt(0));
|
|
|
|
// Make sure the selection is entirely inside this.el
|
|
const { commonAncestorContainer } = selectedRange;
|
|
|
|
if (this.el.contains(commonAncestorContainer)) {
|
|
const stub = rangeToSelection(selectedRange, this.el);
|
|
|
|
const spans = this.highlighter.wrapRange(selectedRange);
|
|
spans.forEach(span => span.className = 'r6o-selection');
|
|
|
|
this._hideNativeSelection();
|
|
|
|
this.emit('select', {
|
|
selection: stub,
|
|
element: selectedRange
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_hideNativeSelection = () => {
|
|
this.el.classList.add('hide-selection');
|
|
}
|
|
|
|
clearSelection = () => {
|
|
this._currentSelection = null;
|
|
|
|
// Remove native selection, if any
|
|
if (window.getSelection) {
|
|
if (window.getSelection().empty) { // Chrome
|
|
window.getSelection().empty();
|
|
} else if (window.getSelection().removeAllRanges) { // Firefox
|
|
window.getSelection().removeAllRanges();
|
|
}
|
|
} else if (document.selection) { // IE?
|
|
document.selection.empty();
|
|
}
|
|
|
|
this.el.classList.remove('hide-selection');
|
|
|
|
const spans = Array.prototype.slice.call(this.el.querySelectorAll('.r6o-selection'));
|
|
if (spans) {
|
|
spans.forEach(span => {
|
|
const parent = span.parentNode;
|
|
parent.insertBefore(document.createTextNode(span.textContent), span);
|
|
parent.removeChild(span);
|
|
});
|
|
}
|
|
this.el.normalize();
|
|
}
|
|
|
|
}
|