Skip to content

Commit c5c4bea

Browse files
committed
feat: provide MediaQuery / prefersReducedMotion
closes #5346
1 parent 37e6c7f commit c5c4bea

File tree

5 files changed

+76
-0
lines changed

5 files changed

+76
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
1+
import { MediaQuery } from 'svelte/reactivity';
2+
13
export * from './spring.js';
24
export * from './tweened.js';
5+
6+
/**
7+
* A media query that matches if the user has requested reduced motion.
8+
* @type {MediaQuery}
9+
*/
10+
export const prefersReducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)');

packages/svelte/src/reactivity/index-client.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { SvelteSet } from './set.js';
33
export { SvelteMap } from './map.js';
44
export { SvelteURL } from './url.js';
55
export { SvelteURLSearchParams } from './url-search-params.js';
6+
export { MediaQuery } from './media-query.js';

packages/svelte/src/reactivity/index-server.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ export const SvelteSet = globalThis.Set;
33
export const SvelteMap = globalThis.Map;
44
export const SvelteURL = globalThis.URL;
55
export const SvelteURLSearchParams = globalThis.URLSearchParams;
6+
7+
export class MediaQuery {
8+
matches = false;
9+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { get, tick } from '../internal/client/runtime.js';
2+
import { set, source } from '../internal/client/reactivity/sources.js';
3+
import { effect_tracking, render_effect } from '../internal/client/reactivity/effects.js';
4+
5+
/**
6+
* Creates a media query and provides a `matches` property that reflects its current state.
7+
*/
8+
export class MediaQuery {
9+
#matches = source(false);
10+
#subscribers = 0;
11+
#query;
12+
/** @type {any} */
13+
#listener;
14+
15+
get matches() {
16+
if (effect_tracking()) {
17+
render_effect(() => {
18+
if (this.#subscribers === 0) {
19+
this.#listener = () => set(this.#matches, this.#query.matches);
20+
this.#query.addEventListener('change', this.#listener);
21+
}
22+
23+
this.#subscribers += 1;
24+
25+
return () => {
26+
tick().then(() => {
27+
// Only count down after timeout, else we would reach 0 before our own render effect reruns,
28+
// but reach 1 again when the tick callback of the prior teardown runs. That would mean we
29+
// re-subcribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
30+
this.#subscribers -= 1;
31+
32+
if (this.#subscribers === 0) {
33+
this.#query.removeEventListener('change', this.#listener);
34+
}
35+
});
36+
};
37+
});
38+
}
39+
40+
return get(this.#matches);
41+
}
42+
43+
/** @param {string} query */
44+
constructor(query) {
45+
this.#query = window.matchMedia(query);
46+
console.log('MediaQuery.constructor', query, this.#query);
47+
this.#matches.v = this.#query.matches;
48+
}
49+
}

packages/svelte/types/index.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,7 @@ declare module 'svelte/legacy' {
16301630
}
16311631

16321632
declare module 'svelte/motion' {
1633+
import type { MediaQuery } from 'svelte/reactivity';
16331634
export interface Spring<T> extends Readable<T> {
16341635
set: (new_value: T, opts?: SpringUpdateOpts) => Promise<void>;
16351636
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>;
@@ -1676,6 +1677,10 @@ declare module 'svelte/motion' {
16761677
easing?: (t: number) => number;
16771678
interpolate?: (a: T, b: T) => (t: number) => T;
16781679
}
1680+
/**
1681+
* A media query that matches if the user has requested reduced motion.
1682+
* */
1683+
export const prefersReducedMotion: MediaQuery;
16791684
/**
16801685
* The spring function in Svelte creates a store whose value is animated, with a motion that simulates the behavior of a spring. This means when the value changes, instead of transitioning at a steady rate, it "bounces" like a spring would, depending on the physics parameters provided. This adds a level of realism to the transitions and can enhance the user experience.
16811686
*
@@ -1720,6 +1725,15 @@ declare module 'svelte/reactivity' {
17201725
[REPLACE](params: URLSearchParams): void;
17211726
#private;
17221727
}
1728+
/**
1729+
* Creates a media query and provides a `matches` property that reflects its current state.
1730+
*/
1731+
export class MediaQuery {
1732+
1733+
constructor(query: string);
1734+
get matches(): boolean;
1735+
#private;
1736+
}
17231737

17241738
export {};
17251739
}

0 commit comments

Comments
 (0)