UNPKG

ngx-breakpoint-observer

Version:

Angular reactive breakpoint observer based on Signals.

319 lines (307 loc) 10 kB
import { inject, DestroyRef, signal, computed, effect, untracked } from '@angular/core'; /** * Breakpoints from Tailwind V3 * * @see https://tailwindcss.com/docs/breakpoints */ const breakpointsTailwind = { sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1536, }; /** * Breakpoints from Bootstrap V5 * * @see https://getbootstrap.com/docs/5.3/layout/breakpoints */ const breakpointsBootstrap = { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1400, }; /** * Breakpoints from Angular Material * * @see https://material.angular.io/cdk/layout/overview */ const breakpointsMaterial = { xsmall: 0, small: 600, medium: 960, large: 1280, xlarge: 1920, }; function increaseWithUnit(target, delta) { if (typeof target === 'number') return target + delta; const value = RegExp(/^-?\d+\.?\d*/).exec(target)?.[0] ?? ''; const unit = target.slice(value.length); const result = Number.parseFloat(value) + delta; if (Number.isNaN(result)) return target; return result + unit; } /** * Get the value of value/signal/getter. */ function toValue(r) { return typeof r === 'function' ? r() : r; } const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'; const defaultWindow = isClient ? window : undefined; /** * Reactive Media Query. * * @param query Media query as a signal or getter or string. * @param options Configuration options for window. * @returns A read-only signal indicating whether the media query matches. */ function injectMediaQuery(query, options = {}) { const destroyRef = inject(DestroyRef); const { window = defaultWindow } = options; const isSupported = window && 'matchMedia' in window && typeof window.matchMedia === 'function'; let mediaQuery; const matches = signal(false); const handler = (event) => { matches.set(event.matches); }; if (isSupported) { mediaQuery = window.matchMedia(toValue(query)); matches.set(mediaQuery.matches); if ('addEventListener' in mediaQuery) mediaQuery.addEventListener('change', handler); // @ts-expect-error deprecated API else mediaQuery.addListener(handler); } destroyRef.onDestroy(() => { if (!mediaQuery) return; if ('removeEventListener' in mediaQuery) mediaQuery.removeEventListener('change', handler); // @ts-expect-error deprecated API else mediaQuery.removeListener(handler); }); return matches.asReadonly(); } /** * Reactive viewport breakpoints. * * This function provides a set of reactive utilities to handle viewport breakpoints. * It includes shortcut methods like `greaterOrEqual`, `smallerOrEqual`, `between`, etc. * * @param breakpoints A map of breakpoint names and their corresponding values. * @param options Configuration options such as window and strategy. * @returns An object with breakpoint utilities and shortcut methods. */ function injectBreakpoints(breakpoints, options = {}) { const { window = defaultWindow, strategy = 'min-width' } = options; function getValue(k, delta) { let v = breakpoints[toValue(k)]; if (delta !== null && delta !== undefined) { v = increaseWithUnit(v, delta); } if (typeof v === 'number') { v = `${v}px`; } return v; } function match(query) { if (!window) return false; return window.matchMedia(query).matches; } const greaterOrEqual = (k) => { return injectMediaQuery(() => `(min-width: ${getValue(k)})`, options); }; const smallerOrEqual = (k) => { return injectMediaQuery(() => `(max-width: ${getValue(k)})`, options); }; function current() { const points = Object.keys(breakpoints).map(i => [i, greaterOrEqual(i)]); return computed(() => points.filter(([, v]) => v()).map(([k]) => k)); } const shortcutMethods = Object.keys(breakpoints).reduce((shortcuts, k) => { Object.defineProperty(shortcuts, k, { get: () => strategy === 'min-width' ? greaterOrEqual(k) : smallerOrEqual(k), enumerable: true, configurable: true, }); return shortcuts; }, {}); return Object.assign(shortcutMethods, { greaterOrEqual, smallerOrEqual, greater(k) { return injectMediaQuery(() => `(min-width: ${getValue(k, 0.1)})`, options); }, smaller(k) { return injectMediaQuery(() => `(max-width: ${getValue(k, -0.1)})`, options); }, between(a, b) { return injectMediaQuery(() => `(min-width: ${getValue(a)}) and (max-width: ${getValue(b, -0.1)})`, options); }, isGreater(k) { return match(`(min-width: ${getValue(k, 0.1)})`); }, isGreaterOrEqual(k) { return match(`(min-width: ${getValue(k)})`); }, isSmaller(k) { return match(`(max-width: ${getValue(k, -0.1)})`); }, isSmallerOrEqual(k) { return match(`(max-width: ${getValue(k)})`); }, isInBetween(a, b) { return match(`(min-width: ${getValue(a)}) and (max-width: ${getValue(b, -0.1)})`); }, current, active() { const breakpoints = current(); return computed(() => breakpoints().length === 0 ? '' : breakpoints()[breakpoints().length - 1]); }, }); } /** * **@deprecated** Use `injectMediaQuery` instead. * * This function will be removed in version `4.0.0`. * * Reactive Media Query. * * @param query * @param options */ function observeMediaQuery(query, options = {}) { const { window = defaultWindow } = options; const isSupported = window && 'matchMedia' in window && typeof window.matchMedia === 'function'; let mediaQuery; const matches = signal(false); const handler = (event) => { matches.set(event.matches); }; const cleanup = () => { if (!mediaQuery) return; if ('removeEventListener' in mediaQuery) mediaQuery.removeEventListener('change', handler); // @ts-expect-error deprecated API else mediaQuery.removeListener(handler); }; effect(onCleanup => { if (!isSupported) return; mediaQuery = window.matchMedia(toValue(query)); if ('addEventListener' in mediaQuery) mediaQuery.addEventListener('change', handler); // @ts-expect-error deprecated API else mediaQuery.addListener(handler); untracked(() => matches.set(!!mediaQuery?.matches)); onCleanup(cleanup); }); return matches.asReadonly(); } /** * **@deprecated** Use `injectBreakpoints` instead. * * This function will be removed in version `4.0.0`. * * Reactive viewport breakpoints. * * @param breakpoints * @param options */ function observeBreakpoints(breakpoints, options = {}) { const { window = defaultWindow, strategy = 'min-width' } = options; function getValue(k, delta) { let v = breakpoints[toValue(k)]; if (delta !== null && delta !== undefined) { v = increaseWithUnit(v, delta); } if (typeof v === 'number') { v = `${v}px`; } return v; } function match(query) { if (!window) return false; return window.matchMedia(query).matches; } const greaterOrEqual = (k) => { return observeMediaQuery(() => `(min-width: ${getValue(k)})`, options); }; const smallerOrEqual = (k) => { return observeMediaQuery(() => `(max-width: ${getValue(k)})`, options); }; function current() { const points = Object.keys(breakpoints).map(i => [i, greaterOrEqual(i)]); return computed(() => points.filter(([, v]) => v()).map(([k]) => k)); } const shortcutMethods = Object.keys(breakpoints).reduce((shortcuts, k) => { Object.defineProperty(shortcuts, k, { get: () => strategy === 'min-width' ? greaterOrEqual(k) : smallerOrEqual(k), enumerable: true, configurable: true, }); return shortcuts; }, {}); return Object.assign(shortcutMethods, { greaterOrEqual, smallerOrEqual, greater(k) { return observeMediaQuery(() => `(min-width: ${getValue(k, 0.1)})`, options); }, smaller(k) { return observeMediaQuery(() => `(max-width: ${getValue(k, -0.1)})`, options); }, between(a, b) { return observeMediaQuery(() => `(min-width: ${getValue(a)}) and (max-width: ${getValue(b, -0.1)})`, options); }, isGreater(k) { return match(`(min-width: ${getValue(k, 0.1)})`); }, isGreaterOrEqual(k) { return match(`(min-width: ${getValue(k)})`); }, isSmaller(k) { return match(`(max-width: ${getValue(k, -0.1)})`); }, isSmallerOrEqual(k) { return match(`(max-width: ${getValue(k)})`); }, isInBetween(a, b) { return match(`(min-width: ${getValue(a)}) and (max-width: ${getValue(b, -0.1)})`); }, current, active() { const breakpoints = current(); return computed(() => breakpoints().length === 0 ? '' : breakpoints()[breakpoints().length - 1]); }, }); } /* * Public API Surface of ngx-breakpoint-observer */ /** * Generated bundle index. Do not edit. */ export { breakpointsBootstrap, breakpointsMaterial, breakpointsTailwind, injectBreakpoints, injectMediaQuery, observeBreakpoints, observeMediaQuery }; //# sourceMappingURL=ngx-breakpoint-observer.mjs.map