ngx-mq 2.11.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +187 -109
- package/fesm2022/ngx-mq.mjs +430 -10
- package/fesm2022/ngx-mq.mjs.map +1 -1
- package/index.d.ts +427 -3
- package/package.json +20 -5
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,180 +1,235 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/martsinlabs/ngx-mq/refs/heads/main/assets/logo.svg" width="
|
|
2
|
+
<img src="https://raw.githubusercontent.com/martsinlabs/ngx-mq/refs/heads/main/assets/logo.svg" width="140" alt="ngx-mq" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
<h3 align="center">
|
|
6
|
-
Signal-Powered Breakpoints & Media Queries
|
|
7
|
-
</h3>
|
|
5
|
+
<h3 align="center">Signal-Powered Breakpoints & Media Queries for Angular</h3>
|
|
8
6
|
|
|
9
|
-
<
|
|
7
|
+
<p align="center">
|
|
8
|
+
Reactive <code>matchMedia</code> as Angular signals. SSR-safe, zoneless-ready, and free of RxJS.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
10
12
|
<a href="https://github.com/martsinlabs/ngx-mq/actions/workflows/ci.yml">
|
|
11
|
-
<img src="https://img.shields.io/github/actions/workflow/status/martsinlabs/ngx-mq/ci.yml?branch=main&label=CI&color=44cc11&logo=github" alt="CI
|
|
13
|
+
<img src="https://img.shields.io/github/actions/workflow/status/martsinlabs/ngx-mq/ci.yml?branch=main&label=CI&color=44cc11&logo=github" alt="CI status" />
|
|
12
14
|
</a>
|
|
13
15
|
<a href="https://codecov.io/gh/martsinlabs/ngx-mq">
|
|
14
16
|
<img src="https://codecov.io/gh/martsinlabs/ngx-mq/branch/main/graph/badge.svg" alt="coverage" />
|
|
15
17
|
</a>
|
|
16
|
-
<br>
|
|
17
18
|
<a href="https://www.npmjs.com/package/ngx-mq">
|
|
18
19
|
<img src="https://img.shields.io/npm/v/ngx-mq.svg?color=007ec6" alt="npm version" />
|
|
19
20
|
</a>
|
|
20
21
|
<a href="https://www.npmjs.com/package/ngx-mq">
|
|
21
22
|
<img src="https://img.shields.io/npm/dm/ngx-mq.svg?color=44cc11" alt="npm downloads" />
|
|
22
23
|
</a>
|
|
24
|
+
<a href="https://bundlephobia.com/package/ngx-mq">
|
|
25
|
+
<img src="https://img.shields.io/bundlephobia/minzip/ngx-mq.svg?color=44cc11&label=minzip" alt="minzipped size" />
|
|
26
|
+
</a>
|
|
23
27
|
<a href="https://opensource.org/license/MIT">
|
|
24
28
|
<img src="https://img.shields.io/npm/l/ngx-mq.svg?color=44cc11" alt="license" />
|
|
25
29
|
</a>
|
|
26
|
-
</
|
|
30
|
+
</p>
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
---
|
|
29
33
|
|
|
30
34
|
## Features
|
|
31
35
|
|
|
32
|
-
-
|
|
33
|
-
- SSR-safe
|
|
34
|
-
- Auto-cleanup
|
|
35
|
-
-
|
|
36
|
-
-
|
|
36
|
+
- **Signal-native**: every query is a `Signal<boolean>` that updates as the viewport changes.
|
|
37
|
+
- **SSR-safe**: returns a configurable static value on the server, then hydrates on the client.
|
|
38
|
+
- **Auto-cleanup**: listeners are tied to Angular's `DestroyRef`; no manual teardown.
|
|
39
|
+
- **Efficient**: one shared `matchMedia` listener per unique query, reused across the app.
|
|
40
|
+
- **Batteries included**: Tailwind, Bootstrap and Material breakpoint presets out of the box.
|
|
41
|
+
- **Composable**: combine any signals with `and` / `or` / `not`.
|
|
42
|
+
- **Tiny and tested**: ~1.9 kB gzipped, 100% line coverage.
|
|
37
43
|
|
|
38
|
-
##
|
|
44
|
+
## Why ngx-mq?
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
Angular's CDK ships [`BreakpointObserver`](https://material.angular.io/cdk/layout/overview), which
|
|
47
|
+
works well but is built around RxJS and raw query strings. `ngx-mq` is designed for the signals era:
|
|
48
|
+
templates read a value directly, there is nothing to subscribe to, and cleanup is automatic.
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
| | `ngx-mq` | CDK `BreakpointObserver` |
|
|
51
|
+
| --------------------- | ------------------------------------------------- | ----------------------------------------- |
|
|
52
|
+
| Reactivity | `Signal<boolean>` | `Observable<BreakpointState>` |
|
|
53
|
+
| Cleanup | Automatic via `DestroyRef` | Manual (`unsubscribe` / `takeUntilDestroyed`) |
|
|
54
|
+
| Template usage | `@if (isDesktop())` | `async` pipe or manual subscription |
|
|
55
|
+
| Named breakpoints | Tailwind, Bootstrap, Material presets or your own | Material breakpoints or raw strings |
|
|
56
|
+
| Media-feature helpers | `colorScheme`, `hover`, `pointer`, ... | Raw query strings |
|
|
57
|
+
| Composition | `and` / `or` / `not` | RxJS operators |
|
|
58
|
+
| SSR | Configurable static value | Handle it yourself |
|
|
59
|
+
| Footprint | ~1.9 kB standalone | Part of `@angular/cdk` |
|
|
43
60
|
|
|
44
|
-
|
|
61
|
+
If you already pull in `@angular/cdk` and live in RxJS, `BreakpointObserver` is a fine choice. If you
|
|
62
|
+
want a signals-first, zoneless-friendly API with batteries included, reach for `ngx-mq`.
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
## Documentation
|
|
47
65
|
|
|
48
|
-
|
|
66
|
+
- **API reference and guides:** https://martsinlabs.github.io/ngx-mq
|
|
67
|
+
- **Live demo:** [StackBlitz](https://stackblitz.com/github/martsinlabs/ngx-mq-demo/tree/demo/v2?file=src%2Fapp%2Fapp.component.ts)
|
|
49
68
|
|
|
50
|
-
|
|
69
|
+
## Installation
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
# For Angular 16–18
|
|
54
|
-
npm install ngx-mq@1
|
|
71
|
+
Install the major version that matches your Angular version:
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
| Angular | Install |
|
|
74
|
+
| ------- | ------------------ |
|
|
75
|
+
| 20 - 22 | `npm i ngx-mq@3` |
|
|
76
|
+
| 19 | `npm i ngx-mq@2` |
|
|
77
|
+
| 16 - 18 | `npm i ngx-mq@1` |
|
|
59
78
|
|
|
60
|
-
##
|
|
79
|
+
## Quick start
|
|
61
80
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Create a custom breakpoint map or use one of the built-in presets (e.g. `provideTailwindBreakpoints()`).
|
|
81
|
+
**1. Provide a breakpoint map** at bootstrap (a custom map or a preset):
|
|
65
82
|
|
|
66
83
|
```ts
|
|
67
84
|
import { bootstrapApplication } from '@angular/platform-browser';
|
|
68
|
-
import { AppComponent } from './app/app.component';
|
|
69
85
|
import { provideBreakpoints } from 'ngx-mq';
|
|
86
|
+
import { AppComponent } from './app/app.component';
|
|
70
87
|
|
|
71
88
|
bootstrapApplication(AppComponent, {
|
|
72
|
-
providers: [
|
|
73
|
-
// Define a custom map (keys are named ranges, values are widths in pixels)
|
|
74
|
-
provideBreakpoints({
|
|
75
|
-
sm: 640,
|
|
76
|
-
md: 768,
|
|
77
|
-
lg: 1024,
|
|
78
|
-
}),
|
|
79
|
-
],
|
|
89
|
+
providers: [provideBreakpoints({ sm: 640, md: 768, lg: 1024 })],
|
|
80
90
|
});
|
|
81
91
|
```
|
|
82
92
|
|
|
83
|
-
**
|
|
93
|
+
**2. Use the helpers** as signals inside any component:
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
```ts
|
|
96
|
+
import { Component } from '@angular/core';
|
|
97
|
+
import { up, down, between } from 'ngx-mq';
|
|
88
98
|
|
|
89
|
-
|
|
99
|
+
@Component({
|
|
100
|
+
selector: 'app-root',
|
|
101
|
+
template: `
|
|
102
|
+
@if (isDesktop()) {
|
|
103
|
+
<app-sidebar />
|
|
104
|
+
}
|
|
105
|
+
`,
|
|
106
|
+
})
|
|
107
|
+
export class AppComponent {
|
|
108
|
+
readonly isMobile = down('md');
|
|
109
|
+
readonly isTablet = between('md', 'lg');
|
|
110
|
+
readonly isDesktop = up('lg');
|
|
111
|
+
}
|
|
112
|
+
```
|
|
90
113
|
|
|
91
|
-
|
|
92
|
-
| --------- | ----------------------------------------------------------------- | ----------------- | --------------------------------------------- |
|
|
93
|
-
| `up` | `bp: string, options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when viewport width ≥ breakpoint |
|
|
94
|
-
| `down` | `bp: string, options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when viewport width < breakpoint |
|
|
95
|
-
| `between` | `minBp: string, maxBp: string, options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when viewport width is in range [a, b] |
|
|
114
|
+
> **Tip:** Call the helpers within Angular's [injection context](https://angular.dev/guide/di/dependency-injection-context) (component fields, `inject`, factories) so their lifecycle stays in sync with the framework.
|
|
96
115
|
|
|
97
|
-
|
|
116
|
+
## API
|
|
98
117
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
Every query helper returns a `Signal<boolean>` and accepts an optional `options` argument
|
|
119
|
+
([`CreateMediaQueryOptions`](#options)). The `options` column is omitted below for brevity.
|
|
120
|
+
|
|
121
|
+
### Configuration
|
|
122
|
+
|
|
123
|
+
Register breakpoints once, then refer to them by name. Use a custom map or a preset:
|
|
103
124
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
125
|
+
```ts
|
|
126
|
+
import {
|
|
127
|
+
provideBreakpoints,
|
|
128
|
+
provideTailwindBreakpoints,
|
|
129
|
+
provideBootstrapBreakpoints,
|
|
130
|
+
provideMaterialBreakpoints,
|
|
131
|
+
} from 'ngx-mq';
|
|
107
132
|
```
|
|
108
133
|
|
|
109
|
-
|
|
134
|
+
| Preset | Breakpoints |
|
|
135
|
+
| ----------- | ------------------------------------------------ |
|
|
136
|
+
| Tailwind | `sm: 640, md: 768, lg: 1024, xl: 1280, 2xl: 1536` |
|
|
137
|
+
| Bootstrap | `sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1400` |
|
|
138
|
+
| Material | `sm: 600, md: 905, lg: 1240, xl: 1440` |
|
|
110
139
|
|
|
111
|
-
|
|
140
|
+
### Breakpoints
|
|
112
141
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `reducedMotion` | `options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when the user has enabled reduced motion. |
|
|
119
|
-
| `hover` | `options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when the user's primary input device supports hover capability. |
|
|
120
|
-
| `anyHover` | `options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when any available input device supports hover capability. |
|
|
121
|
-
| `pointer` | `value: 'fine' \| 'coarse' \| 'none', options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when the user's primary input device matches the specified pointer type. |
|
|
122
|
-
| `anyPointer` | `value: 'fine' \| 'coarse' \| 'none', options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when any available input device matches the specified pointer type. |
|
|
123
|
-
| `colorGamut` | `value: 'srgb' \| 'p3' \| 'rec2020', options?: CreateMediaQueryOptions` | `Signal<boolean>` | `true` when the user's display supports the specified color gamut. |
|
|
142
|
+
| Helper | Arguments | `true` when |
|
|
143
|
+
| --------- | ---------------- | ------------------------------------ |
|
|
144
|
+
| `up` | `bp` | viewport width `>=` `bp` |
|
|
145
|
+
| `down` | `bp` | viewport width `<` `bp` |
|
|
146
|
+
| `between` | `minBp`, `maxBp` | viewport width is in `[minBp, maxBp)` |
|
|
124
147
|
|
|
125
|
-
|
|
148
|
+
> **Note:** `down` and `between` upper bounds are **exclusive**: a small epsilon is subtracted
|
|
149
|
+
> from the max so adjacent ranges (e.g. `down('md')` and `up('md')`) never overlap. Tune it with
|
|
150
|
+
> [`provideBreakpointEpsilon`](#providers).
|
|
126
151
|
|
|
127
|
-
|
|
152
|
+
### Media features
|
|
128
153
|
|
|
129
|
-
|
|
154
|
+
| Helper | Arguments | `true` when |
|
|
155
|
+
| --------------- | ------------------------------ | ------------------------------------ |
|
|
156
|
+
| `orientation` | `'portrait' \| 'landscape'` | the screen orientation matches |
|
|
157
|
+
| `colorScheme` | `'light' \| 'dark'` | the system color scheme matches |
|
|
158
|
+
| `displayMode` | `DisplayModeOption` | the display mode matches (PWA detection) |
|
|
159
|
+
| `reducedMotion` | none | the user prefers reduced motion |
|
|
160
|
+
| `hover` | none | the primary pointer can hover |
|
|
161
|
+
| `anyHover` | none | any available pointer can hover |
|
|
162
|
+
| `pointer` | `'fine' \| 'coarse' \| 'none'` | the primary pointer matches |
|
|
163
|
+
| `anyPointer` | `'fine' \| 'coarse' \| 'none'` | any available pointer matches |
|
|
164
|
+
| `colorGamut` | `'srgb' \| 'p3' \| 'rec2020'` | the display covers the gamut |
|
|
130
165
|
|
|
131
|
-
|
|
132
|
-
| ------------------ | -------------------------------------------------- | ----------------- | --------------------------------------------------------- |
|
|
133
|
-
| `matchMediaSignal` | `query: string, options?: CreateMediaQueryOptions` | `Signal<boolean>` | Provides a signal representing the state of a media query |
|
|
166
|
+
### Custom queries
|
|
134
167
|
|
|
135
|
-
|
|
168
|
+
For anything without a dedicated helper, pass a raw CSS media query:
|
|
136
169
|
|
|
137
170
|
```ts
|
|
138
|
-
import { Signal } from '@angular/core';
|
|
139
171
|
import { matchMediaSignal } from 'ngx-mq';
|
|
140
172
|
|
|
141
|
-
|
|
142
|
-
export const isLandscape = (): Signal<boolean> => matchMediaSignal('(orientation: landscape)');
|
|
173
|
+
readonly isRetina = matchMediaSignal('(min-resolution: 2dppx)');
|
|
143
174
|
```
|
|
144
175
|
|
|
145
|
-
|
|
176
|
+
### Composition
|
|
146
177
|
|
|
147
|
-
|
|
178
|
+
Combine boolean signals into derived ones. Combinators work at the signal level, so the underlying
|
|
179
|
+
listeners stay shared and are still cleaned up automatically.
|
|
148
180
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `provideMaterialBreakpoints()` | none | Registers the default Material 2 breakpoints. |
|
|
155
|
-
| `provideBreakpointEpsilon()` | `epsilon: number` | Sets the epsilon threshold used when comparing breakpoint values. |
|
|
156
|
-
| `provideSsrValue()` | `value: boolean` | Defines the static signal value used during SSR, since media queries are not available on the server. Defaults to `false`. |
|
|
181
|
+
| Helper | Arguments | `true` when |
|
|
182
|
+
| ------ | ---------------------------------- | ------------------------------------------ |
|
|
183
|
+
| `and` | `...conditions: Signal<boolean>[]` | every condition is `true` (empty: `true`) |
|
|
184
|
+
| `or` | `...conditions: Signal<boolean>[]` | any condition is `true` (empty: `false`) |
|
|
185
|
+
| `not` | `condition: Signal<boolean>` | the condition is `false` |
|
|
157
186
|
|
|
158
|
-
|
|
187
|
+
```ts
|
|
188
|
+
import { and, or, not, up, down, hover, orientation, reducedMotion } from 'ngx-mq';
|
|
159
189
|
|
|
160
|
-
|
|
190
|
+
// Large screen, in landscape, with a hover-capable pointer
|
|
191
|
+
readonly isLandscapeDesktop = and(up('lg'), orientation('landscape'), hover());
|
|
161
192
|
|
|
162
|
-
|
|
163
|
-
|
|
193
|
+
// Small screens OR a reduced-motion preference
|
|
194
|
+
readonly prefersSimpleUi = or(down('md'), reducedMotion());
|
|
164
195
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
196
|
+
// Devices without hover (touch-like): `hover()` has no direct inverse helper
|
|
197
|
+
readonly isTouchLike = not(hover());
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Providers
|
|
201
|
+
|
|
202
|
+
Each returns a standard Angular `Provider` you can register at any injector level.
|
|
203
|
+
|
|
204
|
+
| Provider | Argument | Description |
|
|
205
|
+
| ------------------------------- | -------------------- | ----------------------------------------------------- |
|
|
206
|
+
| `provideBreakpoints` | `bps: MqBreakpoints` | Registers a custom breakpoint map. |
|
|
207
|
+
| `provideTailwindBreakpoints` | none | Registers the Tailwind preset. |
|
|
208
|
+
| `provideBootstrapBreakpoints` | none | Registers the Bootstrap preset. |
|
|
209
|
+
| `provideMaterialBreakpoints` | none | Registers the Material 2 preset. |
|
|
210
|
+
| `provideBreakpointEpsilon` | `epsilon: number` | Sets the exclusive-bound epsilon (default `0.02`). |
|
|
211
|
+
| `provideSsrValue` | `value: boolean` | Sets the value signals report during SSR (default `false`). |
|
|
170
212
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
213
|
+
> **Tip:** To register these as environment providers, wrap them with
|
|
214
|
+
> [`makeEnvironmentProviders`](https://angular.dev/api/core/makeEnvironmentProviders).
|
|
215
|
+
|
|
216
|
+
### Options
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
interface CreateMediaQueryOptions {
|
|
220
|
+
/** Value the signal reports during SSR. Overrides the app-wide `provideSsrValue`. */
|
|
221
|
+
ssrValue?: boolean;
|
|
222
|
+
/** Debug name shown for the signal in Angular DevTools. */
|
|
174
223
|
debugName?: string;
|
|
175
224
|
}
|
|
225
|
+
```
|
|
176
226
|
|
|
177
|
-
|
|
227
|
+
### Types
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
type MqBreakpoints = Record<string, number>;
|
|
231
|
+
|
|
232
|
+
type DisplayModeOption =
|
|
178
233
|
| 'browser'
|
|
179
234
|
| 'fullscreen'
|
|
180
235
|
| 'standalone'
|
|
@@ -183,6 +238,33 @@ export type DisplayModeOption =
|
|
|
183
238
|
| 'picture-in-picture';
|
|
184
239
|
```
|
|
185
240
|
|
|
241
|
+
## Server-side rendering
|
|
242
|
+
|
|
243
|
+
`matchMedia` does not exist on the server, so during SSR every signal returns a static value and
|
|
244
|
+
no listeners are created. Set the default with `provideSsrValue`, or override it per call:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
provideSsrValue(false); // app-wide default
|
|
248
|
+
up('lg', { ssrValue: true }); // per-call override
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Once the app hydrates in the browser, each signal switches to the live query result.
|
|
252
|
+
|
|
253
|
+
## How it works
|
|
254
|
+
|
|
255
|
+
`ngx-mq` keeps a single `MediaQueryList` and writable signal per unique query in a global registry
|
|
256
|
+
(Multiton + Flyweight). Callers share that signal, and a reference count tied to `DestroyRef`
|
|
257
|
+
removes the listener and registry entry once the last consumer is destroyed. No manual cleanup, no
|
|
258
|
+
duplicate listeners.
|
|
259
|
+
|
|
260
|
+
## Contributing
|
|
261
|
+
|
|
262
|
+
Contributions are welcome. See [CONTRIBUTING.md](https://github.com/martsinlabs/ngx-mq/blob/main/CONTRIBUTING.md).
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
[MIT](https://github.com/martsinlabs/ngx-mq/blob/main/LICENSE) © Martsin Labs
|
|
267
|
+
|
|
186
268
|
## Sponsors
|
|
187
269
|
|
|
188
270
|
<table>
|
|
@@ -192,7 +274,7 @@ export type DisplayModeOption =
|
|
|
192
274
|
<img
|
|
193
275
|
src="https://raw.githubusercontent.com/getsentry/sentry/7b65f0f23d7eb5ccc035b12776bf5d8f3d9f8965/static/images/logo-sentry.svg"
|
|
194
276
|
width="120"
|
|
195
|
-
alt="Sentry
|
|
277
|
+
alt="Sentry" />
|
|
196
278
|
</p>
|
|
197
279
|
<p>
|
|
198
280
|
<a href="https://sentry.io" target="_blank"><strong>Sentry</strong></a>
|
|
@@ -202,7 +284,3 @@ export type DisplayModeOption =
|
|
|
202
284
|
</td>
|
|
203
285
|
</tr>
|
|
204
286
|
</table>
|
|
205
|
-
|
|
206
|
-
## Contributing
|
|
207
|
-
|
|
208
|
-
[CONTRIBUTING.md](https://github.com/martsinlabs/ngx-mq/blob/main/CONTRIBUTING.md)
|