UNPKG

@mirawision/copily

Version:

A comprehensive clipboard manipulation library for TypeScript, providing functionalities for copying/pasting text, HTML, JSON, images, files, and smart content detection.

248 lines (247 loc) 9.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.listenCopy = exports.listenPaste = exports.interceptPaste = exports.interceptCopy = exports.pasteSmart = void 0; const utils_1 = require("./utils"); const core_1 = require("./core"); /** * Intelligently detect clipboard content and return structured results. * * Aggregates multiple clipboard reads (text, HTML, image, file) and classifies * the content into typed results like `otp`, `url`, `email`, `json`, `html`, * `text`, `image`, and `file`. * * @returns Resolves to an array of detected clipboard items. * @example * const items = await pasteSmart(); */ async function pasteSmart() { const results = []; try { // Try to get text content const text = await (0, core_1.pasteText)(); if (text.trim()) { // Check for OTP if ((0, utils_1.isOTP)(text)) { results.push({ type: 'otp', value: text.trim() }); } // Check for URL if ((0, utils_1.isURL)(text)) { results.push({ type: 'url', value: text.trim() }); } // Check for email if ((0, utils_1.isEmail)(text)) { results.push({ type: 'email', value: text.trim() }); } // Check for JSON if ((0, utils_1.isJSON)(text)) { try { const jsonValue = JSON.parse(text); results.push({ type: 'json', value: jsonValue }); } catch { // Ignore JSON parsing errors } } // Always include as text if not already categorized if (results.length === 0 || results.every(r => r.type !== 'text')) { results.push({ type: 'text', value: text }); } } // Try to get HTML content const html = await (0, core_1.pasteHTML)(); if (html && html !== text) { results.push({ type: 'html', value: html }); } // Try to get image const image = await (0, core_1.pasteImage)(); if (image) { results.push({ type: 'image', blob: image }); } // Try to get file const file = await (0, core_1.pasteFile)(); if (file) { results.push({ type: 'file', file }); } } catch (error) { // Return empty array on error return []; } return results; } exports.pasteSmart = pasteSmart; /** * Intercept native `copy` events and optionally modify clipboard data. * * Use the handler return value to prevent the default or override text/HTML * placed on the clipboard. * * @param handler Function invoked on each `copy` event. * @returns Cleanup function to remove the listener. * @example * const off = interceptCopy(({ selection }) => ({ overrideText: selection.trim() })); */ function interceptCopy(handler) { const originalHandler = (event) => { const selection = window.getSelection()?.toString() || ''; const target = event.target; const result = handler({ event, selection, target }); if (result.prevent) { event.preventDefault(); return; } if (result.overrideText || result.overrideHTML) { event.preventDefault(); if (result.overrideText) { event.clipboardData?.setData('text/plain', result.overrideText); } if (result.overrideHTML) { event.clipboardData?.setData('text/html', result.overrideHTML); } } }; document.addEventListener('copy', originalHandler); // Return cleanup function return () => { document.removeEventListener('copy', originalHandler); }; } exports.interceptCopy = interceptCopy; /** * Intercept native `paste` events and optionally modify inserted content. * * The handler can prevent the default paste, or override with custom * plain text or HTML content. Text overrides are inserted into inputs, * textareas, and contenteditable elements; HTML overrides are inserted for * contenteditable contexts when possible. * * @param handler Function invoked on each `paste` event. * @returns Cleanup function to remove the listener. * @example * const off = interceptPaste(() => ({ overrideText: 'plain text only' })); */ function interceptPaste(handler) { const originalHandler = (event) => { const target = event.target; const plainText = event.clipboardData?.getData('text/plain') || ''; const html = event.clipboardData?.getData('text/html') || ''; const result = handler({ event, plainText, html: html || undefined, target }); if (result.prevent) { event.preventDefault(); return; } if (result.overrideText || result.overrideHTML) { event.preventDefault(); const stripTags = (value) => value.replace(/<[^>]*>/g, ''); // If input/textarea, insert text override (or textified HTML) if (target && (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) { const input = target; const insertion = result.overrideText ?? (result.overrideHTML ? stripTags(result.overrideHTML) : ''); const start = input.selectionStart ?? input.value.length; const end = input.selectionEnd ?? start; input.setRangeText(insertion, start, end, 'end'); input.dispatchEvent(new Event('input', { bubbles: true })); return; } // For contenteditable/others, prefer HTML insertion then text if (result.overrideHTML) { try { // execCommand path (still broadly supported) if (typeof document.execCommand === 'function') { document.execCommand('insertHTML', false, result.overrideHTML); return; } } catch { } const selection = window.getSelection(); const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; if (range) { range.deleteContents(); const temp = document.createElement('div'); temp.innerHTML = result.overrideHTML; const frag = document.createDocumentFragment(); while (temp.firstChild) frag.appendChild(temp.firstChild); range.insertNode(frag); } return; } if (result.overrideText) { try { if (typeof document.execCommand === 'function') { document.execCommand('insertText', false, result.overrideText); return; } } catch { } const selection = window.getSelection(); const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; if (range) { range.deleteContents(); range.insertNode(document.createTextNode(result.overrideText)); } } } }; document.addEventListener('paste', originalHandler); return () => { document.removeEventListener('paste', originalHandler); }; } exports.interceptPaste = interceptPaste; /** * Listen to `paste` events and emit smart-detected clipboard data. * * Internally calls {@link pasteSmart} on paste and passes the results to the * provided callback. * * @param callback Receives the array of detected clipboard results. * @returns Cleanup function to remove the listener. */ function listenPaste(callback) { const handler = async (_event) => { try { const results = await pasteSmart(); callback(results); } catch (error) { // Call callback with empty array on error callback([]); } }; document.addEventListener('paste', handler); // Return unsubscribe function return () => { document.removeEventListener('paste', handler); }; } exports.listenPaste = listenPaste; /** * Listen to `copy` events and emit selection details. * * Provides the raw event, the current text selection, and the event target. * * @param callback Receives `{ event, selection, target }` on each copy. * @returns Cleanup function to remove the listener. */ function listenCopy(callback) { const handler = (event) => { const selection = window.getSelection()?.toString() || ''; const target = event.target ?? null; callback({ event, selection, target }); }; document.addEventListener('copy', handler); return () => { document.removeEventListener('copy', handler); }; } exports.listenCopy = listenCopy;