From 5de0d9a3575d32ac6f51b3b380e70e7a9642ffce Mon Sep 17 00:00:00 2001 From: brunnerh Date: Tue, 8 Mar 2022 17:38:58 +0100 Subject: [PATCH] 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` --- COMPONENT_INDEX.md | 11 ++- docs/src/COMPONENT_API.json | 13 +-- docs/src/pages/components/Breakpoint.svx | 12 ++- .../pages/framed/Breakpoint/Breakpoint.svelte | 4 +- .../Breakpoint/BreakpointObserver.svelte | 15 ++++ src/Breakpoint/Breakpoint.svelte | 84 ++++--------------- src/Breakpoint/breakpointObserver.d.ts | 22 +++++ src/Breakpoint/breakpointObserver.js | 80 ++++++++++++++++++ src/Breakpoint/breakpoints.d.ts | 9 ++ src/Breakpoint/breakpoints.js | 11 +++ src/Breakpoint/index.d.ts | 3 + src/Breakpoint/index.js | 2 + tests/Breakpoint.test.svelte | 6 +- types/Breakpoint/Breakpoint.svelte.d.ts | 9 +- 14 files changed, 180 insertions(+), 101 deletions(-) create mode 100644 docs/src/pages/framed/Breakpoint/BreakpointObserver.svelte create mode 100644 src/Breakpoint/breakpointObserver.d.ts create mode 100644 src/Breakpoint/breakpointObserver.js create mode 100644 src/Breakpoint/breakpoints.d.ts create mode 100644 src/Breakpoint/breakpoints.js create mode 100644 src/Breakpoint/index.d.ts diff --git a/COMPONENT_INDEX.md b/COMPONENT_INDEX.md index f23590a8..e4bc1dc7 100644 --- a/COMPONENT_INDEX.md +++ b/COMPONENT_INDEX.md @@ -358,11 +358,10 @@ export type BreakpointValue = 320 | 672 | 1056 | 1312 | 1584; ### Props -| Prop name | Kind | Reactive | Type | Default value | Description | -| :---------- | :----------------- | :------- | :--------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------- | -| sizes | let | Yes | Record | { sm: false, md: false, lg: false, xlg: false, max: false, } | Carbon grid sizes as an object | -| size | let | Yes | BreakpointSize | undefined | Determine the current Carbon grid breakpoint size | -| breakpoints | const | No | Record | { sm: 320, md: 672, lg: 1056, xlg: 1312, max: 1584, } | Reference the Carbon grid breakpoints | +| Prop name | Kind | Reactive | Type | Default value | Description | +| :-------- | :--------------- | :------- | :------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------- | +| sizes | let | Yes | Record | { sm: false, md: false, lg: false, xlg: false, max: false, } | Carbon grid sizes as an object | +| size | let | Yes | BreakpointSize | undefined | Determine the current Carbon grid breakpoint size | ### Slots @@ -374,7 +373,7 @@ export type BreakpointValue = 320 | 672 | 1056 | 1312 | 1584; | Event name | Type | Detail | | :--------- | :--------- | :----------------------------------------------------------------------- | -| match | dispatched | { size: BreakpointSize; breakpointValue: BreakpointValue; } | +| change | dispatched | { size: BreakpointSize; breakpointValue: BreakpointValue; } | ## `Button` diff --git a/docs/src/COMPONENT_API.json b/docs/src/COMPONENT_API.json index b9ccb5ce..6fac60af 100644 --- a/docs/src/COMPONENT_API.json +++ b/docs/src/COMPONENT_API.json @@ -387,17 +387,6 @@ "isFunctionDeclaration": false, "constant": false, "reactive": true - }, - { - "name": "breakpoints", - "kind": "const", - "description": "Reference the Carbon grid breakpoints", - "type": "Record", - "value": "{ sm: 320, md: 672, lg: 1056, xlg: 1312, max: 1584, }", - "isFunction": false, - "isFunctionDeclaration": false, - "constant": true, - "reactive": false } ], "slots": [ @@ -410,7 +399,7 @@ "events": [ { "type": "dispatched", - "name": "match", + "name": "change", "detail": "{ size: BreakpointSize; breakpointValue: BreakpointValue; }" } ], diff --git a/docs/src/pages/components/Breakpoint.svx b/docs/src/pages/components/Breakpoint.svx index 082f57bc..22bf672f 100644 --- a/docs/src/pages/components/Breakpoint.svx +++ b/docs/src/pages/components/Breakpoint.svx @@ -22,6 +22,14 @@ This utility component uses the [Window.matchMedia API](https://developer.mozill Bind to the `size` prop to determine the current breakpoint size. Possible values include: `"sm" | "md" | "lg" | "xlg" | "max"`. -The `"on:match"` event will fire only when a breakpoint change event occurs (e.g., the browser width is resized). +The `"on:change"` event will fire when the size is initially determined and when a breakpoint change event occurs (e.g., the browser width is resized). - \ No newline at end of file + + +### Store and Breakpoint Values + +As an alternative to the component, `breakpointObserver` can be used to get the current size as a Svelte store. The store has two additional functions which create derived stores that return a `boolean` indicating whether the size is smaller/larger than a certain breakpoint. + +There also exists a `breakpoints` dictionary mapping from `BreakpointSize` to `BreakpointValue`. + + diff --git a/docs/src/pages/framed/Breakpoint/Breakpoint.svelte b/docs/src/pages/framed/Breakpoint/Breakpoint.svelte index 83116706..b1b7e0a1 100644 --- a/docs/src/pages/framed/Breakpoint/Breakpoint.svelte +++ b/docs/src/pages/framed/Breakpoint/Breakpoint.svelte @@ -5,13 +5,13 @@ let events = []; - +

Resize the width of your browser.

Breakpoint size

{size}

-
on:match
+
on:change
   {JSON.stringify(events, null, 2)}
 
diff --git a/docs/src/pages/framed/Breakpoint/BreakpointObserver.svelte b/docs/src/pages/framed/Breakpoint/BreakpointObserver.svelte new file mode 100644 index 00000000..a37e4683 --- /dev/null +++ b/docs/src/pages/framed/Breakpoint/BreakpointObserver.svelte @@ -0,0 +1,15 @@ + + +

Current breakpoint size: {$size}

+

Current breakpoint value: {breakpoints[$size]}px

+

Smaller than medium: {$smaller}

+

Larger than medium: {$larger}

diff --git a/src/Breakpoint/Breakpoint.svelte b/src/Breakpoint/Breakpoint.svelte index 19ef305f..c040e903 100644 --- a/src/Breakpoint/Breakpoint.svelte +++ b/src/Breakpoint/Breakpoint.svelte @@ -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; }} */ @@ -24,77 +24,23 @@ max: false, }; - /** - * Reference the Carbon grid breakpoints - * @type {Record} - */ - 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] }); diff --git a/src/Breakpoint/breakpointObserver.d.ts b/src/Breakpoint/breakpointObserver.d.ts new file mode 100644 index 00000000..6c8cbdf1 --- /dev/null +++ b/src/Breakpoint/breakpointObserver.d.ts @@ -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, 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; + /** + * 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; +}; diff --git a/src/Breakpoint/breakpointObserver.js b/src/Breakpoint/breakpointObserver.js new file mode 100644 index 00000000..a5836bd6 --- /dev/null +++ b/src/Breakpoint/breakpointObserver.js @@ -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} */ + 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.`); +} diff --git a/src/Breakpoint/breakpoints.d.ts b/src/Breakpoint/breakpoints.d.ts new file mode 100644 index 00000000..86da62ef --- /dev/null +++ b/src/Breakpoint/breakpoints.d.ts @@ -0,0 +1,9 @@ +/** + * Pixel sizes of Carbon grid breakpoints. + * @type {Record} + */ +export const breakpoints: Record; + +export type BreakpointSize = "sm" | "md" | "lg" | "xlg" | "max"; + +export type BreakpointValue = 320 | 672 | 1056 | 1312 | 1584; diff --git a/src/Breakpoint/breakpoints.js b/src/Breakpoint/breakpoints.js new file mode 100644 index 00000000..5773f066 --- /dev/null +++ b/src/Breakpoint/breakpoints.js @@ -0,0 +1,11 @@ +/** + * Pixel sizes of Carbon grid breakpoints. + * @type {Record} + */ +export const breakpoints = Object.freeze({ + sm: 320, + md: 672, + lg: 1056, + xlg: 1312, + max: 1584, +}); diff --git a/src/Breakpoint/index.d.ts b/src/Breakpoint/index.d.ts new file mode 100644 index 00000000..e9046700 --- /dev/null +++ b/src/Breakpoint/index.d.ts @@ -0,0 +1,3 @@ +export { default as Breakpoint } from "./Breakpoint.svelte"; +export { breakpointObserver } from "./breakpointObserver"; +export { breakpoints } from "./breakpoints"; diff --git a/src/Breakpoint/index.js b/src/Breakpoint/index.js index a84d174b..749b78c5 100644 --- a/src/Breakpoint/index.js +++ b/src/Breakpoint/index.js @@ -1 +1,3 @@ export { default as Breakpoint } from "./Breakpoint.svelte"; +export { breakpointObserver } from "./breakpointObserver"; +export { breakpoints } from "./breakpoints"; diff --git a/tests/Breakpoint.test.svelte b/tests/Breakpoint.test.svelte index 2b5bef5b..cbad6dc0 100644 --- a/tests/Breakpoint.test.svelte +++ b/tests/Breakpoint.test.svelte @@ -1,14 +1,14 @@ { console.log(e.detail); }}" > diff --git a/types/Breakpoint/Breakpoint.svelte.d.ts b/types/Breakpoint/Breakpoint.svelte.d.ts index b46f0c3e..3fd72642 100644 --- a/types/Breakpoint/Breakpoint.svelte.d.ts +++ b/types/Breakpoint/Breakpoint.svelte.d.ts @@ -22,15 +22,10 @@ export interface BreakpointProps { export default class Breakpoint extends SvelteComponentTyped< BreakpointProps, { - match: CustomEvent<{ + change: CustomEvent<{ size: BreakpointSize; breakpointValue: BreakpointValue; }>; }, { default: { size: BreakpointSize; sizes: Record } } -> { - /** - * Reference the Carbon grid breakpoints - */ - breakpoints: Record; -} +> {}