UNPKG

@usal/svelte

Version:

Ultimate Scroll Animation Library - Lightweight, powerful, wonderfully simple ✨ | Svelte Package

1,354 lines (1,350 loc) 49.1 kB
"use strict"; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/integrations/svelte.ts var svelte_exports = {}; __export(svelte_exports, { default: () => svelte_default, usal: () => usal, useUSAL: () => useUSAL }); module.exports = __toCommonJS(svelte_exports); // src/usal.js var USAL = (() => { var _a; if (typeof window !== "undefined" && ((_a = window.USAL) == null ? void 0 : _a.initialized())) { return window.USAL; } if (typeof window === "undefined") { const asyncNoop = function() { return __async(this, null, function* () { return this; }); }; return { config: function() { return arguments.length === 0 ? {} : this; }, destroy: asyncNoop, restart: asyncNoop, initialized: () => false, version: "1.3.0" }; } const defaultConfig = { defaults: { animation: "fade", direction: "u", duration: 1e3, delay: 0, threshold: 10, splitDelay: 30, forwards: false, easing: "ease-out", blur: false, loop: "mirror" }, observersDelay: 50, once: false }; const instance = { destroying: null, restarting: null, initialized: false, observers: () => { }, elements: /* @__PURE__ */ new Map(), config: __spreadValues({}, defaultConfig) }; const SHADOW_CAPABLE_SELECTOR = "*:not(:is(area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr,textarea,select,option,optgroup,script,style,title,iframe,object,video,audio,canvas,map,svg,math))"; const DATA_USAL_ATTRIBUTE = "data-usal"; const DATA_USAL_ID = `${DATA_USAL_ATTRIBUTE}-id`; const DATA_USAL_SELECTOR = `[${DATA_USAL_ATTRIBUTE}]`; const CONFIG_ANIMATION = 0; const CONFIG_DIRECTION = 1; const CONFIG_DURATION = 2; const CONFIG_DELAY = 3; const CONFIG_THRESHOLD = 4; const CONFIG_EASING = 5; const CONFIG_BLUR = 6; const CONFIG_ONCE = 7; const CONFIG_SPLIT = 8; const CONFIG_COUNT = 9; const CONFIG_TEXT = 10; const CONFIG_LOOP = 11; const CONFIG_FORWARDS = 12; const CONFIG_TUNING = 13; const CONFIG_LINE = 14; const CONFIG_STAGGER = 15; const DIRECTION_UP = 1; const DIRECTION_DOWN = 2; const DIRECTION_LEFT = 4; const DIRECTION_RIGHT = 8; const STYLE_OPACITY = "opacity"; const STYLE_TRANSFORM = "transform"; const STYLE_FILTER = "filter"; const STYLE_PERSPECTIVE = "perspective"; const STYLE_DISPLAY = "display"; const CSS_NONE = "none"; const CSS_INLINE_BLOCK = "inline-block"; const INTERSECTION_THRESHOLDS = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]; const ANIMATION_TYPES = ["fade", "zoomin", "zoomout", "flip", "slide"]; const genTmpId = () => `__usal${Date.now()}_${Math.random().toString(36).slice(2)}`; const calculateVisibilityRatio = (element) => { const rect = element.getBoundingClientRect(); const windowHeight = window.innerHeight; const windowWidth = window.innerWidth; if (rect.bottom <= 0 || rect.top >= windowHeight || rect.right <= 0 || rect.left >= windowWidth) { return 0; } const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0); const visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0); return visibleHeight / rect.height * (visibleWidth / rect.width); }; const captureComputedStyle = (element) => { const computedStyle = window.getComputedStyle(element); return { [STYLE_OPACITY]: computedStyle[STYLE_OPACITY] || "1", [STYLE_TRANSFORM]: computedStyle[STYLE_TRANSFORM] || CSS_NONE, [STYLE_FILTER]: computedStyle[STYLE_FILTER] || CSS_NONE, [STYLE_PERSPECTIVE]: computedStyle[STYLE_PERSPECTIVE] || CSS_NONE }; }; function applyStyles(element, styles, clean = false) { if (!element) return; const _a2 = styles, { offset, composite, easing } = _a2, cleanedStyles = __objRest(_a2, ["offset", "composite", "easing"]); element.animate([cleanedStyles], { duration: 0, fill: "forwards", iterations: 1, id: genTmpId() }); if (instance.destroying == null && !clean) { element.__usalFragment = 1; } else { delete element.__usalFragment; delete element.__usalOGStyle; delete element.__usalID; } } const cancelAllAnimations = (data, element, originalStyle) => new Promise((resolve) => { requestAnimationFrame(() => { if (!element) { resolve(); return; } element.getAnimations().filter((animation) => animation.id && animation.id.startsWith("__usal")).forEach((animation) => { var _a2; animation.cancel(); animation.effect = null; animation.timeline = null; if (originalStyle) { if (((_a2 = data == null ? void 0 : data.config) == null ? void 0 : _a2[CONFIG_SPLIT]) && !data.config[CONFIG_SPLIT].includes("item")) originalStyle = __spreadProps(__spreadValues({}, originalStyle), { [STYLE_DISPLAY]: CSS_INLINE_BLOCK }); applyStyles(element, originalStyle); } }); resolve(); }); }); const resetStyle = (data) => { if (data.config[CONFIG_LOOP]) return; const originalStyle = data.element.__usalOGStyle; if (data.countData) { const span = data.countData.span; applyStyles(span, { [STYLE_DISPLAY]: "inline" }); } else if (data.config[CONFIG_SPLIT]) { if (data.targets) { data.targets().forEach(([target]) => { applyStyles( target, createKeyframes( data.splitConfig || data.config, target.__usalOGStyle || originalStyle )[0] ); }); } } else { applyStyles(data.element, createKeyframes(data.config, originalStyle)[0]); } data.stop = false; }; function extractAndSetConfig(prefix, config, configKey, classString) { const pattern = new RegExp(`${prefix}-\\[[^\\]]+\\]`); const match = classString.match(pattern); if (match) { config[configKey] = match[0].slice(prefix.length + 2, -1); return classString.replace(match[0], ""); } return classString; } const extractAnimation = (firstPart, fallback = null) => { const animationIndex = ANIMATION_TYPES.indexOf(firstPart); return animationIndex !== -1 ? animationIndex : fallback; }; const extractDirection = (secondPart, fallback = null) => { if (!secondPart) return fallback; let direction = 0; for (const char of secondPart) { switch (char) { case "u": direction |= DIRECTION_UP; break; case "d": direction |= DIRECTION_DOWN; break; case "l": direction |= DIRECTION_LEFT; break; case "r": direction |= DIRECTION_RIGHT; break; } } return direction > 0 ? direction : fallback; }; const genEmptyConfig = () => { const config = new Array(16).fill(null); config[CONFIG_TUNING] = []; config[CONFIG_STAGGER] = "index"; return config; }; const parseClasses = (classString) => { var _a2; const config = genEmptyConfig(); classString = classString.replace(/\/\/[^\n\r]*/g, "").replace(new RegExp("\\/\\*.*?\\*\\/", "gs"), ""); classString = classString.toLowerCase().trim(); classString = extractAndSetConfig("count", config, CONFIG_COUNT, classString); classString = extractAndSetConfig("easing", config, CONFIG_EASING, classString); classString = extractAndSetConfig("line", config, CONFIG_LINE, classString); const tokens = classString.split(/\s+/).filter(Boolean); for (const token of tokens) { const parts = token.split("-"); const firstPart = parts[0]; if (config[CONFIG_ANIMATION] === null) { config[CONFIG_ANIMATION] = extractAnimation(firstPart); if (config[CONFIG_ANIMATION] !== null) { config[CONFIG_DIRECTION] = extractDirection(parts[1]); config[CONFIG_TUNING] = parts.slice(1 + (config[CONFIG_DIRECTION] ? 1 : 0)).filter((item) => !isNaN(item) && item !== "").map((item) => +item); continue; } } switch (token) { case "once": config[CONFIG_ONCE] = true; break; case "forwards": config[CONFIG_FORWARDS] = true; break; case "linear": case "ease": case "ease-in": case "ease-out": case "ease-in-out": case "step-start": case "step-end": config[CONFIG_EASING] = token; break; default: switch (firstPart) { case "split": if (parts[1]) config[CONFIG_SPLIT] = ((_a2 = config[CONFIG_SPLIT]) != null ? _a2 : "") + " " + token.slice(6); break; case "blur": if (parts[1]) config[CONFIG_BLUR] = +parts[1]; else config[CONFIG_BLUR] = true; break; case "loop": if (parts[1] === "mirror" || parts[1] === "jump") { config[CONFIG_LOOP] = parts[1]; } else config[CONFIG_LOOP] = true; break; case "text": if (parts[1] === "shimmer" || parts[1] === "fluid") config[CONFIG_TEXT] = parts[1]; break; case "duration": if (parts[1]) config[CONFIG_DURATION] = +parts[1]; break; case "delay": if (parts[1]) config[CONFIG_DELAY] = +parts[1]; if (parts[2]) config[CONFIG_STAGGER] = parts[2]; break; case "threshold": if (parts[1]) config[CONFIG_THRESHOLD] = +parts[1]; break; } } } return config; }; function parseTimeline(content, originalStyle, inlineBlock = false) { const clean = content.replace(/\s/g, "").toLowerCase(); const buildTransform = (type, axis, value, unit) => { const axisStr = axis && ["x", "y", "z"].includes(axis) ? axis.toUpperCase() : type === "rotate" ? "Z" : ""; return `${type}${axisStr}(${value}${unit})`; }; const parseTransforms = (str) => { const regex = /(\w|\w\w)([+-]\d+(?:\.\d+)?)/g; let transforms = ""; let opacity = null; let blur = null; let perspective = null; let glow = null; let fontWeight = null; let match; while ((match = regex.exec(str)) !== null) { const [, prop, value] = match; const num = parseFloat(value); const first2 = prop[0]; const second = prop[1]; switch (first2) { case "t": transforms += " " + buildTransform("translate", second, num, "%"); break; case "r": transforms += " " + buildTransform("rotate", second, num, "deg"); break; case "s": transforms += " " + buildTransform("scale", second, num, ""); break; case "o": opacity = Math.max(0, Math.min(100, num)) / 100; break; case "b": blur = `blur(${Math.max(0, num)}rem)`; break; case "g": glow = `brightness(${Math.max(0, num) / 100})`; break; case "w": fontWeight = Math.max(0, num).toString(); break; case "p": perspective = `${num}rem`; break; } } const result = {}; if (transforms) result[STYLE_TRANSFORM] = transforms.trim(); if (opacity !== null) result[STYLE_OPACITY] = opacity; if (blur || glow) { result[STYLE_FILTER] = [blur, glow].filter(Boolean).join(" "); } if (fontWeight) result.fontWeight = fontWeight; if (perspective) result[STYLE_PERSPECTIVE] = perspective; return result; }; const keyframes = /* @__PURE__ */ new Map(); clean.split("|").forEach((frame, index) => { const percentMatch = frame.match(/^(\d+)/); const percent = index === 0 ? 0 : percentMatch ? Math.max(0, Math.min(100, parseInt(percentMatch[1]))) : 100; keyframes.set(percent, parseTransforms(frame.replace(/^\d+/, ""))); }); if (Object.keys(keyframes.get(0)).length === 0) { keyframes.set(0, originalStyle); } if (keyframes.size === 1) { keyframes.set(100, originalStyle); } else { const allKeys = [...keyframes.keys()]; if (keyframes.size >= 3) { const minKey = Math.min(...allKeys); keyframes.set(0, keyframes.get(minKey)); } const maxKey = Math.max(...allKeys); keyframes.set(100, keyframes.get(maxKey)); } const sorted = Array.from(keyframes.entries()).filter(([_, frame]) => Object.keys(frame).length > 0).sort((a, b) => a[0] - b[0]); const compressed = sorted.map(([percent, frame]) => __spreadValues(__spreadValues({ offset: (5 + percent * 0.9) / 100 }, frame), inlineBlock && { display: "inline-block" })); const first = __spreadValues(__spreadValues({}, sorted[0][1]), inlineBlock && { display: "inline-block" }); const last = __spreadValues(__spreadValues({}, sorted[sorted.length - 1][1]), inlineBlock && { display: "inline-block" }); return [__spreadValues({ offset: 0 }, first), ...compressed, __spreadValues({ offset: 1 }, last)]; } const createKeyframes = (config, originalStyle) => { var _a2, _b2, _c, _d, _e, _f; if (!originalStyle) return; const isSplitText = config[CONFIG_SPLIT] && !((_a2 = config[CONFIG_SPLIT]) == null ? void 0 : _a2.includes("item")); if (config[CONFIG_TEXT] === "shimmer") config[CONFIG_LINE] = "o+50g+100|50o+100g+130|o+50g+100"; else if (config[CONFIG_TEXT] === "fluid") config[CONFIG_LINE] = "w+100|50w+900|w+100"; if (config[CONFIG_LINE]) return parseTimeline(config[CONFIG_LINE], originalStyle, isSplitText); const animationType = (_b2 = config[CONFIG_ANIMATION]) != null ? _b2 : extractAnimation(instance.config.defaults.animation, 0); const direction = (_c = config[CONFIG_DIRECTION]) != null ? _c : extractDirection(instance.config.defaults.direction, 1); const blur = (_d = config[CONFIG_BLUR]) != null ? _d : instance.config.defaults.blur; const tuning = config[CONFIG_TUNING]; let firstTuning = tuning == null ? void 0 : tuning.at(0); const lastTuning = tuning == null ? void 0 : tuning.at(-1); let secondTuning = tuning == null ? void 0 : tuning.at(1); let fromTimeline = "o+0"; if (animationType === 4) fromTimeline = `o+${parseFloat(originalStyle[STYLE_OPACITY]) * 100}`; const defaultDelta = isSplitText ? 50 : 15; const intensity = (lastTuning != null ? lastTuning : defaultDelta) / 100; if (animationType === 1 || animationType === 2) { fromTimeline += `s+${1 + (animationType === 1 ? -intensity : intensity)}`; firstTuning = null; secondTuning = (tuning == null ? void 0 : tuning.length) === 2 ? null : secondTuning; } else if (animationType === 3) { const angle = firstTuning != null ? firstTuning : 90; if (direction & (DIRECTION_UP | DIRECTION_DOWN)) { const rotX = direction & DIRECTION_UP ? angle : -angle; fromTimeline += `rx${rotX > 0 ? "+" : ""}${rotX}`; } if (direction & (DIRECTION_LEFT | DIRECTION_RIGHT)) { const rotY = direction & DIRECTION_LEFT ? -angle : angle; fromTimeline += `ry${rotY > 0 ? "+" : ""}${rotY}`; } if (!(direction & (DIRECTION_UP | DIRECTION_DOWN | DIRECTION_LEFT | DIRECTION_RIGHT))) { fromTimeline += `ry+${angle}`; } const perspectiveValue = (tuning == null ? void 0 : tuning.length) === 2 ? lastTuning : 25; fromTimeline += `p+${perspectiveValue != null ? perspectiveValue : 25}`; } if (animationType !== 3 && direction) { if (direction & DIRECTION_RIGHT) { fromTimeline += `tx-${firstTuning != null ? firstTuning : defaultDelta}`; } else if (direction & DIRECTION_LEFT) { fromTimeline += `tx+${firstTuning != null ? firstTuning : defaultDelta}`; } if (direction & DIRECTION_DOWN) { fromTimeline += `ty-${(_e = secondTuning != null ? secondTuning : firstTuning) != null ? _e : defaultDelta}`; } else if (direction & DIRECTION_UP) { fromTimeline += `ty+${(_f = secondTuning != null ? secondTuning : firstTuning) != null ? _f : defaultDelta}`; } } if (blur) { const blurValue = blur === true ? 0.625 : typeof blur === "number" && !isNaN(blur) ? Math.max(0, blur) : 0.625; fromTimeline += `b+${blurValue}`; } return parseTimeline(fromTimeline, originalStyle, isSplitText); }; function getStaggerFunction(targets, strategy = "index") { const targetsData = targets.map((target) => { const rect = target.getBoundingClientRect(); return { target, x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; }); const bounds = targetsData.reduce( (acc, item) => ({ minX: Math.min(acc.minX, item.x), maxX: Math.max(acc.maxX, item.x), minY: Math.min(acc.minY, item.y), maxY: Math.max(acc.maxY, item.y) }), { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity } ); const centerX = (bounds.minX + bounds.maxX) / 2; const centerY = (bounds.minY + bounds.maxY) / 2; const metrics = targetsData.map((item, index) => { let value; switch (strategy) { case "linear": value = Math.hypot(item.x, item.y); break; case "center": value = Math.hypot(item.x - centerX, item.y - centerY); break; case "edges": value = Math.min( Math.abs(item.x - bounds.minX), Math.abs(item.x - bounds.maxX), Math.abs(item.y - bounds.minY), Math.abs(item.y - bounds.maxY) ); break; case "random": value = Math.random(); break; default: value = index; } return value; }); const min = Math.min(...metrics); const max = Math.max(...metrics); const range = max - min || 1; return (splitDelay = 50) => targetsData.map((item, index) => { const normalizedValue = (metrics[index] - min) / range; let delay; if (strategy === "index") { delay = index * splitDelay; } else { delay = normalizedValue * (targets.length - 1) * splitDelay; } return [item.target, delay]; }); } const setupSplit = (element, splitBy, strategy, resolve) => { const targets = []; if (splitBy === "item") { Array.from(element.children).forEach((child) => { child.__usalOGStyle = captureComputedStyle(child); targets.push(child); }); return [getStaggerFunction(targets, strategy), null]; } const createSpan = (content) => { const span = document.createElement("span"); span.textContent = content; return span; }; const processTextContent = (text) => { if (!(text == null ? void 0 : text.trim())) return text ? document.createTextNode(text) : null; const wrapper = document.createElement("span"); const words = text.split(/(\s+)/); words.forEach((word) => { if (!word) return; if (/\s/.test(word)) { wrapper.appendChild(document.createTextNode(word)); return; } if (splitBy === "word") { const span = createSpan(word); applyStyles(span, { [STYLE_DISPLAY]: CSS_INLINE_BLOCK }); wrapper.appendChild(span); targets.push(span); return; } const container = document.createElement("span"); applyStyles(container, { [STYLE_DISPLAY]: CSS_INLINE_BLOCK, whiteSpace: "nowrap" }); let chars; if (typeof Intl !== "undefined" && Intl.Segmenter) { const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" }); chars = Array.from(segmenter.segment(word), (s) => s.segment); } else { chars = word.match( new RegExp("\\p{RI}\\p{RI}|(?:\\p{Emoji}(?:\\u200D\\p{Emoji})*)|(?:\\P{M}\\p{M}*)|.", "gsu") ) || [word]; } chars.forEach((char) => { const span = createSpan(char); container.appendChild(span); targets.push(span); }); wrapper.appendChild(container); }); return wrapper; }; const textNodes = []; let countTextNodes = 0; let wrappers = null; const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false); while (walker.nextNode()) { if (walker.currentNode.textContent.trim()) { textNodes.push(walker.currentNode); countTextNodes++; } } if (textNodes.length) wrappers = []; textNodes.forEach((textNode) => { const processed = processTextContent(textNode.textContent); wrappers.push(processed); requestAnimationFrame(() => { requestAnimationFrame(() => { textNode.parentNode.replaceChild(processed, textNode); countTextNodes--; if (countTextNodes === 0) { resolve(); } }); }); }); return [getStaggerFunction(targets, strategy), wrappers]; }; const setupCount = (element, config, data, resolve) => { const original = config[CONFIG_COUNT].trim(); const clean = original.replace(/[^\d\s,.]/g, ""); const separators = [",", ".", " "].filter((s) => clean.includes(s)); const sepPositions = separators.map((s) => ({ s, p: clean.lastIndexOf(s) })).sort((a, b) => b.p - a.p); let value, decimals = 0, thousandSep = "", decimalSep = ""; if (separators.length === 0) { value = parseFloat(clean); } else if (separators.length === 1) { const sep = separators[0]; const afterSep = clean.substring(clean.lastIndexOf(sep) + 1); if (afterSep.length <= 3 && afterSep.length > 0 && sep !== " ") { decimalSep = sep; decimals = afterSep.length; value = parseFloat(clean.replace(sep, ".")); } else { thousandSep = sep; value = parseFloat(clean.replace(new RegExp(`\\${thousandSep}`, "g"), "")); } } else { decimalSep = sepPositions[0].s; thousandSep = sepPositions[1].s; const processed = clean.replace(new RegExp(`\\${thousandSep}`, "g"), "").replace(decimalSep, "."); value = parseFloat(processed); decimals = clean.substring(sepPositions[0].p + 1).replace(/\D/g, "").length; } let span = null; let wrapper = null; const findAndReplace = (node) => { if (span) return; if (node.nodeType === Node.TEXT_NODE) { const text = node.textContent; const index = text.indexOf(config[CONFIG_COUNT]); if (index !== -1) { const before = text.substring(0, index); const after = text.substring(index + config[CONFIG_COUNT].length); wrapper = document.createElement("span"); if (before) wrapper.appendChild(document.createTextNode(before)); span = document.createElement("span"); span.textContent = original; wrapper.appendChild(span); if (after) wrapper.appendChild(document.createTextNode(after)); data.textWrappers = [wrapper]; requestAnimationFrame(() => { requestAnimationFrame(() => { node.parentNode.replaceChild(wrapper, node); resolve(); }); }); } } else if (node.nodeType === Node.ELEMENT_NODE) { Array.from(node.childNodes).forEach(findAndReplace); } }; findAndReplace(element); if (!span) { resolve(); return false; } data.countData = { value, decimals, span, thousandSep, decimalSep }; return true; }; const animateCount = (countData, options) => { const { duration, easing } = options; const { value, decimals, span, thousandSep, decimalSep } = countData; let tickTime = null; let currentTime = 0; let playState = "idle"; let playbackRate = 1; const getEasingFunction = (easingType) => { switch (easingType) { case "linear": return (t) => t; case "ease": return (t) => { if (t === 0) return 0; if (t === 1) return 1; return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }; case "ease-in": return (t) => t * t * t; case "ease-out": return (t) => 1 - Math.pow(1 - t, 3); case "ease-in-out": return (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; default: return (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } }; const easingFunction = getEasingFunction(easing); const formatNumber = (val) => { const parts = (decimals > 0 ? val.toFixed(decimals) : Math.floor(val).toString()).split("."); if (thousandSep && parts[0].length > 3) { const reversed = parts[0].split("").reverse(); parts[0] = reversed.reduce( (acc, digit, i) => i > 0 && i % 3 === 0 ? digit + thousandSep + acc : digit + acc, "" ); } return parts.length > 1 && decimalSep ? parts[0] + decimalSep + parts[1] : parts[0]; }; const updateValue = (time) => { const progress = Math.max(0, Math.min(1, time / duration)); if (progress <= 0.06) { span.textContent = formatNumber(0); } else if (progress >= 0.94) { span.textContent = formatNumber(value); } else { const adjustedProgress = (progress - 0.05) / 0.9; const easedProgress = easingFunction(adjustedProgress); const currentValue = value * easedProgress; span.textContent = formatNumber(currentValue); } if (progress >= 1 && playbackRate > 0 || progress <= 0 && playbackRate < 0) { playState = "finished"; } }; return { tick(now, loop = false) { if (loop && playState !== "paused" || playState === "running") { playState = "running"; let elapsed = now - tickTime; const maxElapsed = 100; if (elapsed > maxElapsed) { elapsed = 16.67; } currentTime = currentTime + (playbackRate > 0 ? elapsed : -elapsed); currentTime = Math.max(0, Math.min(duration, currentTime)); tickTime = now; updateValue(currentTime); } }, play() { if (playState === "finished" || playState === "running") return; tickTime = performance.now(); playState = "running"; }, pause() { if (playState === "running") { playState = "paused"; } }, cancel() { currentTime = duration * 0.95; updateValue(currentTime); playState = "finished"; }, persist() { }, get playState() { return playState; }, get currentTime() { return currentTime; }, set currentTime(time) { currentTime = Math.max(0, Math.min(duration, time)); updateValue(currentTime); }, get playbackRate() { return playbackRate; }, set playbackRate(rate) { playbackRate = rate; } }; }; class AnimationController { reset() { this.rafId = null; this.animations = /* @__PURE__ */ new Map(); } constructor(data) { this.data = data; this.reset(); } timeToSayGoodbye() { if (this.animations.size !== 0) return false; cancelAnimationFrame(this.rafId); this.reset(); this.data.resolve(); return true; } cleanupAnimation(toCleanup) { toCleanup.forEach(([animation, info]) => { const clean = () => { this.animations.delete(animation); this.timeToSayGoodbye(); }; if (this.data.countData) { animation.cancel(); clean(); } else cancelAllAnimations(this.data, info.element, info.originalStyle).then(() => { clean(); }); }); } prepare(tickTime) { var _a2; const toCleanup = []; const toAnimate = []; for (const [animation, info] of this.animations) { if (this.data.stop) { toCleanup.push([animation, info]); continue; } (_a2 = animation.tick) == null ? void 0 : _a2.call(animation, tickTime, info.loop); const progress = animation.currentTime / info.duration; if (!info.loop && (progress >= 0.95 || animation.playState === "finished")) { toCleanup.push([animation, info]); continue; } else if (!info.waiting && !info.pendingPlay && (progress >= 0.95 && info.playbackRate > 0 || progress <= 0.05 && info.playbackRate < 0)) { if (info.playbackRate < 0) animation.currentTime = info.duration * 0.03; else animation.currentTime = info.duration * 0.97; animation.pause(); info.waiting = true; } if (info.pendingPlay && tickTime >= info.playAt) { info.pendingPlay = false; animation.play(); } toAnimate.push(info); } return { toCleanup, toAnimate }; } animate(toAnimate, tickTime) { if (toAnimate.length > 0 && toAnimate.every((info) => info.waiting)) { const isJump = toAnimate[0].loop === "jump"; const newPlaybackRate = isJump ? 1 : -toAnimate[0].playbackRate; const delays = toAnimate.map((info) => info.staggerDelay); const maxDelay = Math.max(...delays); toAnimate.forEach((next) => { if (isJump) next.animation.currentTime = next.duration * 0.03; next.animation.playbackRate = newPlaybackRate; next.playbackRate = newPlaybackRate; next.waiting = false; let delay = next.staggerDelay; if (newPlaybackRate < 0) { delay = maxDelay - next.staggerDelay; } if (!next.hasStarted && next.initialDelay > 0) { next.hasStarted = true; delay += next.initialDelay; } if (delay === 0) { next.animation.play(); } else { next.playAt = tickTime + delay; next.pendingPlay = true; } }); } } tick() { const tickTime = performance.now(); const { toCleanup, toAnimate } = this.prepare(tickTime); this.cleanupAnimation(toCleanup); this.animate(toAnimate, tickTime); if (!this.timeToSayGoodbye()) { this.rafId = requestAnimationFrame(() => this.tick()); } } add(element, config, originalStyle, initialDelay = 0, staggerDelay = 0) { var _a2, _b2, _c, _d, _e, _f, _g; const duration = Math.max( 0, (((_b2 = (_a2 = this.data.config[CONFIG_DURATION]) != null ? _a2 : instance.config.defaults.duration) != null ? _b2 : 1e3) + 1) / 0.9 ); const easing = (_c = this.data.config[CONFIG_EASING]) != null ? _c : instance.config.defaults.easing; const forwards = (_e = (_d = this.data.config[CONFIG_FORWARDS]) != null ? _d : instance.config.defaults.forwards) != null ? _e : false; const loopDefaults = (_f = instance.config.defaults.loop) != null ? _f : "mirror"; let loop = this.data.config[CONFIG_LOOP] === true ? loopDefaults : this.data.config[CONFIG_LOOP]; let options = { duration, easing, fill: "forwards" }; let keyframes = []; if (this.data.config[CONFIG_TEXT]) { loop = loop != null ? loop : loopDefaults; options.easing = (_g = this.data.config[CONFIG_EASING]) != null ? _g : "linear"; } keyframes = createKeyframes(config, originalStyle); if (forwards) originalStyle = keyframes[keyframes.length - 1]; options = __spreadProps(__spreadValues({}, options), { delay: 0, id: genTmpId() }); const animation = this.data.countData ? animateCount(this.data.countData, options) : element.animate(keyframes, options); animation.persist(); animation.currentTime = duration * 0.03; animation.pause(); this.animations.set(animation, { animation, element, duration, staggerDelay, initialDelay, originalStyle, loop, playbackRate: -1, waiting: true, hasStarted: false }); } letsGo() { var _a2, _b2, _c, _d, _e; const { element, config, targets, splitConfig } = this.data; const initialDelay = Math.max(0, (_b2 = (_a2 = config[CONFIG_DELAY]) != null ? _a2 : instance.config.defaults.delay) != null ? _b2 : 0); const originalStyle = element.__usalOGStyle; const splitDelay = Math.max( 0, (_d = (_c = splitConfig[CONFIG_DELAY]) != null ? _c : instance.config.defaults.splitDelay) != null ? _d : 0 ); let notReadYet = ((_e = targets == null ? void 0 : targets()) == null ? void 0 : _e.length) || (originalStyle ? 0 : 1); if (targets) { targets(splitDelay).forEach(([target, staggerDelay]) => { const targetOriginalStyle = target.__usalOGStyle || originalStyle; if (!targetOriginalStyle) return; notReadYet--; this.add( target, this.data.splitConfig, targetOriginalStyle, initialDelay, parseInt(staggerDelay) ); }); } else if (originalStyle) { this.add(element, config, originalStyle, initialDelay); } if (notReadYet === 0 && !this.rafId) { this.tick(); } } } const tryAnimate = (data) => { if (data.stop) return; data.hasAnimated = true; data.processing = new Promise((resolve) => { data.resolve = resolve; data.controller.letsGo(); }).then(() => { data.onfinish(); data.processing = null; data.stop = true; }); }; const tryAnimateIfVisible = (data, ratio = null) => { var _a2, _b2; if (data.config[CONFIG_LOOP] || data.processing !== null || data.hasAnimated && ((_a2 = data.config[CONFIG_ONCE]) != null ? _a2 : instance.config.once)) return; const _ratio = ratio != null ? ratio : calculateVisibilityRatio(data.element); if (data.stop && _ratio < 0.01) { resetStyle(data); return; } const threshold = Math.max( 0, Math.min(1, ((_b2 = data.config[CONFIG_THRESHOLD]) != null ? _b2 : instance.config.defaults.threshold) / 100) ); if (_ratio >= threshold) { tryAnimate(data); } }; const cleanupElement = (data) => new Promise((resolve) => { data.onfinish = () => { data.onfinish = () => { }; requestAnimationFrame( () => requestAnimationFrame(() => { if (data.targets) { data.targets().forEach(([target]) => { if (target.__usalOGStyle) { applyStyles(target, target.__usalOGStyle, true); } }); } if (data.textWrappers) { data.textWrappers.forEach((wrapper) => { if (wrapper == null ? void 0 : wrapper.parentNode) { wrapper.parentNode.replaceChild( document.createTextNode(wrapper.textContent), wrapper ); } }); } if (data.element.__usalOGStyle) { applyStyles(data.element, data.element.__usalOGStyle, true); } resolve(); }) ); }; if (data.processing === null) data.onfinish(); else data.stop = true; }); const processElement = (element, elementObserver) => { var _a2, _b2; if (element.__usalProcessing) return; if (!element.__usalID) { element.__usalOGStyle = captureComputedStyle(element); element.__usalID = (_a2 = element.getAttribute(DATA_USAL_ID)) != null ? _a2 : genTmpId(); } const classes = element.getAttribute(DATA_USAL_ATTRIBUTE) || ""; const existingData = instance.elements.get(element.__usalID); if (existingData) { if (classes !== existingData.configString) { element.__usalProcessing = true; instance.elements.delete(element.__usalID); elementObserver.unobserve(element); cleanupElement(existingData).then(() => { delete element.__usalProcessing; processElement(element, elementObserver); }); } return; } element.__usalFragment = 1; const config = parseClasses(classes); const data = { element, config, splitConfig: [...config], configString: classes, targets: null, state: null, stop: false, hasAnimated: false, processing: null, countData: null, onfinish: () => { }, controller: null, resolve: () => { }, textWrappers: null }; instance.elements.set(element.__usalID, { configString: classes }); const splitBy = (_b2 = config[CONFIG_SPLIT]) == null ? void 0 : _b2.split(" ").find( (item) => ["word", "letter", "item"].includes(item) ); data.processing = new Promise((resolve) => { let resolveNow = false; if (config[CONFIG_COUNT]) { setupCount(element, config, data, resolve); } else if (splitBy) { const splitOverrides = parseClasses(config[CONFIG_SPLIT]); const emptyConfig = genEmptyConfig(); data.splitConfig = config.map((value, index) => { const override = splitOverrides[index]; const empty = emptyConfig[index]; if (Array.isArray(override) && Array.isArray(empty)) { return override.length > 0 ? override : value; } return override !== empty ? override : value; }); const [targets, textWrappers] = setupSplit( element, splitBy, data.splitConfig[CONFIG_STAGGER], resolve ); data.targets = targets; data.textWrappers = textWrappers; resolveNow = textWrappers === null; } else resolveNow = true; if (resolveNow) resolve(); }).then(() => { if (data.stop) data.onfinish(); else { instance.elements.set(element.__usalID, data); data.controller = new AnimationController(data); resetStyle(data); requestAnimationFrame(() => __async(null, null, function* () { if (config[CONFIG_LOOP]) { tryAnimate(data); } else { tryAnimateIfVisible(data); elementObserver.observe(element); } })); } data.processing = null; }); }; const setupObservers = () => { const domObservers = /* @__PURE__ */ new Set(); const resizeObservers = /* @__PURE__ */ new Set(); const observedDOMs = /* @__PURE__ */ new Set(); let lastScan = 0; let throttleOnTailTimer = null; const elementObserver = new IntersectionObserver( (entries) => { for (const entry of entries) { const data = instance.elements.get( entry.target.__usalID || entry.target.getAttribute(DATA_USAL_ID) ); if (data) { tryAnimateIfVisible(data, entry.intersectionRatio); } } }, { threshold: INTERSECTION_THRESHOLDS } ); const collectAllDOMs = (root = document.body, collected = /* @__PURE__ */ new Set()) => { if (collected.has(root)) return collected; collected.add(root); for (const el of root.querySelectorAll(SHADOW_CAPABLE_SELECTOR)) { if (el.shadowRoot && !collected.has(el.shadowRoot)) { collectAllDOMs(el.shadowRoot, collected); } } return collected; }; const observeDOM = (dom) => { const mutationObs = new MutationObserver(handleObserverEvents); mutationObs.observe(dom, { childList: true, subtree: true, attributes: true }); domObservers.add(mutationObs); const resizeObs = new ResizeObserver(handleObserverEvents); if (dom === document.body || dom.host) { resizeObs.observe(dom === document.body ? dom : dom.host); resizeObservers.add(resizeObs); } }; const scanAllDOMs = () => { var _a2; for (const [id, data] of instance.elements) { if (!data || !data.element) { instance.elements.delete(id); continue; } if (!data.element.isConnected) { elementObserver.unobserve(data.element); cleanupElement(data).then(() => { instance.elements.delete(id); }); } else { tryAnimateIfVisible(data); } } const allDOMs = collectAllDOMs(); for (const dom of allDOMs) { if (!observedDOMs.has(dom)) { observeDOM(dom); observedDOMs.add(dom); } const elements = (_a2 = dom.querySelectorAll) == null ? void 0 : _a2.call(dom, DATA_USAL_SELECTOR); for (const element of elements) { processElement(element, elementObserver); } } lastScan = Date.now(); }; const handleObserverEvents = (events) => { const items = Array.isArray(events) ? events : [events]; const hasUsalFragment = (target) => !!target.__usalFragment; let cancel = null; for (const item of items) { if (item.type === "attributes") { const attrName = item.attributeName; if (attrName === DATA_USAL_ATTRIBUTE || attrName === DATA_USAL_ID) { processElement(item.target, elementObserver); cancel = true; } } if (cancel === null) { if (hasUsalFragment(item.target)) cancel = true; if (item.type === "childList") { const hasUsalFragmentChild = [...item.addedNodes, ...item.removedNodes].some( hasUsalFragment ); if (hasUsalFragmentChild) cancel = true; } } } if (cancel) return; const timeSinceLastScan = Date.now() - lastScan; if (timeSinceLastScan >= instance.config.observersDelay) { scanAllDOMs(); } else { if (throttleOnTailTimer) clearTimeout(throttleOnTailTimer); throttleOnTailTimer = setTimeout( () => { scanAllDOMs(); }, Math.max(0, instance.config.observersDelay - timeSinceLastScan) ); } }; scanAllDOMs(); return () => { clearTimeout(throttleOnTailTimer); domObservers.forEach((obs) => obs.disconnect()); resizeObservers.forEach((obs) => obs.disconnect()); elementObserver.disconnect(); domObservers.clear(); resizeObservers.clear(); observedDOMs.clear(); }; }; const autoInit = () => { if (!instance.initialized) { instance.initialized = true; instance.observers = setupObservers(); } }; const publicAPI = { config(newConfig = {}) { if (arguments.length === 0) return __spreadValues({}, instance.config); if (newConfig.defaults) { newConfig.defaults = __spreadValues(__spreadValues({}, instance.config.defaults), newConfig.defaults); } Object.assign(instance.config, newConfig); return publicAPI; }, destroy() { return __async(this, null, function* () { if (!instance.initialized) return Promise.resolve(); if (instance.destroying != null) return instance.destroying; if (instance.destroyTimer) { clearTimeout(instance.destroyTimer); } instance.destroying = new Promise((resolve) => { instance.destroyTimer = setTimeout(() => __async(null, null, function* () { instance.destroyTimer = null; instance.observers(); const elements = Array.from(instance.elements.values()); yield Promise.all(elements.map((data) => cleanupElement(data))); instance.elements.clear(); instance.observers = () => { }; instance.initialized = false; instance.destroying = null; resolve(); }), 50); }); return instance.destroying; }); }, restart() { return __async(this, null, function* () { if (instance.restarting != null) return instance.restarting; if (instance.destroyTimer) { clearTimeout(instance.destroyTimer); instance.destroyTimer = null; } if (instance.restartTimer) { clearTimeout(instance.restartTimer); } instance.restarting = new Promise((resolve) => { instance.restartTimer = setTimeout(() => { instance.restartTimer = null; publicAPI.destroy().then( () => new Promise((resolveInit) => { requestAnimationFrame(() => { if (document.readyState === "loading") { document.addEventListener( "DOMContentLoaded", () => { autoInit(); resolveInit(publicAPI); }, { once: true } ); } else { autoInit(); resolveInit(publicAPI); } }); }) ).then(resolve).finally(() => { instance.restarting = null; }); }, 50); }); return instance.restarting; }); }, initialized: () => instance.initialized, version: "1.3.0" }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", autoInit, { once: true }); } else { requestAnimationFrame(autoInit); } return publicAPI; })(); if (typeof window !== "undefined") { window.USAL = USAL; } var usal_default = USAL; // src/integrations/svelte.ts var usal = (node, value = "fade") => { node.setAttribute("data-usal", value); return { update(newValue) { node.setAttribute("data-usal", newValue); }, destroy() { } }; }; var useUSAL = () => ({ config: (config) => { if (config === void 0) { return usal_default.config(); } usal_default.config(config); }, destroy: () => usal_default.destroy(), restart: () => usal_default.restart() }); var svelte_default = usal_default; //# sourceMappingURL=index.cjs.map