breaking(breakpoint): add breakpointObserver store (#1092)

* breaking: re-name event "on:match" to "on:change" in `Breakpoint.svelte`

* feat: add `breakpointObserver` read-only store

* refactor: export breakpoint constants from `breakpoints.js`
This commit is contained in:
brunnerh 2022-03-08 17:38:58 +01:00 committed by GitHub
commit 5de0d9a357
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 180 additions and 101 deletions

View file

@ -2,7 +2,7 @@
/**
* @typedef {"sm" | "md" | "lg" | "xlg" | "max"} BreakpointSize
* @typedef {320 | 672 | 1056 | 1312 | 1584} BreakpointValue
* @event {{ size: BreakpointSize; breakpointValue: BreakpointValue; }} match
* @event {{ size: BreakpointSize; breakpointValue: BreakpointValue; }} change
* @slot {{ size: BreakpointSize; sizes: Record<BreakpointSize, boolean>; }}
*/
@ -24,77 +24,23 @@
max: false,
};
/**
* Reference the Carbon grid breakpoints
* @type {Record<BreakpointSize, BreakpointValue>}
*/
export const breakpoints = {
sm: 320,
md: 672,
lg: 1056,
xlg: 1312,
max: 1584,
};
import { createEventDispatcher, onMount } from "svelte";
import { createEventDispatcher } from "svelte";
import { breakpointObserver } from "./breakpointObserver";
import { breakpoints } from "./breakpoints";
const dispatch = createEventDispatcher();
const observer = breakpointObserver();
onMount(() => {
const width = window.innerWidth;
if (width > breakpoints.max) size = "max";
else if (width < breakpoints.md) size = "sm";
else if (width >= breakpoints.md && width <= breakpoints.lg) size = "md";
else if (width >= breakpoints.lg && width <= breakpoints.xlg) size = "lg";
else if (width >= breakpoints.xlg && width <= breakpoints.max) size = "xlg";
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 matchersBySize = Object.fromEntries(
matchers.map(([size, queryList]) => [queryList.media, size])
);
function handleChange({ matches, media }) {
const size = matchersBySize[media];
sizes = { ...sizes };
sizes[size] = matches;
if (matches)
dispatch("match", { size, breakpointValue: breakpoints[size] });
}
matchers.forEach(([size, queryList]) =>
queryList.addEventListener("change", handleChange)
);
return () => {
matchers.forEach(([size, queryList]) =>
queryList.removeEventListener("change", handleChange)
);
};
});
$: {
if (sizes.sm) size = "sm";
if (sizes.md) size = "md";
if (sizes.lg) size = "lg";
if (sizes.xlg) size = "xlg";
if (sizes.max) size = "max";
}
$: size = $observer;
$: sizes = {
sm: size == "sm",
md: size == "md",
lg: size == "lg",
xlg: size == "xlg",
max: size == "max",
};
$: if (size != undefined)
dispatch("change", { size, breakpointValue: breakpoints[size] });
</script>
<slot size="{size}" sizes="{sizes}" />

22
src/Breakpoint/breakpointObserver.d.ts vendored Normal file
View file

@ -0,0 +1,22 @@
import type { Readable, Subscriber, Unsubscriber } from "svelte/store";
import type { BreakpointSize, BreakpointValue } from "./breakpoints";
/**
* 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(): {
subscribe: (this: void, run: Subscriber<any>, invalidate?: (value?: any) => void) => Unsubscriber;
/**
* 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: BreakpointSize) => Readable<boolean>;
/**
* 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: BreakpointSize) => Readable<boolean>;
};

View file

@ -0,0 +1,80 @@
import { onMount } from "svelte";
import { derived, writable } from "svelte/store";
import { breakpoints } from "./breakpoints";
/**
* 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() {
const store = writable(undefined);
onMount(() => {
/** @type {Record<import("./breakpoints").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 = Object.fromEntries(
matchers.map(([size, queryList]) => [queryList.media, size])
);
const size = matchers.find(([size, queryList]) => queryList.matches)[0];
store.set(size);
/** @type {(e: MediaQueryListEvent) => void} */
function handleChange({ matches, media }) {
const size = sizeByMedia[media];
if (matches) store.set(size);
}
matchers.forEach(([size, queryList]) =>
queryList.addEventListener("change", handleChange)
);
return () => {
matchers.forEach(([size, queryList]) =>
queryList.removeEventListener("change", handleChange)
);
};
});
return {
subscribe: store.subscribe,
/**
* Returns a store readable store that returns whether the current
* breakpoint is smaller than {@link size}.
* @param {import("./breakpoints").BreakpointSize} size Size to compare against.
*/
smallerThan: (size) => {
checkSizeValid(size);
return derived(store, ($size) => breakpoints[$size] < breakpoints[size]);
},
/**
* Returns a store readable store that returns whether the current
* breakpoint is larger than {@link size}.
* @param {import("./breakpoints").BreakpointSize} size Size to compare against.
*/
largerThan: (size) => {
checkSizeValid(size);
return derived(store, ($size) => breakpoints[$size] > breakpoints[size]);
},
};
}
function checkSizeValid(size) {
if (size in breakpoints == false)
throw new Error(`"${size}" is not a valid breakpoint size.`);
}

9
src/Breakpoint/breakpoints.d.ts vendored Normal file
View file

@ -0,0 +1,9 @@
/**
* Pixel sizes of Carbon grid breakpoints.
* @type {Record<BreakpointSize, BreakpointValue>}
*/
export const breakpoints: Record<BreakpointSize, BreakpointValue>;
export type BreakpointSize = "sm" | "md" | "lg" | "xlg" | "max";
export type BreakpointValue = 320 | 672 | 1056 | 1312 | 1584;

View file

@ -0,0 +1,11 @@
/**
* Pixel sizes of Carbon grid breakpoints.
* @type {Record<import("./breakpoints").BreakpointSize, BreakpointValue>}
*/
export const breakpoints = Object.freeze({
sm: 320,
md: 672,
lg: 1056,
xlg: 1312,
max: 1584,
});

3
src/Breakpoint/index.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
export { default as Breakpoint } from "./Breakpoint.svelte";
export { breakpointObserver } from "./breakpointObserver";
export { breakpoints } from "./breakpoints";

View file

@ -1 +1,3 @@
export { default as Breakpoint } from "./Breakpoint.svelte";
export { breakpointObserver } from "./breakpointObserver";
export { breakpoints } from "./breakpoints";