UNPKG

@rxxuzi/gumi

Version:

Clean & minimal design system with delightful interactions

178 lines 5.93 kB
// code-copy.ts // Code block copy functionality import * as dom from '../core/dom'; import { Toast } from './toast'; export class CodeCopy { constructor(element, options = {}) { this.isVisible = false; this.element = dom.$(element); if (!this.element) { throw new Error('Code element not found'); } this.options = { copyText: 'Copy', successText: 'Copied!', errorText: 'Failed to copy', showToast: true, buttonClass: 'code-copy-btn', iconClass: 'copy-icon', ...options }; this.init(); } init() { // Ensure the code block is positioned relatively const computedStyle = window.getComputedStyle(this.element); if (computedStyle.position === 'static') { this.element.style.position = 'relative'; } this.createButton(); this.bindEvents(); } createButton() { this.button = dom.createElement('button', { className: this.options.buttonClass, attributes: { title: this.options.copyText, 'aria-label': this.options.copyText }, html: ` <svg class="${this.options.iconClass}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> <path d="m5 15-1-1V6a2 2 0 0 1 2-2h8"></path> </svg> ` }); this.element.appendChild(this.button); // Initially hide the button this.button.style.opacity = '0'; this.button.style.visibility = 'hidden'; } bindEvents() { // Show/hide button on hover dom.on(this.element, 'mouseenter', () => { this.showButton(); }); dom.on(this.element, 'mouseleave', () => { this.hideButton(); }); // Copy functionality dom.on(this.button, 'click', (e) => { e.preventDefault(); e.stopPropagation(); this.copyCode(); }); } showButton() { if (this.isVisible) return; this.isVisible = true; this.button.style.visibility = 'visible'; this.button.style.opacity = '1'; } hideButton() { if (!this.isVisible) return; this.isVisible = false; this.button.style.opacity = '0'; // Hide after transition setTimeout(() => { if (!this.isVisible) { this.button.style.visibility = 'hidden'; } }, 200); } async copyCode() { try { // Get the text content, excluding the copy button const textToCopy = this.getCodeText(); if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(textToCopy); } else { // Fallback for older browsers this.fallbackCopy(textToCopy); } this.showSuccess(); } catch (error) { this.showError(); } } getCodeText() { // Clone the element to avoid modifying the original const clone = this.element.cloneNode(true); // Remove the copy button from the clone const button = clone.querySelector(`.${this.options.buttonClass}`); if (button) { button.remove(); } // Get text content, preserving line breaks return clone.textContent || clone.innerText || ''; } fallbackCopy(text) { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); } finally { document.body.removeChild(textArea); } } showSuccess() { // Just add success class, keep the same icon dom.addClass(this.button, 'success'); // Show toast if enabled if (this.options.showToast) { Toast.show(this.options.successText, { type: 'success', duration: 2000 }); } // Reset after 2 seconds setTimeout(() => { dom.removeClass(this.button, 'success'); }, 2000); } showError() { // Just add error class, keep the same icon dom.addClass(this.button, 'error'); // Show toast if enabled if (this.options.showToast) { Toast.show(this.options.errorText, { type: 'error', duration: 2000 }); } // Reset after 2 seconds setTimeout(() => { dom.removeClass(this.button, 'error'); }, 2000); } destroy() { if (this.button) { this.button.remove(); } } static initAll(selector = 'pre, code, .code-block') { const elements = dom.$$(selector); const instances = []; elements.forEach(element => { var _a; // Skip if already initialized if (element.__gumi_code_copy) return; // Skip inline code elements (only process block code) if (element.tagName.toLowerCase() === 'code' && ((_a = element.parentElement) === null || _a === void 0 ? void 0 : _a.tagName.toLowerCase()) !== 'pre') { return; } const instance = new CodeCopy(element); element.__gumi_code_copy = instance; instances.push(instance); }); return instances; } } //# sourceMappingURL=code-copy.js.map