UNPKG

@casoon/dragonfly

Version:

Modular, lightweight CSS framework and design system for modern web projects. Optimized for Astro JS, LightningCSS and Container Queries with @layer-based architecture and comprehensive accessibility.

266 lines (229 loc) 7.58 kB
/** * DOM Helper Functions * * Utilities for DOM manipulation, file downloads, and browser interactions. */ /** * Download file from URL * * Enables quick, user-friendly downloads without opening new tabs. * Works with both URLs and data URIs. * * @param {string} url - URL or data URI of the file * @param {string} filename - Desired filename for download * @param {Object} options - Download options * @param {boolean} options.newTab - Open in new tab instead of direct download * @returns {boolean} True if download was initiated * * @example * // Download PDF * downloadFile('/resume.pdf', 'MyResume.pdf'); * * @example * // Download data as file * const data = JSON.stringify(userData, null, 2); * const dataUri = `data:application/json;charset=utf-8,${encodeURIComponent(data)}`; * downloadFile(dataUri, 'user-data.json'); */ export function downloadFile(url, filename, options = {}) { const { newTab = false } = options; if (!url || !filename) { console.warn('downloadFile: URL and filename are required'); return false; } try { if (newTab) { window.open(url, '_blank'); return true; } const a = document.createElement('a'); a.href = url; a.download = filename; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); return true; } catch (error) { console.error('Download failed:', error); return false; } } /** * Download text content as file * * @param {string} content - Text content to download * @param {string} filename - Filename for download * @param {string} mimeType - MIME type (default: 'text/plain') * @returns {boolean} True if download was initiated * * @example * downloadTextAsFile('Hello World!', 'greeting.txt'); * downloadTextAsFile(JSON.stringify(data), 'data.json', 'application/json'); */ export function downloadTextAsFile(content, filename, mimeType = 'text/plain') { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const success = downloadFile(url, filename); // Clean up object URL setTimeout(() => URL.revokeObjectURL(url), 100); return success; } /** * Check if element is in viewport * * @param {Element} element - DOM element to check * @param {number} threshold - Percentage of element that must be visible (0-1) * @returns {boolean} True if element is in viewport * * @example * if (isInViewport(element)) { * // Element is visible, start animation * } */ export function isInViewport(element, threshold = 0) { if (!element || !element.getBoundingClientRect) return false; const rect = element.getBoundingClientRect(); const windowHeight = window.innerHeight || document.documentElement.clientHeight; const windowWidth = window.innerWidth || document.documentElement.clientWidth; const verticalVisible = rect.top < windowHeight && rect.bottom > 0; const horizontalVisible = rect.left < windowWidth && rect.right > 0; if (!verticalVisible || !horizontalVisible) return false; if (threshold === 0) return true; const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0); const visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0); const visibleArea = visibleHeight * visibleWidth; const totalArea = rect.height * rect.width; return visibleArea / totalArea >= threshold; } /** * Smooth scroll to element * * @param {Element|string} target - Element or selector to scroll to * @param {Object} options - Scroll options * @param {number} options.offset - Offset from target in pixels * @param {string} options.behavior - Scroll behavior ('smooth', 'auto') * @param {string} options.block - Vertical alignment ('start', 'center', 'end') * @returns {boolean} True if scroll was initiated * * @example * scrollToElement('#section-2', { offset: -100 }); * scrollToElement(document.getElementById('target'), { behavior: 'smooth' }); */ export function scrollToElement(target, options = {}) { const { offset = 0, behavior = 'smooth', block = 'start' } = options; const element = typeof target === 'string' ? document.querySelector(target) : target; if (!element) { console.warn('scrollToElement: Target element not found'); return false; } const elementPosition = element.getBoundingClientRect().top + window.pageYOffset; const offsetPosition = elementPosition + offset; window.scrollTo({ top: offsetPosition, behavior, }); return true; } /** * Get element's computed style property * * @param {Element} element - DOM element * @param {string} property - CSS property name * @returns {string} Computed style value * * @example * const color = getComputedStyle(element, 'color'); * const width = getComputedStyle(element, 'width'); */ export function getComputedStyleProperty(element, property) { if (!element || !property) return ''; const computed = window.getComputedStyle(element); return computed.getPropertyValue(property); } /** * Wait for element to appear in DOM * * @param {string} selector - CSS selector * @param {number} timeout - Timeout in milliseconds (default: 5000) * @returns {Promise<Element>} Promise that resolves with element * * @example * const modal = await waitForElement('.modal'); * modal.focus(); */ export function waitForElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { const element = document.querySelector(selector); if (element) { resolve(element); return; } const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { observer.disconnect(); resolve(element); } }); observer.observe(document.body, { childList: true, subtree: true, }); setTimeout(() => { observer.disconnect(); reject(new Error(`Element ${selector} not found within ${timeout}ms`)); }, timeout); }); } /** * Create and dispatch custom event * * @param {string} eventName - Name of the event * @param {*} detail - Event detail data * @param {Element} target - Target element (default: document) * @returns {boolean} True if event was dispatched * * @example * dispatchCustomEvent('user:login', { userId: 123 }); * dispatchCustomEvent('modal:close', null, modalElement); */ export function dispatchCustomEvent(eventName, detail = null, target = document) { if (!eventName) return false; try { const event = new CustomEvent(eventName, { detail, bubbles: true, cancelable: true, }); return target.dispatchEvent(event); } catch (error) { console.error('Failed to dispatch custom event:', error); return false; } } /** * Get element's position relative to page * * @param {Element} element - DOM element * @returns {Object} Object with x, y, width, height properties * * @example * const pos = getElementPosition(button); * console.log(`Button is at ${pos.x}, ${pos.y}`); */ export function getElementPosition(element) { if (!element || !element.getBoundingClientRect) { return { x: 0, y: 0, width: 0, height: 0 }; } const rect = element.getBoundingClientRect(); const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; return { x: rect.left + scrollLeft, y: rect.top + scrollTop, width: rect.width, height: rect.height, }; } export { downloadFile as default };