carbon-components-svelte
Version:
Svelte implementation of the Carbon Design System
108 lines (94 loc) • 3.52 kB
JavaScript
import { onMount } from "svelte";
import { derived, writable } from "svelte/store";
import { breakpoints } from "./breakpoints";
/** @typedef {import("./breakpoints").BreakpointSize} BreakpointSize */
/**
* Attaches `matchMedia` listeners for every breakpoint and invokes `callback`
* with the current size immediately, then again on every subsequent change.
* Framework-agnostic (no `onMount`), so it can be called from a Svelte action
* as well as from {@link breakpointObserver}.
* @param {(size: BreakpointSize) => void} callback
* @returns {() => void} Cleanup function that removes the listeners.
*/
export function observeBreakpoint(callback) {
/** @type {Record<BreakpointSize, MediaQueryList>} */
const match = {
sm: window.matchMedia(`(max-width: ${breakpoints.md}px)`),
md: window.matchMedia(
`(min-width: ${breakpoints.md}px) and (max-width: ${breakpoints.lg}px)`,
),
lg: window.matchMedia(
`(min-width: ${breakpoints.lg}px) and (max-width: ${breakpoints.xlg}px)`,
),
xlg: window.matchMedia(
`(min-width: ${breakpoints.xlg}px) and (max-width: ${breakpoints.max}px)`,
),
max: window.matchMedia(`(min-width: ${breakpoints.max}px)`),
};
const matchers = Object.entries(match);
const sizeByMedia = /** @type {Record<string, BreakpointSize>} */ (
Object.fromEntries(
matchers.map(([size, queryList]) => [queryList.media, size]),
)
);
const entry = matchers.find(([_size, queryList]) => queryList.matches);
const size = entry?.[0];
if (size !== undefined) callback(/** @type {BreakpointSize} */ (size));
/** @type {(e: MediaQueryListEvent) => void} */
function handleChange({ matches, media }) {
const raw = sizeByMedia[media];
if (matches && raw !== undefined) {
callback(/** @type {BreakpointSize} */ (raw));
}
}
for (const [_size, queryList] of matchers) {
queryList.addEventListener("change", handleChange, { passive: true });
}
return () => {
for (const [_size, queryList] of matchers) {
queryList.removeEventListener("change", handleChange);
}
};
}
/**
* Creates a readable store that returns the current breakpoint size.
* It also provides functions for creating derived stores used to do comparisons.
*/
export function breakpointObserver() {
/** @type {import("svelte/store").Writable<BreakpointSize | undefined>} */
const store = writable(undefined);
onMount(() => observeBreakpoint((size) => store.set(size)));
return {
subscribe: store.subscribe,
/**
* Returns a store readable store that returns whether the current
* breakpoint is smaller than {@link size}.
* @param {BreakpointSize} size Size to compare against.
*/
smallerThan: (size) => {
checkSizeValid(size);
return derived(store, ($size) =>
$size === undefined ? false : breakpoints[$size] < breakpoints[size],
);
},
/**
* Returns a store readable store that returns whether the current
* breakpoint is larger than {@link size}.
* @param {BreakpointSize} size Size to compare against.
*/
largerThan: (size) => {
checkSizeValid(size);
return derived(store, ($size) =>
$size === undefined ? false : breakpoints[$size] > breakpoints[size],
);
},
};
}
/**
* @param {BreakpointSize} size
*/
function checkSizeValid(size) {
if (size in breakpoints === false)
throw new Error(`"${size}" is not a valid breakpoint size.`);
}
export default breakpointObserver;