UNPKG

highlight-share

Version:

Medium-like text selection sharing without dependencies

140 lines (110 loc) 4.15 kB
import { stylePopover, lifeCycleFactory } from "./popover"; import { constrainRange } from "./selection"; import { extend, isCallable } from "./utils"; import render from "./render"; let _undefined; const eventTypes = [ "selectionchange", "mouseup", "touchend", "touchcancel" ]; export default (opts) => { const options = (Object.assign || extend)({ document, selector: "body", sharers: [], popoverClass: "highlight-share-popover", transformer: raw => raw.trim().replace(/\s+/g, " ") }, opts || {}); let initialized = false; let destroyed = false; let _document = _undefined; let _window = _undefined; let popover = _undefined; let lifeCycle = _undefined; return { init() { if (initialized) return false; _document = options.document; _window = _document.defaultView; if (!_window.getSelection) { // eslint-disable-next-line no-console console.warn("highlight-share: Selection API isn't supported"); return false; } eventTypes.forEach(addListener); _window.addEventListener("resize", resizeHandler); lifeCycle = lifeCycleFactory(_document); return initialized = true; }, destroy() { if (!initialized || destroyed) return false; eventTypes.forEach(removeListener); _window.removeEventListener("resize", resizeHandler); killPopover(); _document = _undefined; _window = _undefined; return destroyed = true; } }; function addListener(type) { _document.addEventListener(type, selectionCheck); } function removeListener(type) { _document.removeEventListener(type, selectionCheck); } function resizeHandler() { if (popover) { stylePopover(popover, getConstrainedRange(), options); } } function selectionCheck({ type }) { const shouldHavePopover = type === "selectionchange"; if (!popover !== shouldHavePopover) { // Safari iOS fires selectionchange *before* click, so tapping on a sharer would be prevented setTimeout(() => { const range = getConstrainedRange(); if (range) drawPopover(range); else killPopover(); }, 10); } } function getConstrainedRange() { const selection = _window.getSelection(); const range = selection.rangeCount && selection.getRangeAt(0); if (!range) return; const constrainedRange = constrainRange(range, options.selector); if (constrainedRange.collapsed || !constrainedRange.getClientRects().length) return; // eslint-disable-next-line consistent-return return constrainedRange; } function drawPopover(range) { const toBeOpened = !popover; const rawText = range.toString(); const text = options.transformer(rawText); const sharers = options.sharers.filter(sharerCheck.bind(null, text, rawText)); if (!sharers.length) { if (popover) killPopover(); return; } if (toBeOpened) popover = lifeCycle.createPopover(); popover.sharers = sharers; popover.innerHTML = render(options, sharers, text, rawText); stylePopover(popover, range, options); if (!toBeOpened) return; lifeCycle.attachPopover(popover); if (isCallable(options.onOpen)) { options.onOpen(popover, text, rawText); } } function killPopover() { if (!popover) return; lifeCycle.removePopover(popover); popover = _undefined; if (isCallable(options.onClose)) { options.onClose(); } } function sharerCheck(text, rawText, sharer) { const active = sharer.active; if (isCallable(active)) { return active(text, rawText); } if (active !== _undefined) { return active; } return true; } };