import { append_empty_stylesheet, detach, get_root_for_style } from './dom.js'; import { raf } from './environment.js'; // we need to store the information for multiple documents because a Svelte application could also contain iframes // https://github.com/sveltejs/svelte/issues/3624 /** @type {Map} */ const managed_styles = new Map(); let active = 0; // https://github.com/darkskyapp/string-hash/blob/master/index.js /** * @param {string} str * @returns {number} */ function hash(str) { let hash = 5381; let i = str.length; while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); return hash >>> 0; } /** * @param {Document | ShadowRoot} doc * @param {Element & ElementCSSInlineStyle} node * @returns {{ stylesheet: any; rules: {}; }} */ function create_style_information(doc, node) { const info = { stylesheet: append_empty_stylesheet(node), rules: {} }; managed_styles.set(doc, info); return info; } /** * @param {Element & ElementCSSInlineStyle} node * @param {number} a * @param {number} b * @param {number} duration * @param {number} delay * @param {(t: number) => number} ease * @param {(t: number, u: number) => string} fn * @param {number} uid * @returns {string} */ export function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) { const step = 16.666 / duration; let keyframes = '{\n'; for (let p = 0; p <= 1; p += step) { const t = a + (b - a) * ease(p); keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`; } const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; const name = `__svelte_${hash(rule)}_${uid}`; const doc = get_root_for_style(node); const { stylesheet, rules } = managed_styles.get(doc) || create_style_information(doc, node); if (!rules[name]) { rules[name] = true; stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); } const animation = node.style.animation || ''; node.style.animation = `${ animation ? `${animation}, ` : '' }${name} ${duration}ms linear ${delay}ms 1 both`; active += 1; return name; } /** * @param {Element & ElementCSSInlineStyle} node * @param {string} [name] * @returns {void} */ export function delete_rule(node, name) { const previous = (node.style.animation || '').split(', '); const next = previous.filter( name ? (anim) => anim.indexOf(name) < 0 // remove specific animation : (anim) => anim.indexOf('__svelte') === -1 // remove all Svelte animations ); const deleted = previous.length - next.length; if (deleted) { node.style.animation = next.join(', '); active -= deleted; if (!active) clear_rules(); } } /** @returns {void} */ export function clear_rules() { raf(() => { if (active) return; managed_styles.forEach((info) => { const { ownerNode } = info.stylesheet; // there is no ownerNode if it runs on jsdom. if (ownerNode) detach(ownerNode); }); managed_styles.clear(); }); }