index.js
  1  import { cubicOut, cubicInOut, linear } from '../easing/index.js';
  2  import { assign, split_css_unit, is_function } from '../internal/index.js';
  3  
  4  /**
  5   * Animates a `blur` filter alongside an element's opacity.
  6   *
  7   * https://svelte.dev/docs/svelte-transition#blur
  8   * @param {Element} node
  9   * @param {import('./public').BlurParams} [params]
 10   * @returns {import('./public').TransitionConfig}
 11   */
 12  export function blur(
 13  	node,
 14  	{ delay = 0, duration = 400, easing = cubicInOut, amount = 5, opacity = 0 } = {}
 15  ) {
 16  	const style = getComputedStyle(node);
 17  	const target_opacity = +style.opacity;
 18  	const f = style.filter === 'none' ? '' : style.filter;
 19  	const od = target_opacity * (1 - opacity);
 20  	const [value, unit] = split_css_unit(amount);
 21  	return {
 22  		delay,
 23  		duration,
 24  		easing,
 25  		css: (_t, u) => `opacity: ${target_opacity - od * u}; filter: ${f} blur(${u * value}${unit});`
 26  	};
 27  }
 28  
 29  /**
 30   * Animates the opacity of an element from 0 to the current opacity for `in` transitions and from the current opacity to 0 for `out` transitions.
 31   *
 32   * https://svelte.dev/docs/svelte-transition#fade
 33   * @param {Element} node
 34   * @param {import('./public').FadeParams} [params]
 35   * @returns {import('./public').TransitionConfig}
 36   */
 37  export function fade(node, { delay = 0, duration = 400, easing = linear } = {}) {
 38  	const o = +getComputedStyle(node).opacity;
 39  	return {
 40  		delay,
 41  		duration,
 42  		easing,
 43  		css: (t) => `opacity: ${t * o}`
 44  	};
 45  }
 46  
 47  /**
 48   * Animates the x and y positions and the opacity of an element. `in` transitions animate from the provided values, passed as parameters to the element's default values. `out` transitions animate from the element's default values to the provided values.
 49   *
 50   * https://svelte.dev/docs/svelte-transition#fly
 51   * @param {Element} node
 52   * @param {import('./public').FlyParams} [params]
 53   * @returns {import('./public').TransitionConfig}
 54   */
 55  export function fly(
 56  	node,
 57  	{ delay = 0, duration = 400, easing = cubicOut, x = 0, y = 0, opacity = 0 } = {}
 58  ) {
 59  	const style = getComputedStyle(node);
 60  	const target_opacity = +style.opacity;
 61  	const transform = style.transform === 'none' ? '' : style.transform;
 62  	const od = target_opacity * (1 - opacity);
 63  	const [xValue, xUnit] = split_css_unit(x);
 64  	const [yValue, yUnit] = split_css_unit(y);
 65  	return {
 66  		delay,
 67  		duration,
 68  		easing,
 69  		css: (t, u) => `
 70  			transform: ${transform} translate(${(1 - t) * xValue}${xUnit}, ${(1 - t) * yValue}${yUnit});
 71  			opacity: ${target_opacity - od * u}`
 72  	};
 73  }
 74  
 75  /**
 76   * Slides an element in and out.
 77   *
 78   * https://svelte.dev/docs/svelte-transition#slide
 79   * @param {Element} node
 80   * @param {import('./public').SlideParams} [params]
 81   * @returns {import('./public').TransitionConfig}
 82   */
 83  export function slide(node, { delay = 0, duration = 400, easing = cubicOut, axis = 'y' } = {}) {
 84  	const style = getComputedStyle(node);
 85  	const opacity = +style.opacity;
 86  	const primary_property = axis === 'y' ? 'height' : 'width';
 87  	const primary_property_value = parseFloat(style[primary_property]);
 88  	const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right'];
 89  	const capitalized_secondary_properties = secondary_properties.map(
 90  		(e) => `${e[0].toUpperCase()}${e.slice(1)}`
 91  	);
 92  	const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`]);
 93  	const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`]);
 94  	const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`]);
 95  	const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`]);
 96  	const border_width_start_value = parseFloat(
 97  		style[`border${capitalized_secondary_properties[0]}Width`]
 98  	);
 99  	const border_width_end_value = parseFloat(
100  		style[`border${capitalized_secondary_properties[1]}Width`]
101  	);
102  	return {
103  		delay,
104  		duration,
105  		easing,
106  		css: (t) =>
107  			'overflow: hidden;' +
108  			`opacity: ${Math.min(t * 20, 1) * opacity};` +
109  			`${primary_property}: ${t * primary_property_value}px;` +
110  			`padding-${secondary_properties[0]}: ${t * padding_start_value}px;` +
111  			`padding-${secondary_properties[1]}: ${t * padding_end_value}px;` +
112  			`margin-${secondary_properties[0]}: ${t * margin_start_value}px;` +
113  			`margin-${secondary_properties[1]}: ${t * margin_end_value}px;` +
114  			`border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` +
115  			`border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;`
116  	};
117  }
118  
119  /**
120   * Animates the opacity and scale of an element. `in` transitions animate from an element's current (default) values to the provided values, passed as parameters. `out` transitions animate from the provided values to an element's default values.
121   *
122   * https://svelte.dev/docs/svelte-transition#scale
123   * @param {Element} node
124   * @param {import('./public').ScaleParams} [params]
125   * @returns {import('./public').TransitionConfig}
126   */
127  export function scale(
128  	node,
129  	{ delay = 0, duration = 400, easing = cubicOut, start = 0, opacity = 0 } = {}
130  ) {
131  	const style = getComputedStyle(node);
132  	const target_opacity = +style.opacity;
133  	const transform = style.transform === 'none' ? '' : style.transform;
134  	const sd = 1 - start;
135  	const od = target_opacity * (1 - opacity);
136  	return {
137  		delay,
138  		duration,
139  		easing,
140  		css: (_t, u) => `
141  			transform: ${transform} scale(${1 - sd * u});
142  			opacity: ${target_opacity - od * u}
143  		`
144  	};
145  }
146  
147  /**
148   * Animates the stroke of an SVG element, like a snake in a tube. `in` transitions begin with the path invisible and draw the path to the screen over time. `out` transitions start in a visible state and gradually erase the path. `draw` only works with elements that have a `getTotalLength` method, like `<path>` and `<polyline>`.
149   *
150   * https://svelte.dev/docs/svelte-transition#draw
151   * @param {SVGElement & { getTotalLength(): number }} node
152   * @param {import('./public').DrawParams} [params]
153   * @returns {import('./public').TransitionConfig}
154   */
155  export function draw(node, { delay = 0, speed, duration, easing = cubicInOut } = {}) {
156  	let len = node.getTotalLength();
157  	const style = getComputedStyle(node);
158  	if (style.strokeLinecap !== 'butt') {
159  		len += parseInt(style.strokeWidth);
160  	}
161  	if (duration === undefined) {
162  		if (speed === undefined) {
163  			duration = 800;
164  		} else {
165  			duration = len / speed;
166  		}
167  	} else if (typeof duration === 'function') {
168  		duration = duration(len);
169  	}
170  	return {
171  		delay,
172  		duration,
173  		easing,
174  		css: (_, u) => `
175  			stroke-dasharray: ${len};
176  			stroke-dashoffset: ${u * len};
177  		`
178  	};
179  }
180  
181  /**
182   * The `crossfade` function creates a pair of [transitions](https://svelte.dev/docs#template-syntax-element-directives-transition-fn) called `send` and `receive`. When an element is 'sent', it looks for a corresponding element being 'received', and generates a transition that transforms the element to its counterpart's position and fades it out. When an element is 'received', the reverse happens. If there is no counterpart, the `fallback` transition is used.
183   *
184   * https://svelte.dev/docs/svelte-transition#crossfade
185   * @param {import('./public').CrossfadeParams & {
186   * 	fallback?: (node: Element, params: import('./public').CrossfadeParams, intro: boolean) => import('./public').TransitionConfig;
187   * }} params
188   * @returns {[(node: any, params: import('./public').CrossfadeParams & { key: any; }) => () => import('./public').TransitionConfig, (node: any, params: import('./public').CrossfadeParams & { key: any; }) => () => import('./public').TransitionConfig]}
189   */
190  export function crossfade({ fallback, ...defaults }) {
191  	/** @type {Map<any, Element>} */
192  	const to_receive = new Map();
193  	/** @type {Map<any, Element>} */
194  	const to_send = new Map();
195  	/**
196  	 * @param {Element} from_node
197  	 * @param {Element} node
198  	 * @param {import('./public').CrossfadeParams} params
199  	 * @returns {import('./public').TransitionConfig}
200  	 */
201  	function crossfade(from_node, node, params) {
202  		const {
203  			delay = 0,
204  			duration = (d) => Math.sqrt(d) * 30,
205  			easing = cubicOut
206  		} = assign(assign({}, defaults), params);
207  		const from = from_node.getBoundingClientRect();
208  		const to = node.getBoundingClientRect();
209  		const dx = from.left - to.left;
210  		const dy = from.top - to.top;
211  		const dw = from.width / to.width;
212  		const dh = from.height / to.height;
213  		const d = Math.sqrt(dx * dx + dy * dy);
214  		const style = getComputedStyle(node);
215  		const transform = style.transform === 'none' ? '' : style.transform;
216  		const opacity = +style.opacity;
217  		return {
218  			delay,
219  			duration: is_function(duration) ? duration(d) : duration,
220  			easing,
221  			css: (t, u) => `
222  				opacity: ${t * opacity};
223  				transform-origin: top left;
224  				transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${
225  				t + (1 - t) * dh
226  			});
227  			`
228  		};
229  	}
230  
231  	/**
232  	 * @param {Map<any, Element>} items
233  	 * @param {Map<any, Element>} counterparts
234  	 * @param {boolean} intro
235  	 * @returns {(node: any, params: import('./public').CrossfadeParams & { key: any; }) => () => import('./public').TransitionConfig}
236  	 */
237  	function transition(items, counterparts, intro) {
238  		return (node, params) => {
239  			items.set(params.key, node);
240  			return () => {
241  				if (counterparts.has(params.key)) {
242  					const other_node = counterparts.get(params.key);
243  					counterparts.delete(params.key);
244  					return crossfade(other_node, node, params);
245  				}
246  				// if the node is disappearing altogether
247  				// (i.e. wasn't claimed by the other list)
248  				// then we need to supply an outro
249  				items.delete(params.key);
250  				return fallback && fallback(node, params, intro);
251  			};
252  		};
253  	}
254  	return [transition(to_send, to_receive, false), transition(to_receive, to_send, true)];
255  }