ngx-breakpoint-observer
Version:
Angular reactive breakpoint observer based on Signals.
319 lines (307 loc) • 10 kB
JavaScript
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