import Emitter from 'tiny-emitter'; import listen from 'good-listener'; import ClipboardActionDefault from './actions/default'; import ClipboardActionCut from './actions/cut'; import ClipboardActionCopy from './actions/copy'; /** * Helper function to retrieve attribute value. * @param {String} suffix * @param {Element} element */ function getAttributeValue(suffix, element) { const attribute = `data-clipboard-${suffix}`; if (!element.hasAttribute(attribute)) { return; } return element.getAttribute(attribute); } /** * Base class which takes one or more elements, adds event listeners to them, * and instantiates a new `ClipboardAction` on each click. */ class Clipboard extends Emitter { /** * @param {String|HTMLElement|HTMLCollection|NodeList} trigger * @param {Object} options */ constructor(trigger, options) { super(); this.resolveOptions(options); this.listenClick(trigger); } /** * Defines if attributes would be resolved using internal setter functions * or custom functions that were passed in the constructor. * @param {Object} options */ resolveOptions(options = {}) { this.action = typeof options.action === 'function' ? options.action : this.defaultAction; this.target = typeof options.target === 'function' ? options.target : this.defaultTarget; this.text = typeof options.text === 'function' ? options.text : this.defaultText; this.container = typeof options.container === 'object' ? options.container : document.body; } /** * Adds a click event listener to the passed trigger. * @param {String|HTMLElement|HTMLCollection|NodeList} trigger */ listenClick(trigger) { this.listener = listen(trigger, 'click', (e) => this.onClick(e)); } /** * Defines a new `ClipboardAction` on each click event. * @param {Event} e */ onClick(e) { const trigger = e.delegateTarget || e.currentTarget; const action = this.action(trigger) || 'copy'; const text = ClipboardActionDefault({ action, container: this.container, target: this.target(trigger), text: this.text(trigger), }); // Fires an event based on the copy operation result. this.emit(text ? 'success' : 'error', { action, text, trigger, clearSelection() { if (trigger) { trigger.focus(); } window.getSelection().removeAllRanges(); }, }); } /** * Default `action` lookup function. * @param {Element} trigger */ defaultAction(trigger) { return getAttributeValue('action', trigger); } /** * Default `target` lookup function. * @param {Element} trigger */ defaultTarget(trigger) { const selector = getAttributeValue('target', trigger); if (selector) { return document.querySelector(selector); } } /** * Allow fire programmatically a copy action * @param {String|HTMLElement} target * @param {Object} options * @returns Text copied. */ static copy(target, options = { container: document.body }) { return ClipboardActionCopy(target, options); } /** * Allow fire programmatically a cut action * @param {String|HTMLElement} target * @returns Text cutted. */ static cut(target) { return ClipboardActionCut(target); } /** * Returns the support of the given action, or all actions if no action is * given. * @param {String} [action] */ static isSupported(action = ['copy', 'cut']) { const actions = typeof action === 'string' ? [action] : action; let support = !!document.queryCommandSupported; actions.forEach((action) => { support = support && !!document.queryCommandSupported(action); }); return support; } /** * Default `text` lookup function. * @param {Element} trigger */ defaultText(trigger) { return getAttributeValue('text', trigger); } /** * Destroy lifecycle. */ destroy() { this.listener.destroy(); } } export default Clipboard;