// saladui/core/animation-utils.js /** * Animation utilities for SaladUI framework components * Provides functions to handle animations and transitions */ /** * Animation handler for state transitions * * @param {Object} animConfig - Animation configuration object * @param {HTMLElement} targetElement - Element to animate * @returns {Promise} A Promise that resolves when animation completes */ export function animateTransition(animConfig, targetElement) { if (!animConfig || !targetElement) { return Promise.resolve(); // Return resolved promise if no animation or target } const { animation, duration = 200 } = animConfig; // Process animation classes const animationClasses = (animation || ["", "", ""]).map((item) => typeof item === "string" ? item.split(/\s+/) : [], ); // Execute animation with promise return executeAnimation(targetElement, { animation: animationClasses, duration, }); } /** * Execute animation sequence on target element * * @param {HTMLElement} targetElement - Element to animate * @param {Object} animOptions - Animation options * @returns {Promise} Promise that resolves when animation completes */ export function executeAnimation(targetElement, animOptions) { console.log("Animating", targetElement, animOptions); return new Promise((resolve) => { const { animation, duration } = animOptions; let [transitionRun, transitionStart, transitionEnd] = animation || [ [], [], [], ]; // First animation frame: apply start classes addOrRemoveClasses( targetElement, transitionStart, [].concat(transitionRun).concat(transitionEnd), ); // Next frame: apply running classes window.requestAnimationFrame(() => { addOrRemoveClasses(targetElement, transitionRun, []); // Next frame: apply end classes window.requestAnimationFrame(() => addOrRemoveClasses(targetElement, transitionEnd, transitionStart), ); }); // After duration, clean up classes and resolve promise setTimeout(() => { addOrRemoveClasses( targetElement, [], [].concat(transitionRun).concat(transitionStart).concat(transitionEnd), ); resolve(); }, duration); }); } /** * Add and remove CSS classes from a target element * * @param {HTMLElement} targetElement - Element to modify classes on * @param {Array} addClasses - Classes to add * @param {Array} removeClasses - Classes to remove */ export function addOrRemoveClasses( targetElement, addClasses = [], removeClasses = [], ) { if (!targetElement) return; if (addClasses.length > 0) { targetElement.classList.add(...addClasses.filter(Boolean)); } if (removeClasses.length > 0) { targetElement.classList.remove(...removeClasses.filter(Boolean)); } } /** * Filter result constants * @enum {number} */ const FilterResult = { IGNORE_AND_CONTINUE: 0, // Ignore node but traverse children SELECT_AND_CONTINUE: 1, // Select node and traverse children IGNORE_AND_SKIP: -1, // Ignore node and skip children }; /** * Custom DOM query selector with advanced filtering capabilities * * @param {Node} root - Starting DOM node * @param {Function} filterFunction - Function returning a FilterResult value * @param {Object} [options] - Optional settings * @param {boolean} [options.breadthFirst=true] - Use breadth-first (true) or depth-first (false) * @returns {Element[]} Matching elements */ export function queryDOM( root, filterFunction, options = { breadthFirst: true }, ) { // Validate inputs if (!(root instanceof Node)) throw new TypeError("Root must be a DOM node"); if (typeof filterFunction !== "function") throw new TypeError("Filter must be a function"); const result = []; const nodes = [...root.children]; const getNext = options.breadthFirst !== false ? () => nodes.shift() : () => nodes.pop(); while (nodes.length > 0) { const current = getNext(); // Skip non-element nodes if (!(current instanceof Element)) continue; // Process based on filter result const filterResult = filterFunction(current); if (filterResult === FilterResult.SELECT_AND_CONTINUE) { result.push(current); addChildren(current, nodes); } else if (filterResult === FilterResult.IGNORE_AND_CONTINUE) { addChildren(current, nodes); } // IGNORE_AND_SKIP: do nothing } return result; } /** * Add element's children to the traversal collection */ function addChildren(element, collection) { for (let i = 0; i < element.children.length; i++) { collection.push(element.children[i]); } }