@openstax/highlighter
Advanced tools
Comparing version 1.6.3 to 1.7.0-alpha1
import Highlight, { IOptions as HighlightOptions } from './Highlight'; | ||
import SerializedHighlight from './SerializedHighlight'; | ||
export declare const ON_SELECT_DELAY = 500; | ||
interface IOptions { | ||
@@ -16,2 +17,4 @@ snapTableRows?: boolean; | ||
private options; | ||
private selectionTimeout; | ||
private previousRange; | ||
constructor(container: HTMLElement, options?: IOptions); | ||
@@ -32,6 +35,8 @@ unmount(): void; | ||
readonly document: Document; | ||
private onMouseup; | ||
private onSelectionChange; | ||
private onClickHandler; | ||
private onClick; | ||
private onSelect; | ||
private compareRanges; | ||
} | ||
export {}; |
@@ -10,5 +10,8 @@ "use strict"; | ||
const SerializedHighlight_1 = require("./SerializedHighlight"); | ||
exports.ON_SELECT_DELAY = 500; | ||
class Highlighter { | ||
constructor(container, options = {}) { | ||
this.highlights = {}; | ||
this.selectionTimeout = null; | ||
this.previousRange = null; | ||
this.eraseAll = () => { | ||
@@ -25,20 +28,34 @@ this.getHighlights().forEach(this.erase); | ||
}; | ||
this.onMouseup = (ev) => { | ||
this.onSelectionChange = () => { | ||
const selection = this.document.getSelection(); | ||
if (!selection) { | ||
if (!selection | ||
|| selection.isCollapsed | ||
|| selection.type === 'None' | ||
|| !dom_1.default(this.container).contains(selection.anchorNode) | ||
|| !dom_1.default(this.container).contains(selection.focusNode) | ||
|| this.compareRanges(selection ? selection_1.getRange(selection) : null, this.previousRange)) { | ||
return; | ||
} | ||
if (selection.isCollapsed) { | ||
this.onClick(ev); | ||
if (this.selectionTimeout) { | ||
clearTimeout(this.selectionTimeout); | ||
} | ||
else { | ||
this.onSelect(selection); | ||
} | ||
this.selectionTimeout = setTimeout(() => { | ||
const sel = this.document.getSelection(); | ||
if (!sel) { | ||
return; | ||
} | ||
this.onSelect(sel); | ||
}, exports.ON_SELECT_DELAY); | ||
}; | ||
this.onClickHandler = (event) => { | ||
this.onClick(event); | ||
}; | ||
this.container = container; | ||
this.options = Object.assign({ className: 'highlight' }, options); | ||
this.container.addEventListener('mouseup', this.onMouseup); | ||
this.container.addEventListener('click', this.onClickHandler); | ||
document.addEventListener('selectionchange', this.onSelectionChange); | ||
} | ||
unmount() { | ||
this.container.removeEventListener('mouseup', this.onMouseup); | ||
this.container.removeEventListener('click', this.onClickHandler); | ||
document.removeEventListener('selectionchange', this.onSelectionChange); | ||
} | ||
@@ -120,2 +137,3 @@ highlight(highlight) { | ||
const range = selection_1.snapSelection(selection, this.options); | ||
this.previousRange = range || null; | ||
if (onSelect && range) { | ||
@@ -133,4 +151,24 @@ const highlights = Object.values(this.highlights) | ||
} | ||
compareRanges(range1, range2) { | ||
if (range1 === null && range2 === null) { | ||
return true; | ||
} | ||
if (range1 === null && range2) { | ||
return false; | ||
} | ||
if (range2 === null && range1) { | ||
return false; | ||
} | ||
if (range1 | ||
&& range2 | ||
&& range1.startContainer === range2.startContainer | ||
&& range1.endContainer === range2.endContainer | ||
&& range1.startOffset === range2.startOffset | ||
&& range1.endOffset === range2.endOffset) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
} | ||
exports.default = Highlighter; | ||
//# sourceMappingURL=Highlighter.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Highlight_1 = require("./Highlight"); | ||
const Highlighter_1 = require("./Highlighter"); | ||
const injectHighlightWrappersUtils = require("./injectHighlightWrappers"); | ||
const rangeContents = require("./rangeContents"); | ||
@@ -41,2 +43,45 @@ const selection = require("./selection"); | ||
}); | ||
describe('onClick', () => { | ||
test('handle clicks inside of the container', () => { | ||
const spyOnClick = jest.fn(); | ||
const container = document.createElement('div'); | ||
// tslint:disable-next-line no-unused-expression | ||
new Highlighter_1.default(container, { onClick: spyOnClick }); | ||
const e = document.createEvent('MouseEvent'); | ||
e.initEvent('click', true, true); | ||
container.dispatchEvent(e); | ||
expect(spyOnClick).toHaveBeenCalledWith(undefined, e); | ||
}); | ||
test('handle clicks on the highlights', () => { | ||
const spyOnClick = jest.fn(); | ||
const spyInjectHighlightWrappersUtils = jest.fn(); | ||
const container = document.createElement('div'); | ||
const highlightElement = document.createElement('span'); | ||
highlightElement.setAttribute(injectHighlightWrappersUtils.DATA_ATTR, 'highlight'); | ||
highlightElement.setAttribute(injectHighlightWrappersUtils.DATA_ID_ATTR, 'some-highlight'); | ||
container.append(highlightElement); | ||
const highlight = new Highlight_1.default(new Range(), { id: 'some-highlight', content: 'asd' }); | ||
jest.spyOn(injectHighlightWrappersUtils, 'default') | ||
.mockImplementation(spyInjectHighlightWrappersUtils); | ||
// tslint:disable-next-line no-unused-expression | ||
const highlighter = new Highlighter_1.default(container, { onClick: spyOnClick }); | ||
highlighter.highlight(highlight); | ||
const e = document.createEvent('MouseEvent'); | ||
e.initEvent('click', true, true); | ||
Object.defineProperty(e, 'target', { value: highlightElement }); | ||
container.dispatchEvent(e); | ||
expect(spyInjectHighlightWrappersUtils).toHaveBeenCalledWith(highlight, expect.anything()); | ||
expect(spyOnClick).toHaveBeenCalledWith(highlight, e); | ||
}); | ||
test('does not handle clicks outside of the container', () => { | ||
const spyOnClick = jest.fn(); | ||
const container = document.createElement('div'); | ||
// tslint:disable-next-line no-unused-expression | ||
new Highlighter_1.default(container, { onClick: spyOnClick }); | ||
const e = document.createEvent('MouseEvent'); | ||
e.initEvent('click', true, true); | ||
document.dispatchEvent(e); | ||
expect(spyOnClick).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
describe('onSelect', () => { | ||
@@ -46,2 +91,3 @@ let getSelectionSpy; | ||
let rangeContentsStringSpy; | ||
jest.useFakeTimers(); | ||
beforeEach(() => { | ||
@@ -60,13 +106,25 @@ getSelectionSpy = jest.spyOn(document, 'getSelection'); | ||
let highlight; | ||
const highlighter = new Highlighter_1.default(document.createElement('div'), { onSelect: (_, newHighlight) => highlight = newHighlight }); | ||
const container = document.createElement('div'); | ||
const node = document.createElement('div'); | ||
node.innerHTML = 'some text'; | ||
container.appendChild(node); | ||
// tslint:disable-next-line no-unused-expression | ||
new Highlighter_1.default(container, { onSelect: (_, newHighlight) => highlight = newHighlight }); | ||
const inputSelection = new Selection(); | ||
const selectionRange = new Range(); | ||
const snappedRange = new Range(); | ||
selectionRange.setStart(node, 0); | ||
selectionRange.setEnd(node, 5); | ||
selectionRange.setStart(node, 0); | ||
selectionRange.setEnd(node, 5); | ||
Object.defineProperty(inputSelection, 'isCollapsed', { value: false }); | ||
Object.defineProperty(inputSelection, 'anchorNode', { value: node }); | ||
Object.defineProperty(inputSelection, 'focusNode', { value: node }); | ||
inputSelection.getRangeAt = jest.fn(() => selectionRange); | ||
snapSelectionSpy.mockImplementation(() => snappedRange); | ||
getSelectionSpy.mockImplementation(() => inputSelection); | ||
const e = document.createEvent('MouseEvents'); | ||
e.initEvent('mouseup', true, true); | ||
highlighter.container.dispatchEvent(e); | ||
const e = document.createEvent('Event'); | ||
e.initEvent('selectionchange', true, true); | ||
document.dispatchEvent(e); | ||
jest.runTimersToTime(Highlighter_1.ON_SELECT_DELAY); | ||
if (highlight === undefined) { | ||
@@ -80,3 +138,41 @@ expect(highlight).toBeDefined(); | ||
}); | ||
it('noops on selecitonchange event if there is no selection, selection is collapsed or selection type is None', () => { | ||
const spyOnSelect = jest.fn(); | ||
const container = document.createElement('div'); | ||
const node = document.createElement('div'); | ||
container.appendChild(node); | ||
// tslint:disable-next-line no-unused-expression | ||
new Highlighter_1.default(container, { onSelect: spyOnSelect }); | ||
getSelectionSpy.mockImplementation(() => null); | ||
const e = document.createEvent('Event'); | ||
e.initEvent('selectionchange', true, true); | ||
document.dispatchEvent(e); | ||
jest.runTimersToTime(Highlighter_1.ON_SELECT_DELAY + 100); | ||
expect(spyOnSelect).not.toHaveBeenCalled(); | ||
getSelectionSpy.mockImplementation(() => ({ isCollapsed: true })); | ||
jest.runTimersToTime(Highlighter_1.ON_SELECT_DELAY + 100); | ||
expect(spyOnSelect).not.toHaveBeenCalled(); | ||
getSelectionSpy.mockImplementation(() => ({ isCollapsed: false, type: 'None' })); | ||
jest.runTimersToTime(Highlighter_1.ON_SELECT_DELAY + 100); | ||
expect(spyOnSelect).not.toHaveBeenCalled(); | ||
}); | ||
it('noops on selecitonchange event if anchorNode or focusNode is not in the container', () => { | ||
const spyOnSelect = jest.fn(); | ||
const container = document.createElement('div'); | ||
const nodeInside = document.createElement('div'); | ||
const nodeOutside = document.createElement('div'); | ||
container.appendChild(nodeInside); | ||
// tslint:disable-next-line no-unused-expression | ||
new Highlighter_1.default(container, { onSelect: spyOnSelect }); | ||
getSelectionSpy.mockImplementation(() => ({ isCollapsed: false, anchorNode: nodeOutside, focusNode: nodeInside })); | ||
const e = document.createEvent('Event'); | ||
e.initEvent('selectionchange', true, true); | ||
document.dispatchEvent(e); | ||
jest.runTimersToTime(Highlighter_1.ON_SELECT_DELAY + 100); | ||
expect(spyOnSelect).not.toHaveBeenCalled(); | ||
getSelectionSpy.mockImplementation(() => ({ isCollapsed: false, anchorNode: nodeInside, focusNode: nodeOutside })); | ||
jest.runTimersToTime(Highlighter_1.ON_SELECT_DELAY + 100); | ||
expect(spyOnSelect).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
//# sourceMappingURL=Highlighter.test.js.map |
{ | ||
"name": "@openstax/highlighter", | ||
"version": "1.6.3", | ||
"version": "1.7.0-alpha1", | ||
"main": "dist/index.js", | ||
@@ -71,3 +71,3 @@ "license": "MIT", | ||
"dependencies": { | ||
"@openstax/highlights-client": "0.2.2", | ||
"@openstax/highlights-client": "0.2.1", | ||
"change-case": "^4.0.0", | ||
@@ -74,0 +74,0 @@ "serialize-selection": "^1.1.1", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
177072
2077
2
+ Added@openstax/highlights-client@0.2.1(transitive)
- Removed@openstax/highlights-client@0.2.2(transitive)