react-mobile-viewport 0.1.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 +21 -0
- package/README.md +270 -0
- package/dist/index.cjs +158 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +63 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/script.cjs +35 -0
- package/dist/script.cjs.map +1 -0
- package/dist/script.d.cts +9 -0
- package/dist/script.d.ts +9 -0
- package/dist/script.js +32 -0
- package/dist/script.js.map +1 -0
- package/package.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# react-mobile-viewport
|
|
2
|
+
|
|
3
|
+
SSR-safe React hooks that tell you whether the viewport is mobile, tablet, or desktop. Built for **React 18+** and **Next.js App Router**.
|
|
4
|
+
|
|
5
|
+
It uses CSS `matchMedia` (not user-agent sniffing) and React’s `useSyncExternalStore`, so server HTML and the first client render stay in sync.
|
|
6
|
+
|
|
7
|
+
## Why this approach
|
|
8
|
+
|
|
9
|
+
| Approach | Use it? | Why |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `window.matchMedia('(max-width: 767px)')` | Yes | Same source of truth as CSS. Updates only when the breakpoint is crossed. |
|
|
12
|
+
| `window.innerWidth` on every resize | Only if you need exact pixels | Extra renders. Still fine behind `useViewportSize`. |
|
|
13
|
+
| User-Agent parsing | Avoid as the primary signal | Tablets, “Request Desktop Site”, and privacy features make it unreliable. |
|
|
14
|
+
| CSS `@media` only | Best for layout | JS should switch *behavior*, not duplicate your entire layout. |
|
|
15
|
+
|
|
16
|
+
**Hydration rule:** the value rendered on the server must match the first client render. This package does that with `useSyncExternalStore` and an explicit SSR fallback (`false` / `"desktop"` unless you pass a cookie-based hint).
|
|
17
|
+
|
|
18
|
+
## Install (after you publish)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install react-mobile-viewport
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Peer dependencies: `react` and `react-dom` >= 18.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### React (client)
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { useIsMobile } from "react-mobile-viewport";
|
|
32
|
+
|
|
33
|
+
export function Nav() {
|
|
34
|
+
const isMobile = useIsMobile();
|
|
35
|
+
|
|
36
|
+
return isMobile ? <MobileMenu /> : <DesktopMenu />;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Custom breakpoint (widths **below** 1024px count as mobile):
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
const isMobile = useIsMobile({ breakpoint: 1024 });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Breakpoints
|
|
47
|
+
|
|
48
|
+
Default: mobile `< 768`, tablet `768–1023`, desktop `>= 1024`.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { useBreakpoint } from "react-mobile-viewport";
|
|
52
|
+
|
|
53
|
+
export function LayoutSwitch() {
|
|
54
|
+
const { isMobile, isTablet, isDesktop, breakpoint } = useBreakpoint();
|
|
55
|
+
return <p>{breakpoint}</p>;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Arbitrary media queries
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { useMediaQuery } from "react-mobile-viewport";
|
|
63
|
+
|
|
64
|
+
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
|
|
65
|
+
const isPortrait = useMediaQuery("(orientation: portrait)");
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Viewport size (only when you need pixels)
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useViewportSize } from "react-mobile-viewport";
|
|
72
|
+
|
|
73
|
+
const { width, height } = useViewportSize();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Prefer `useIsMobile` / `useBreakpoint` for UI branching. Size updates on every resize.
|
|
77
|
+
|
|
78
|
+
### Next.js App Router
|
|
79
|
+
|
|
80
|
+
Hooks use the DOM, so call them from a **Client Component**:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
"use client";
|
|
84
|
+
|
|
85
|
+
import { useIsMobile } from "react-mobile-viewport";
|
|
86
|
+
|
|
87
|
+
export function DeviceLabel() {
|
|
88
|
+
const isMobile = useIsMobile();
|
|
89
|
+
return <span>{isMobile ? "Mobile" : "Desktop"}</span>;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
You can import that component from a Server Component. Do **not** import the hooks directly in a Server Component.
|
|
94
|
+
|
|
95
|
+
### Optional: avoid a desktop flash on mobile
|
|
96
|
+
|
|
97
|
+
Without a hint, SSR assumes desktop (`false`). Mobile users may see desktop UI for one frame.
|
|
98
|
+
|
|
99
|
+
1. Set a cookie before paint with the server-safe helper.
|
|
100
|
+
2. Read it in the root layout and pass it into `ViewportProvider`.
|
|
101
|
+
|
|
102
|
+
`app/layout.tsx`:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { cookies } from "next/headers";
|
|
106
|
+
import {
|
|
107
|
+
getViewportCookieScript,
|
|
108
|
+
parseViewportCookie,
|
|
109
|
+
} from "react-mobile-viewport/script";
|
|
110
|
+
import { ViewportProvider } from "react-mobile-viewport";
|
|
111
|
+
|
|
112
|
+
export default async function RootLayout({
|
|
113
|
+
children,
|
|
114
|
+
}: {
|
|
115
|
+
children: React.ReactNode;
|
|
116
|
+
}) {
|
|
117
|
+
const cookieStore = await cookies();
|
|
118
|
+
const ssrIsMobile = parseViewportCookie(
|
|
119
|
+
cookieStore.get("viewport")?.value,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<html lang="en">
|
|
124
|
+
<head>
|
|
125
|
+
<script
|
|
126
|
+
dangerouslySetInnerHTML={{ __html: getViewportCookieScript() }}
|
|
127
|
+
/>
|
|
128
|
+
</head>
|
|
129
|
+
<body>
|
|
130
|
+
<ViewportProvider ssrIsMobile={ssrIsMobile ?? false}>
|
|
131
|
+
{children}
|
|
132
|
+
</ViewportProvider>
|
|
133
|
+
</body>
|
|
134
|
+
</html>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Import `getViewportCookieScript` from `react-mobile-viewport/script`, not the root entry. The root entry is marked `"use client"`.
|
|
140
|
+
|
|
141
|
+
## API
|
|
142
|
+
|
|
143
|
+
| Export | Returns | Notes |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `useIsMobile({ breakpoint, ssrIsMobile })` | `boolean` | Default breakpoint `768`. |
|
|
146
|
+
| `useBreakpoint({ mobileMaxWidth, tabletMaxWidth, ssrBreakpoint })` | `{ isMobile, isTablet, isDesktop, breakpoint }` | |
|
|
147
|
+
| `useMediaQuery(query, { ssrMatch })` | `boolean` | Primitive hook. |
|
|
148
|
+
| `useViewportSize({ ssrWidth, ssrHeight })` | `{ width, height }` | Resize listener, rAF-batched. |
|
|
149
|
+
| `ViewportProvider` | context | Share breakpoint + SSR defaults. |
|
|
150
|
+
| `getViewportCookieScript()` | `string` | From `/script`. Inline in `<head>`. |
|
|
151
|
+
| `parseViewportCookie(value)` | `boolean \| undefined` | From `/script`. |
|
|
152
|
+
|
|
153
|
+
Hooks work **without** a provider. The provider is only for shared defaults.
|
|
154
|
+
|
|
155
|
+
## Local development
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npm install
|
|
159
|
+
npm test
|
|
160
|
+
npm run build
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Publish to npm — step by step
|
|
164
|
+
|
|
165
|
+
### 1. Create an npm account
|
|
166
|
+
|
|
167
|
+
Sign up at [https://www.npmjs.com/signup](https://www.npmjs.com/signup) and enable 2FA.
|
|
168
|
+
|
|
169
|
+
### 2. Pick a unique name
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npm view react-mobile-viewport
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
If that prints package info, the name is taken. Change `"name"` in `package.json`. Scoped names are safer:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"name": "@your-name/react-mobile-viewport"
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Fill in `"author"`, `"repository"`, and `"homepage"` before you publish.
|
|
184
|
+
|
|
185
|
+
### 3. Log in
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm login
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Confirm with:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npm whoami
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 4. Verify the tarball
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
npm run build
|
|
201
|
+
npm pack --dry-run
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
You should see `dist/`, `package.json`, `README.md`, and `LICENSE` — not `src/` or `node_modules/`.
|
|
205
|
+
|
|
206
|
+
### 5. Publish
|
|
207
|
+
|
|
208
|
+
Unscoped public package:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npm publish --access public
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Scoped package (`@your-name/...`) must use `--access public` unless you have a paid org.
|
|
215
|
+
|
|
216
|
+
`prepublishOnly` already runs typecheck, tests, and build.
|
|
217
|
+
|
|
218
|
+
### 6. First-time checklist
|
|
219
|
+
|
|
220
|
+
- [ ] Package name is unique
|
|
221
|
+
- [ ] Version is `0.1.0` (or `1.0.0` when you are ready)
|
|
222
|
+
- [ ] README install command matches the name
|
|
223
|
+
- [ ] `npm pack --dry-run` looks right
|
|
224
|
+
- [ ] You are logged in (`npm whoami`)
|
|
225
|
+
- [ ] 2FA code is ready
|
|
226
|
+
|
|
227
|
+
### 7. Later versions
|
|
228
|
+
|
|
229
|
+
Follow [semver](https://semver.org/):
|
|
230
|
+
|
|
231
|
+
- Bug fix → `0.1.1`
|
|
232
|
+
- New compatible API → `0.2.0`
|
|
233
|
+
- Breaking change → `1.0.0`
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
npm version patch
|
|
237
|
+
npm publish --access public
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 8. Use it in a Next.js app
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
npm install react-mobile-viewport
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
If you test locally before publishing:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npm pack
|
|
250
|
+
# copies a .tgz next to package.json
|
|
251
|
+
cd /path/to/your-next-app
|
|
252
|
+
npm install /path/to/react-mobile-viewport-0.1.0.tgz
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Project layout
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
src/index.ts Client entry (hooks + provider)
|
|
259
|
+
src/script.ts Server-safe cookie helpers
|
|
260
|
+
src/use-is-mobile.ts
|
|
261
|
+
src/use-breakpoint.ts
|
|
262
|
+
src/use-media-query.ts
|
|
263
|
+
src/use-viewport-size.ts
|
|
264
|
+
src/provider.tsx
|
|
265
|
+
tsup.config.ts Dual ESM + CJS build, "use client" banner
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/constants.ts
|
|
8
|
+
var DEFAULT_MOBILE_MAX_WIDTH = 768;
|
|
9
|
+
var DEFAULT_TABLET_MAX_WIDTH = 1024;
|
|
10
|
+
var DEFAULT_VIEWPORT_COOKIE = "viewport";
|
|
11
|
+
var defaultConfig = {
|
|
12
|
+
breakpoint: DEFAULT_MOBILE_MAX_WIDTH,
|
|
13
|
+
tabletMaxWidth: DEFAULT_TABLET_MAX_WIDTH,
|
|
14
|
+
ssrIsMobile: false,
|
|
15
|
+
ssrBreakpoint: "desktop",
|
|
16
|
+
ssrWidth: 0,
|
|
17
|
+
ssrHeight: 0
|
|
18
|
+
};
|
|
19
|
+
var ViewportContext = react.createContext(defaultConfig);
|
|
20
|
+
function ViewportProvider({
|
|
21
|
+
children,
|
|
22
|
+
breakpoint = defaultConfig.breakpoint,
|
|
23
|
+
tabletMaxWidth = defaultConfig.tabletMaxWidth,
|
|
24
|
+
ssrIsMobile = defaultConfig.ssrIsMobile,
|
|
25
|
+
ssrBreakpoint = ssrIsMobile ? "mobile" : defaultConfig.ssrBreakpoint,
|
|
26
|
+
ssrWidth = defaultConfig.ssrWidth,
|
|
27
|
+
ssrHeight = defaultConfig.ssrHeight
|
|
28
|
+
}) {
|
|
29
|
+
const value = react.useMemo(
|
|
30
|
+
() => ({
|
|
31
|
+
breakpoint,
|
|
32
|
+
tabletMaxWidth,
|
|
33
|
+
ssrIsMobile,
|
|
34
|
+
ssrBreakpoint,
|
|
35
|
+
ssrWidth,
|
|
36
|
+
ssrHeight
|
|
37
|
+
}),
|
|
38
|
+
[breakpoint, tabletMaxWidth, ssrIsMobile, ssrBreakpoint, ssrWidth, ssrHeight]
|
|
39
|
+
);
|
|
40
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ViewportContext.Provider, { value, children });
|
|
41
|
+
}
|
|
42
|
+
function useViewportConfig() {
|
|
43
|
+
return react.useContext(ViewportContext);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/media-query.ts
|
|
47
|
+
var subscribeCache = /* @__PURE__ */ new Map();
|
|
48
|
+
function attachMediaListener(mql, onChange) {
|
|
49
|
+
if (typeof mql.addEventListener === "function") {
|
|
50
|
+
mql.addEventListener("change", onChange);
|
|
51
|
+
return () => mql.removeEventListener("change", onChange);
|
|
52
|
+
}
|
|
53
|
+
mql.addListener(onChange);
|
|
54
|
+
return () => mql.removeListener(onChange);
|
|
55
|
+
}
|
|
56
|
+
function getMediaQuerySubscribe(query) {
|
|
57
|
+
const cached = subscribeCache.get(query);
|
|
58
|
+
if (cached) return cached;
|
|
59
|
+
const subscribe = (onStoreChange) => {
|
|
60
|
+
const mql = window.matchMedia(query);
|
|
61
|
+
return attachMediaListener(mql, onStoreChange);
|
|
62
|
+
};
|
|
63
|
+
subscribeCache.set(query, subscribe);
|
|
64
|
+
return subscribe;
|
|
65
|
+
}
|
|
66
|
+
function getMediaQuerySnapshot(query) {
|
|
67
|
+
return window.matchMedia(query).matches;
|
|
68
|
+
}
|
|
69
|
+
function maxWidthQuery(maxWidth) {
|
|
70
|
+
return `(max-width: ${maxWidth - 1}px)`;
|
|
71
|
+
}
|
|
72
|
+
function betweenWidthQuery(minWidth, maxWidth) {
|
|
73
|
+
return `(min-width: ${minWidth}px) and (max-width: ${maxWidth - 1}px)`;
|
|
74
|
+
}
|
|
75
|
+
function useMediaQuery(query, options = {}) {
|
|
76
|
+
const ssrMatch = options.ssrMatch ?? false;
|
|
77
|
+
const subscribe = getMediaQuerySubscribe(query);
|
|
78
|
+
const getSnapshot = react.useCallback(
|
|
79
|
+
() => getMediaQuerySnapshot(query),
|
|
80
|
+
[query]
|
|
81
|
+
);
|
|
82
|
+
const getServerSnapshot = react.useCallback(() => ssrMatch, [ssrMatch]);
|
|
83
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/use-breakpoint.ts
|
|
87
|
+
function useBreakpoint(options = {}) {
|
|
88
|
+
const config = useViewportConfig();
|
|
89
|
+
const mobileMaxWidth = options.mobileMaxWidth ?? config.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;
|
|
90
|
+
const tabletMaxWidth = options.tabletMaxWidth ?? config.tabletMaxWidth ?? DEFAULT_TABLET_MAX_WIDTH;
|
|
91
|
+
const ssrBreakpoint = options.ssrBreakpoint ?? config.ssrBreakpoint;
|
|
92
|
+
const isMobile = useMediaQuery(maxWidthQuery(mobileMaxWidth), {
|
|
93
|
+
ssrMatch: ssrBreakpoint === "mobile"
|
|
94
|
+
});
|
|
95
|
+
const isTablet = useMediaQuery(
|
|
96
|
+
betweenWidthQuery(mobileMaxWidth, tabletMaxWidth),
|
|
97
|
+
{ ssrMatch: ssrBreakpoint === "tablet" }
|
|
98
|
+
);
|
|
99
|
+
const breakpoint = isMobile ? "mobile" : isTablet ? "tablet" : "desktop";
|
|
100
|
+
return {
|
|
101
|
+
isMobile,
|
|
102
|
+
isTablet,
|
|
103
|
+
isDesktop: breakpoint === "desktop",
|
|
104
|
+
breakpoint
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/use-is-mobile.ts
|
|
109
|
+
function useIsMobile(options = {}) {
|
|
110
|
+
const config = useViewportConfig();
|
|
111
|
+
const breakpoint = options.breakpoint ?? config.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;
|
|
112
|
+
const ssrIsMobile = options.ssrIsMobile ?? config.ssrIsMobile;
|
|
113
|
+
return useMediaQuery(maxWidthQuery(breakpoint), { ssrMatch: ssrIsMobile });
|
|
114
|
+
}
|
|
115
|
+
var cachedSize = { width: 0, height: 0 };
|
|
116
|
+
function readSize() {
|
|
117
|
+
const width = window.innerWidth;
|
|
118
|
+
const height = window.innerHeight;
|
|
119
|
+
if (cachedSize.width !== width || cachedSize.height !== height) {
|
|
120
|
+
cachedSize = { width, height };
|
|
121
|
+
}
|
|
122
|
+
return cachedSize;
|
|
123
|
+
}
|
|
124
|
+
function subscribeToResize(onStoreChange) {
|
|
125
|
+
let frame = 0;
|
|
126
|
+
const onResize = () => {
|
|
127
|
+
cancelAnimationFrame(frame);
|
|
128
|
+
frame = requestAnimationFrame(onStoreChange);
|
|
129
|
+
};
|
|
130
|
+
window.addEventListener("resize", onResize);
|
|
131
|
+
return () => {
|
|
132
|
+
cancelAnimationFrame(frame);
|
|
133
|
+
window.removeEventListener("resize", onResize);
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function useViewportSize(options = {}) {
|
|
137
|
+
const config = useViewportConfig();
|
|
138
|
+
const ssrWidth = options.ssrWidth ?? config.ssrWidth;
|
|
139
|
+
const ssrHeight = options.ssrHeight ?? config.ssrHeight;
|
|
140
|
+
const serverSnapshot = react.useMemo(
|
|
141
|
+
() => ({ width: ssrWidth, height: ssrHeight }),
|
|
142
|
+
[ssrWidth, ssrHeight]
|
|
143
|
+
);
|
|
144
|
+
const getServerSnapshot = react.useCallback(() => serverSnapshot, [serverSnapshot]);
|
|
145
|
+
return react.useSyncExternalStore(subscribeToResize, readSize, getServerSnapshot);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
exports.DEFAULT_MOBILE_MAX_WIDTH = DEFAULT_MOBILE_MAX_WIDTH;
|
|
149
|
+
exports.DEFAULT_TABLET_MAX_WIDTH = DEFAULT_TABLET_MAX_WIDTH;
|
|
150
|
+
exports.DEFAULT_VIEWPORT_COOKIE = DEFAULT_VIEWPORT_COOKIE;
|
|
151
|
+
exports.ViewportProvider = ViewportProvider;
|
|
152
|
+
exports.useBreakpoint = useBreakpoint;
|
|
153
|
+
exports.useIsMobile = useIsMobile;
|
|
154
|
+
exports.useMediaQuery = useMediaQuery;
|
|
155
|
+
exports.useViewportConfig = useViewportConfig;
|
|
156
|
+
exports.useViewportSize = useViewportSize;
|
|
157
|
+
//# sourceMappingURL=index.cjs.map
|
|
158
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/provider.tsx","../src/media-query.ts","../src/use-media-query.ts","../src/use-breakpoint.ts","../src/use-is-mobile.ts","../src/use-viewport-size.ts"],"names":["createContext","useMemo","jsx","useContext","useCallback","useSyncExternalStore"],"mappings":";;;;;;AAAO,IAAM,wBAAA,GAA2B;AACjC,IAAM,wBAAA,GAA2B;AACjC,IAAM,uBAAA,GAA0B;ACKvC,IAAM,aAAA,GAAgC;AAAA,EACpC,UAAA,EAAY,wBAAA;AAAA,EACZ,cAAA,EAAgB,wBAAA;AAAA,EAChB,WAAA,EAAa,KAAA;AAAA,EACb,aAAA,EAAe,SAAA;AAAA,EACf,QAAA,EAAU,CAAA;AAAA,EACV,SAAA,EAAW;AACb,CAAA;AAEA,IAAM,eAAA,GAAkBA,oBAA8B,aAAa,CAAA;AAE5D,SAAS,gBAAA,CAAiB;AAAA,EAC/B,QAAA;AAAA,EACA,aAAa,aAAA,CAAc,UAAA;AAAA,EAC3B,iBAAiB,aAAA,CAAc,cAAA;AAAA,EAC/B,cAAc,aAAA,CAAc,WAAA;AAAA,EAC5B,aAAA,GAAgB,WAAA,GAAc,QAAA,GAAW,aAAA,CAAc,aAAA;AAAA,EACvD,WAAW,aAAA,CAAc,QAAA;AAAA,EACzB,YAAY,aAAA,CAAc;AAC5B,CAAA,EAA0B;AACxB,EAAA,MAAM,KAAA,GAAQC,aAAA;AAAA,IACZ,OAAuB;AAAA,MACrB,UAAA;AAAA,MACA,cAAA;AAAA,MACA,WAAA;AAAA,MACA,aAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA,CAAC,UAAA,EAAY,cAAA,EAAgB,WAAA,EAAa,aAAA,EAAe,UAAU,SAAS;AAAA,GAC9E;AAEA,EAAA,uBACEC,cAAA,CAAC,eAAA,CAAgB,QAAA,EAAhB,EAAyB,OAAe,QAAA,EAAS,CAAA;AAEtD;AAEO,SAAS,iBAAA,GAAoC;AAClD,EAAA,OAAOC,iBAAW,eAAe,CAAA;AACnC;;;AC5CA,IAAM,cAAA,uBAAqB,GAAA,EAAuB;AAElD,SAAS,mBAAA,CACP,KACA,QAAA,EACY;AACZ,EAAA,IAAI,OAAO,GAAA,CAAI,gBAAA,KAAqB,UAAA,EAAY;AAC9C,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACvC,IAAA,OAAO,MAAM,GAAA,CAAI,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,EACzD;AAEA,EAAA,GAAA,CAAI,YAAY,QAAQ,CAAA;AACxB,EAAA,OAAO,MAAM,GAAA,CAAI,cAAA,CAAe,QAAQ,CAAA;AAC1C;AAEO,SAAS,uBAAuB,KAAA,EAA0B;AAC/D,EAAA,MAAM,MAAA,GAAS,cAAA,CAAe,GAAA,CAAI,KAAK,CAAA;AACvC,EAAA,IAAI,QAAQ,OAAO,MAAA;AAEnB,EAAA,MAAM,SAAA,GAAuB,CAAC,aAAA,KAAkB;AAC9C,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AACnC,IAAA,OAAO,mBAAA,CAAoB,KAAK,aAAa,CAAA;AAAA,EAC/C,CAAA;AAEA,EAAA,cAAA,CAAe,GAAA,CAAI,OAAO,SAAS,CAAA;AACnC,EAAA,OAAO,SAAA;AACT;AAEO,SAAS,sBAAsB,KAAA,EAAwB;AAC5D,EAAA,OAAO,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA,CAAE,OAAA;AAClC;AAEO,SAAS,cAAc,QAAA,EAA0B;AACtD,EAAA,OAAO,CAAA,YAAA,EAAe,WAAW,CAAC,CAAA,GAAA,CAAA;AACpC;AAEO,SAAS,iBAAA,CAAkB,UAAkB,QAAA,EAA0B;AAC5E,EAAA,OAAO,CAAA,YAAA,EAAe,QAAQ,CAAA,oBAAA,EAAuB,QAAA,GAAW,CAAC,CAAA,GAAA,CAAA;AACnE;ACpCO,SAAS,aAAA,CACd,KAAA,EACA,OAAA,GAAgC,EAAC,EACxB;AACT,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,KAAA;AACrC,EAAA,MAAM,SAAA,GAAY,uBAAuB,KAAK,CAAA;AAC9C,EAAA,MAAM,WAAA,GAAcC,iBAAA;AAAA,IAClB,MAAM,sBAAsB,KAAK,CAAA;AAAA,IACjC,CAAC,KAAK;AAAA,GACR;AACA,EAAA,MAAM,oBAAoBA,iBAAA,CAAY,MAAM,QAAA,EAAU,CAAC,QAAQ,CAAC,CAAA;AAEhE,EAAA,OAAOC,0BAAA,CAAqB,SAAA,EAAW,WAAA,EAAa,iBAAiB,CAAA;AACvE;;;ACRO,SAAS,aAAA,CACd,OAAA,GAAgC,EAAC,EAMjC;AACA,EAAA,MAAM,SAAS,iBAAA,EAAkB;AACjC,EAAA,MAAM,cAAA,GACJ,OAAA,CAAQ,cAAA,IAAkB,MAAA,CAAO,UAAA,IAAc,wBAAA;AACjD,EAAA,MAAM,cAAA,GACJ,OAAA,CAAQ,cAAA,IAAkB,MAAA,CAAO,cAAA,IAAkB,wBAAA;AACrD,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,aAAA,IAAiB,MAAA,CAAO,aAAA;AAEtD,EAAA,MAAM,QAAA,GAAW,aAAA,CAAc,aAAA,CAAc,cAAc,CAAA,EAAG;AAAA,IAC5D,UAAU,aAAA,KAAkB;AAAA,GAC7B,CAAA;AACD,EAAA,MAAM,QAAA,GAAW,aAAA;AAAA,IACf,iBAAA,CAAkB,gBAAgB,cAAc,CAAA;AAAA,IAChD,EAAE,QAAA,EAAU,aAAA,KAAkB,QAAA;AAAS,GACzC;AAEA,EAAA,MAAM,UAAA,GAA6B,QAAA,GAC/B,QAAA,GACA,QAAA,GACE,QAAA,GACA,SAAA;AAEN,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAW,UAAA,KAAe,SAAA;AAAA,IAC1B;AAAA,GACF;AACF;;;ACtCO,SAAS,WAAA,CAAY,OAAA,GAA8B,EAAC,EAAY;AACrE,EAAA,MAAM,SAAS,iBAAA,EAAkB;AACjC,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,UAAA,IAAc,MAAA,CAAO,UAAA,IAAc,wBAAA;AAC9D,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,WAAA,IAAe,MAAA,CAAO,WAAA;AAElD,EAAA,OAAO,cAAc,aAAA,CAAc,UAAU,GAAG,EAAE,QAAA,EAAU,aAAa,CAAA;AAC3E;ACRA,IAAI,UAAA,GAA2B,EAAE,KAAA,EAAO,CAAA,EAAG,QAAQ,CAAA,EAAE;AAErD,SAAS,QAAA,GAAyB;AAChC,EAAA,MAAM,QAAQ,MAAA,CAAO,UAAA;AACrB,EAAA,MAAM,SAAS,MAAA,CAAO,WAAA;AAEtB,EAAA,IAAI,UAAA,CAAW,KAAA,KAAU,KAAA,IAAS,UAAA,CAAW,WAAW,MAAA,EAAQ;AAC9D,IAAA,UAAA,GAAa,EAAE,OAAO,MAAA,EAAO;AAAA,EAC/B;AAEA,EAAA,OAAO,UAAA;AACT;AAEA,SAAS,kBAAkB,aAAA,EAAuC;AAChE,EAAA,IAAI,KAAA,GAAQ,CAAA;AAEZ,EAAA,MAAM,WAAW,MAAM;AACrB,IAAA,oBAAA,CAAqB,KAAK,CAAA;AAC1B,IAAA,KAAA,GAAQ,sBAAsB,aAAa,CAAA;AAAA,EAC7C,CAAA;AAEA,EAAA,MAAA,CAAO,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AAC1C,EAAA,OAAO,MAAM;AACX,IAAA,oBAAA,CAAqB,KAAK,CAAA;AAC1B,IAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,QAAQ,CAAA;AAAA,EAC/C,CAAA;AACF;AAEO,SAAS,eAAA,CACd,OAAA,GAAkC,EAAC,EACrB;AACd,EAAA,MAAM,SAAS,iBAAA,EAAkB;AACjC,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,QAAA,IAAY,MAAA,CAAO,QAAA;AAC5C,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,SAAA,IAAa,MAAA,CAAO,SAAA;AAC9C,EAAA,MAAM,cAAA,GAAiBJ,aAAAA;AAAA,IACrB,OAAqB,EAAE,KAAA,EAAO,QAAA,EAAU,QAAQ,SAAA,EAAU,CAAA;AAAA,IAC1D,CAAC,UAAU,SAAS;AAAA,GACtB;AACA,EAAA,MAAM,oBAAoBG,iBAAAA,CAAY,MAAM,cAAA,EAAgB,CAAC,cAAc,CAAC,CAAA;AAE5E,EAAA,OAAOC,0BAAAA,CAAqB,iBAAA,EAAmB,QAAA,EAAU,iBAAiB,CAAA;AAC5E","file":"index.cjs","sourcesContent":["export const DEFAULT_MOBILE_MAX_WIDTH = 768;\nexport const DEFAULT_TABLET_MAX_WIDTH = 1024;\nexport const DEFAULT_VIEWPORT_COOKIE = \"viewport\";\n","import { createContext, useContext, useMemo } from \"react\";\nimport {\n DEFAULT_MOBILE_MAX_WIDTH,\n DEFAULT_TABLET_MAX_WIDTH,\n} from \"./constants\";\nimport type { ViewportConfig, ViewportProviderProps } from \"./types\";\n\nconst defaultConfig: ViewportConfig = {\n breakpoint: DEFAULT_MOBILE_MAX_WIDTH,\n tabletMaxWidth: DEFAULT_TABLET_MAX_WIDTH,\n ssrIsMobile: false,\n ssrBreakpoint: \"desktop\",\n ssrWidth: 0,\n ssrHeight: 0,\n};\n\nconst ViewportContext = createContext<ViewportConfig>(defaultConfig);\n\nexport function ViewportProvider({\n children,\n breakpoint = defaultConfig.breakpoint,\n tabletMaxWidth = defaultConfig.tabletMaxWidth,\n ssrIsMobile = defaultConfig.ssrIsMobile,\n ssrBreakpoint = ssrIsMobile ? \"mobile\" : defaultConfig.ssrBreakpoint,\n ssrWidth = defaultConfig.ssrWidth,\n ssrHeight = defaultConfig.ssrHeight,\n}: ViewportProviderProps) {\n const value = useMemo(\n (): ViewportConfig => ({\n breakpoint,\n tabletMaxWidth,\n ssrIsMobile,\n ssrBreakpoint,\n ssrWidth,\n ssrHeight,\n }),\n [breakpoint, tabletMaxWidth, ssrIsMobile, ssrBreakpoint, ssrWidth, ssrHeight],\n );\n\n return (\n <ViewportContext.Provider value={value}>{children}</ViewportContext.Provider>\n );\n}\n\nexport function useViewportConfig(): ViewportConfig {\n return useContext(ViewportContext);\n}\n","type Subscribe = (onStoreChange: () => void) => () => void;\n\nconst subscribeCache = new Map<string, Subscribe>();\n\nfunction attachMediaListener(\n mql: MediaQueryList,\n onChange: () => void,\n): () => void {\n if (typeof mql.addEventListener === \"function\") {\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n }\n\n mql.addListener(onChange);\n return () => mql.removeListener(onChange);\n}\n\nexport function getMediaQuerySubscribe(query: string): Subscribe {\n const cached = subscribeCache.get(query);\n if (cached) return cached;\n\n const subscribe: Subscribe = (onStoreChange) => {\n const mql = window.matchMedia(query);\n return attachMediaListener(mql, onStoreChange);\n };\n\n subscribeCache.set(query, subscribe);\n return subscribe;\n}\n\nexport function getMediaQuerySnapshot(query: string): boolean {\n return window.matchMedia(query).matches;\n}\n\nexport function maxWidthQuery(maxWidth: number): string {\n return `(max-width: ${maxWidth - 1}px)`;\n}\n\nexport function betweenWidthQuery(minWidth: number, maxWidth: number): string {\n return `(min-width: ${minWidth}px) and (max-width: ${maxWidth - 1}px)`;\n}\n","import { useCallback, useSyncExternalStore } from \"react\";\nimport { getMediaQuerySnapshot, getMediaQuerySubscribe } from \"./media-query\";\nimport type { UseMediaQueryOptions } from \"./types\";\n\nexport function useMediaQuery(\n query: string,\n options: UseMediaQueryOptions = {},\n): boolean {\n const ssrMatch = options.ssrMatch ?? false;\n const subscribe = getMediaQuerySubscribe(query);\n const getSnapshot = useCallback(\n () => getMediaQuerySnapshot(query),\n [query],\n );\n const getServerSnapshot = useCallback(() => ssrMatch, [ssrMatch]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import {\n DEFAULT_MOBILE_MAX_WIDTH,\n DEFAULT_TABLET_MAX_WIDTH,\n} from \"./constants\";\nimport { betweenWidthQuery, maxWidthQuery } from \"./media-query\";\nimport { useViewportConfig } from \"./provider\";\nimport type { BreakpointName, UseBreakpointOptions } from \"./types\";\nimport { useMediaQuery } from \"./use-media-query\";\n\nexport function useBreakpoint(\n options: UseBreakpointOptions = {},\n): {\n isMobile: boolean;\n isTablet: boolean;\n isDesktop: boolean;\n breakpoint: BreakpointName;\n} {\n const config = useViewportConfig();\n const mobileMaxWidth =\n options.mobileMaxWidth ?? config.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;\n const tabletMaxWidth =\n options.tabletMaxWidth ?? config.tabletMaxWidth ?? DEFAULT_TABLET_MAX_WIDTH;\n const ssrBreakpoint = options.ssrBreakpoint ?? config.ssrBreakpoint;\n\n const isMobile = useMediaQuery(maxWidthQuery(mobileMaxWidth), {\n ssrMatch: ssrBreakpoint === \"mobile\",\n });\n const isTablet = useMediaQuery(\n betweenWidthQuery(mobileMaxWidth, tabletMaxWidth),\n { ssrMatch: ssrBreakpoint === \"tablet\" },\n );\n\n const breakpoint: BreakpointName = isMobile\n ? \"mobile\"\n : isTablet\n ? \"tablet\"\n : \"desktop\";\n\n return {\n isMobile,\n isTablet,\n isDesktop: breakpoint === \"desktop\",\n breakpoint,\n };\n}\n","import { DEFAULT_MOBILE_MAX_WIDTH } from \"./constants\";\nimport { maxWidthQuery } from \"./media-query\";\nimport { useViewportConfig } from \"./provider\";\nimport type { UseIsMobileOptions } from \"./types\";\nimport { useMediaQuery } from \"./use-media-query\";\n\nexport function useIsMobile(options: UseIsMobileOptions = {}): boolean {\n const config = useViewportConfig();\n const breakpoint = options.breakpoint ?? config.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;\n const ssrIsMobile = options.ssrIsMobile ?? config.ssrIsMobile;\n\n return useMediaQuery(maxWidthQuery(breakpoint), { ssrMatch: ssrIsMobile });\n}\n","import { useCallback, useMemo, useSyncExternalStore } from \"react\";\nimport { useViewportConfig } from \"./provider\";\nimport type { UseViewportSizeOptions, ViewportSize } from \"./types\";\n\nlet cachedSize: ViewportSize = { width: 0, height: 0 };\n\nfunction readSize(): ViewportSize {\n const width = window.innerWidth;\n const height = window.innerHeight;\n\n if (cachedSize.width !== width || cachedSize.height !== height) {\n cachedSize = { width, height };\n }\n\n return cachedSize;\n}\n\nfunction subscribeToResize(onStoreChange: () => void): () => void {\n let frame = 0;\n\n const onResize = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(onStoreChange);\n };\n\n window.addEventListener(\"resize\", onResize);\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener(\"resize\", onResize);\n };\n}\n\nexport function useViewportSize(\n options: UseViewportSizeOptions = {},\n): ViewportSize {\n const config = useViewportConfig();\n const ssrWidth = options.ssrWidth ?? config.ssrWidth;\n const ssrHeight = options.ssrHeight ?? config.ssrHeight;\n const serverSnapshot = useMemo(\n (): ViewportSize => ({ width: ssrWidth, height: ssrHeight }),\n [ssrWidth, ssrHeight],\n );\n const getServerSnapshot = useCallback(() => serverSnapshot, [serverSnapshot]);\n\n return useSyncExternalStore(subscribeToResize, readSize, getServerSnapshot);\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
declare const DEFAULT_MOBILE_MAX_WIDTH = 768;
|
|
5
|
+
declare const DEFAULT_TABLET_MAX_WIDTH = 1024;
|
|
6
|
+
declare const DEFAULT_VIEWPORT_COOKIE = "viewport";
|
|
7
|
+
|
|
8
|
+
type BreakpointName = "mobile" | "tablet" | "desktop";
|
|
9
|
+
interface UseMediaQueryOptions {
|
|
10
|
+
/** Value used on the server and during hydration. Default: `false`. */
|
|
11
|
+
ssrMatch?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface UseIsMobileOptions {
|
|
14
|
+
/** Widths below this value (px) are mobile. Default: `768`. */
|
|
15
|
+
breakpoint?: number;
|
|
16
|
+
/** SSR/hydration fallback. Default: `false`. */
|
|
17
|
+
ssrIsMobile?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface UseBreakpointOptions {
|
|
20
|
+
/** Widths below this value (px) are mobile. Default: `768`. */
|
|
21
|
+
mobileMaxWidth?: number;
|
|
22
|
+
/** Widths below this value (px) and not mobile are tablet. Default: `1024`. */
|
|
23
|
+
tabletMaxWidth?: number;
|
|
24
|
+
/** SSR/hydration fallback. Default: `"desktop"`. */
|
|
25
|
+
ssrBreakpoint?: BreakpointName;
|
|
26
|
+
}
|
|
27
|
+
interface ViewportSize {
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
}
|
|
31
|
+
interface UseViewportSizeOptions {
|
|
32
|
+
ssrWidth?: number;
|
|
33
|
+
ssrHeight?: number;
|
|
34
|
+
}
|
|
35
|
+
interface ViewportConfig {
|
|
36
|
+
breakpoint: number;
|
|
37
|
+
tabletMaxWidth: number;
|
|
38
|
+
ssrIsMobile: boolean;
|
|
39
|
+
ssrBreakpoint: BreakpointName;
|
|
40
|
+
ssrWidth: number;
|
|
41
|
+
ssrHeight: number;
|
|
42
|
+
}
|
|
43
|
+
interface ViewportProviderProps extends Partial<ViewportConfig> {
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare function ViewportProvider({ children, breakpoint, tabletMaxWidth, ssrIsMobile, ssrBreakpoint, ssrWidth, ssrHeight, }: ViewportProviderProps): react.JSX.Element;
|
|
48
|
+
declare function useViewportConfig(): ViewportConfig;
|
|
49
|
+
|
|
50
|
+
declare function useBreakpoint(options?: UseBreakpointOptions): {
|
|
51
|
+
isMobile: boolean;
|
|
52
|
+
isTablet: boolean;
|
|
53
|
+
isDesktop: boolean;
|
|
54
|
+
breakpoint: BreakpointName;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
declare function useIsMobile(options?: UseIsMobileOptions): boolean;
|
|
58
|
+
|
|
59
|
+
declare function useMediaQuery(query: string, options?: UseMediaQueryOptions): boolean;
|
|
60
|
+
|
|
61
|
+
declare function useViewportSize(options?: UseViewportSizeOptions): ViewportSize;
|
|
62
|
+
|
|
63
|
+
export { type BreakpointName, DEFAULT_MOBILE_MAX_WIDTH, DEFAULT_TABLET_MAX_WIDTH, DEFAULT_VIEWPORT_COOKIE, type UseBreakpointOptions, type UseIsMobileOptions, type UseMediaQueryOptions, type UseViewportSizeOptions, type ViewportConfig, ViewportProvider, type ViewportProviderProps, type ViewportSize, useBreakpoint, useIsMobile, useMediaQuery, useViewportConfig, useViewportSize };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
declare const DEFAULT_MOBILE_MAX_WIDTH = 768;
|
|
5
|
+
declare const DEFAULT_TABLET_MAX_WIDTH = 1024;
|
|
6
|
+
declare const DEFAULT_VIEWPORT_COOKIE = "viewport";
|
|
7
|
+
|
|
8
|
+
type BreakpointName = "mobile" | "tablet" | "desktop";
|
|
9
|
+
interface UseMediaQueryOptions {
|
|
10
|
+
/** Value used on the server and during hydration. Default: `false`. */
|
|
11
|
+
ssrMatch?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface UseIsMobileOptions {
|
|
14
|
+
/** Widths below this value (px) are mobile. Default: `768`. */
|
|
15
|
+
breakpoint?: number;
|
|
16
|
+
/** SSR/hydration fallback. Default: `false`. */
|
|
17
|
+
ssrIsMobile?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface UseBreakpointOptions {
|
|
20
|
+
/** Widths below this value (px) are mobile. Default: `768`. */
|
|
21
|
+
mobileMaxWidth?: number;
|
|
22
|
+
/** Widths below this value (px) and not mobile are tablet. Default: `1024`. */
|
|
23
|
+
tabletMaxWidth?: number;
|
|
24
|
+
/** SSR/hydration fallback. Default: `"desktop"`. */
|
|
25
|
+
ssrBreakpoint?: BreakpointName;
|
|
26
|
+
}
|
|
27
|
+
interface ViewportSize {
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
}
|
|
31
|
+
interface UseViewportSizeOptions {
|
|
32
|
+
ssrWidth?: number;
|
|
33
|
+
ssrHeight?: number;
|
|
34
|
+
}
|
|
35
|
+
interface ViewportConfig {
|
|
36
|
+
breakpoint: number;
|
|
37
|
+
tabletMaxWidth: number;
|
|
38
|
+
ssrIsMobile: boolean;
|
|
39
|
+
ssrBreakpoint: BreakpointName;
|
|
40
|
+
ssrWidth: number;
|
|
41
|
+
ssrHeight: number;
|
|
42
|
+
}
|
|
43
|
+
interface ViewportProviderProps extends Partial<ViewportConfig> {
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare function ViewportProvider({ children, breakpoint, tabletMaxWidth, ssrIsMobile, ssrBreakpoint, ssrWidth, ssrHeight, }: ViewportProviderProps): react.JSX.Element;
|
|
48
|
+
declare function useViewportConfig(): ViewportConfig;
|
|
49
|
+
|
|
50
|
+
declare function useBreakpoint(options?: UseBreakpointOptions): {
|
|
51
|
+
isMobile: boolean;
|
|
52
|
+
isTablet: boolean;
|
|
53
|
+
isDesktop: boolean;
|
|
54
|
+
breakpoint: BreakpointName;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
declare function useIsMobile(options?: UseIsMobileOptions): boolean;
|
|
58
|
+
|
|
59
|
+
declare function useMediaQuery(query: string, options?: UseMediaQueryOptions): boolean;
|
|
60
|
+
|
|
61
|
+
declare function useViewportSize(options?: UseViewportSizeOptions): ViewportSize;
|
|
62
|
+
|
|
63
|
+
export { type BreakpointName, DEFAULT_MOBILE_MAX_WIDTH, DEFAULT_TABLET_MAX_WIDTH, DEFAULT_VIEWPORT_COOKIE, type UseBreakpointOptions, type UseIsMobileOptions, type UseMediaQueryOptions, type UseViewportSizeOptions, type ViewportConfig, ViewportProvider, type ViewportProviderProps, type ViewportSize, useBreakpoint, useIsMobile, useMediaQuery, useViewportConfig, useViewportSize };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useMemo, useContext, useCallback, useSyncExternalStore } from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/constants.ts
|
|
6
|
+
var DEFAULT_MOBILE_MAX_WIDTH = 768;
|
|
7
|
+
var DEFAULT_TABLET_MAX_WIDTH = 1024;
|
|
8
|
+
var DEFAULT_VIEWPORT_COOKIE = "viewport";
|
|
9
|
+
var defaultConfig = {
|
|
10
|
+
breakpoint: DEFAULT_MOBILE_MAX_WIDTH,
|
|
11
|
+
tabletMaxWidth: DEFAULT_TABLET_MAX_WIDTH,
|
|
12
|
+
ssrIsMobile: false,
|
|
13
|
+
ssrBreakpoint: "desktop",
|
|
14
|
+
ssrWidth: 0,
|
|
15
|
+
ssrHeight: 0
|
|
16
|
+
};
|
|
17
|
+
var ViewportContext = createContext(defaultConfig);
|
|
18
|
+
function ViewportProvider({
|
|
19
|
+
children,
|
|
20
|
+
breakpoint = defaultConfig.breakpoint,
|
|
21
|
+
tabletMaxWidth = defaultConfig.tabletMaxWidth,
|
|
22
|
+
ssrIsMobile = defaultConfig.ssrIsMobile,
|
|
23
|
+
ssrBreakpoint = ssrIsMobile ? "mobile" : defaultConfig.ssrBreakpoint,
|
|
24
|
+
ssrWidth = defaultConfig.ssrWidth,
|
|
25
|
+
ssrHeight = defaultConfig.ssrHeight
|
|
26
|
+
}) {
|
|
27
|
+
const value = useMemo(
|
|
28
|
+
() => ({
|
|
29
|
+
breakpoint,
|
|
30
|
+
tabletMaxWidth,
|
|
31
|
+
ssrIsMobile,
|
|
32
|
+
ssrBreakpoint,
|
|
33
|
+
ssrWidth,
|
|
34
|
+
ssrHeight
|
|
35
|
+
}),
|
|
36
|
+
[breakpoint, tabletMaxWidth, ssrIsMobile, ssrBreakpoint, ssrWidth, ssrHeight]
|
|
37
|
+
);
|
|
38
|
+
return /* @__PURE__ */ jsx(ViewportContext.Provider, { value, children });
|
|
39
|
+
}
|
|
40
|
+
function useViewportConfig() {
|
|
41
|
+
return useContext(ViewportContext);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/media-query.ts
|
|
45
|
+
var subscribeCache = /* @__PURE__ */ new Map();
|
|
46
|
+
function attachMediaListener(mql, onChange) {
|
|
47
|
+
if (typeof mql.addEventListener === "function") {
|
|
48
|
+
mql.addEventListener("change", onChange);
|
|
49
|
+
return () => mql.removeEventListener("change", onChange);
|
|
50
|
+
}
|
|
51
|
+
mql.addListener(onChange);
|
|
52
|
+
return () => mql.removeListener(onChange);
|
|
53
|
+
}
|
|
54
|
+
function getMediaQuerySubscribe(query) {
|
|
55
|
+
const cached = subscribeCache.get(query);
|
|
56
|
+
if (cached) return cached;
|
|
57
|
+
const subscribe = (onStoreChange) => {
|
|
58
|
+
const mql = window.matchMedia(query);
|
|
59
|
+
return attachMediaListener(mql, onStoreChange);
|
|
60
|
+
};
|
|
61
|
+
subscribeCache.set(query, subscribe);
|
|
62
|
+
return subscribe;
|
|
63
|
+
}
|
|
64
|
+
function getMediaQuerySnapshot(query) {
|
|
65
|
+
return window.matchMedia(query).matches;
|
|
66
|
+
}
|
|
67
|
+
function maxWidthQuery(maxWidth) {
|
|
68
|
+
return `(max-width: ${maxWidth - 1}px)`;
|
|
69
|
+
}
|
|
70
|
+
function betweenWidthQuery(minWidth, maxWidth) {
|
|
71
|
+
return `(min-width: ${minWidth}px) and (max-width: ${maxWidth - 1}px)`;
|
|
72
|
+
}
|
|
73
|
+
function useMediaQuery(query, options = {}) {
|
|
74
|
+
const ssrMatch = options.ssrMatch ?? false;
|
|
75
|
+
const subscribe = getMediaQuerySubscribe(query);
|
|
76
|
+
const getSnapshot = useCallback(
|
|
77
|
+
() => getMediaQuerySnapshot(query),
|
|
78
|
+
[query]
|
|
79
|
+
);
|
|
80
|
+
const getServerSnapshot = useCallback(() => ssrMatch, [ssrMatch]);
|
|
81
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/use-breakpoint.ts
|
|
85
|
+
function useBreakpoint(options = {}) {
|
|
86
|
+
const config = useViewportConfig();
|
|
87
|
+
const mobileMaxWidth = options.mobileMaxWidth ?? config.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;
|
|
88
|
+
const tabletMaxWidth = options.tabletMaxWidth ?? config.tabletMaxWidth ?? DEFAULT_TABLET_MAX_WIDTH;
|
|
89
|
+
const ssrBreakpoint = options.ssrBreakpoint ?? config.ssrBreakpoint;
|
|
90
|
+
const isMobile = useMediaQuery(maxWidthQuery(mobileMaxWidth), {
|
|
91
|
+
ssrMatch: ssrBreakpoint === "mobile"
|
|
92
|
+
});
|
|
93
|
+
const isTablet = useMediaQuery(
|
|
94
|
+
betweenWidthQuery(mobileMaxWidth, tabletMaxWidth),
|
|
95
|
+
{ ssrMatch: ssrBreakpoint === "tablet" }
|
|
96
|
+
);
|
|
97
|
+
const breakpoint = isMobile ? "mobile" : isTablet ? "tablet" : "desktop";
|
|
98
|
+
return {
|
|
99
|
+
isMobile,
|
|
100
|
+
isTablet,
|
|
101
|
+
isDesktop: breakpoint === "desktop",
|
|
102
|
+
breakpoint
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/use-is-mobile.ts
|
|
107
|
+
function useIsMobile(options = {}) {
|
|
108
|
+
const config = useViewportConfig();
|
|
109
|
+
const breakpoint = options.breakpoint ?? config.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;
|
|
110
|
+
const ssrIsMobile = options.ssrIsMobile ?? config.ssrIsMobile;
|
|
111
|
+
return useMediaQuery(maxWidthQuery(breakpoint), { ssrMatch: ssrIsMobile });
|
|
112
|
+
}
|
|
113
|
+
var cachedSize = { width: 0, height: 0 };
|
|
114
|
+
function readSize() {
|
|
115
|
+
const width = window.innerWidth;
|
|
116
|
+
const height = window.innerHeight;
|
|
117
|
+
if (cachedSize.width !== width || cachedSize.height !== height) {
|
|
118
|
+
cachedSize = { width, height };
|
|
119
|
+
}
|
|
120
|
+
return cachedSize;
|
|
121
|
+
}
|
|
122
|
+
function subscribeToResize(onStoreChange) {
|
|
123
|
+
let frame = 0;
|
|
124
|
+
const onResize = () => {
|
|
125
|
+
cancelAnimationFrame(frame);
|
|
126
|
+
frame = requestAnimationFrame(onStoreChange);
|
|
127
|
+
};
|
|
128
|
+
window.addEventListener("resize", onResize);
|
|
129
|
+
return () => {
|
|
130
|
+
cancelAnimationFrame(frame);
|
|
131
|
+
window.removeEventListener("resize", onResize);
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function useViewportSize(options = {}) {
|
|
135
|
+
const config = useViewportConfig();
|
|
136
|
+
const ssrWidth = options.ssrWidth ?? config.ssrWidth;
|
|
137
|
+
const ssrHeight = options.ssrHeight ?? config.ssrHeight;
|
|
138
|
+
const serverSnapshot = useMemo(
|
|
139
|
+
() => ({ width: ssrWidth, height: ssrHeight }),
|
|
140
|
+
[ssrWidth, ssrHeight]
|
|
141
|
+
);
|
|
142
|
+
const getServerSnapshot = useCallback(() => serverSnapshot, [serverSnapshot]);
|
|
143
|
+
return useSyncExternalStore(subscribeToResize, readSize, getServerSnapshot);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { DEFAULT_MOBILE_MAX_WIDTH, DEFAULT_TABLET_MAX_WIDTH, DEFAULT_VIEWPORT_COOKIE, ViewportProvider, useBreakpoint, useIsMobile, useMediaQuery, useViewportConfig, useViewportSize };
|
|
147
|
+
//# sourceMappingURL=index.js.map
|
|
148
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/provider.tsx","../src/media-query.ts","../src/use-media-query.ts","../src/use-breakpoint.ts","../src/use-is-mobile.ts","../src/use-viewport-size.ts"],"names":["useMemo","useCallback","useSyncExternalStore"],"mappings":"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","file":"index.js","sourcesContent":["export const DEFAULT_MOBILE_MAX_WIDTH = 768;\nexport const DEFAULT_TABLET_MAX_WIDTH = 1024;\nexport const DEFAULT_VIEWPORT_COOKIE = \"viewport\";\n","import { createContext, useContext, useMemo } from \"react\";\nimport {\n DEFAULT_MOBILE_MAX_WIDTH,\n DEFAULT_TABLET_MAX_WIDTH,\n} from \"./constants\";\nimport type { ViewportConfig, ViewportProviderProps } from \"./types\";\n\nconst defaultConfig: ViewportConfig = {\n breakpoint: DEFAULT_MOBILE_MAX_WIDTH,\n tabletMaxWidth: DEFAULT_TABLET_MAX_WIDTH,\n ssrIsMobile: false,\n ssrBreakpoint: \"desktop\",\n ssrWidth: 0,\n ssrHeight: 0,\n};\n\nconst ViewportContext = createContext<ViewportConfig>(defaultConfig);\n\nexport function ViewportProvider({\n children,\n breakpoint = defaultConfig.breakpoint,\n tabletMaxWidth = defaultConfig.tabletMaxWidth,\n ssrIsMobile = defaultConfig.ssrIsMobile,\n ssrBreakpoint = ssrIsMobile ? \"mobile\" : defaultConfig.ssrBreakpoint,\n ssrWidth = defaultConfig.ssrWidth,\n ssrHeight = defaultConfig.ssrHeight,\n}: ViewportProviderProps) {\n const value = useMemo(\n (): ViewportConfig => ({\n breakpoint,\n tabletMaxWidth,\n ssrIsMobile,\n ssrBreakpoint,\n ssrWidth,\n ssrHeight,\n }),\n [breakpoint, tabletMaxWidth, ssrIsMobile, ssrBreakpoint, ssrWidth, ssrHeight],\n );\n\n return (\n <ViewportContext.Provider value={value}>{children}</ViewportContext.Provider>\n );\n}\n\nexport function useViewportConfig(): ViewportConfig {\n return useContext(ViewportContext);\n}\n","type Subscribe = (onStoreChange: () => void) => () => void;\n\nconst subscribeCache = new Map<string, Subscribe>();\n\nfunction attachMediaListener(\n mql: MediaQueryList,\n onChange: () => void,\n): () => void {\n if (typeof mql.addEventListener === \"function\") {\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n }\n\n mql.addListener(onChange);\n return () => mql.removeListener(onChange);\n}\n\nexport function getMediaQuerySubscribe(query: string): Subscribe {\n const cached = subscribeCache.get(query);\n if (cached) return cached;\n\n const subscribe: Subscribe = (onStoreChange) => {\n const mql = window.matchMedia(query);\n return attachMediaListener(mql, onStoreChange);\n };\n\n subscribeCache.set(query, subscribe);\n return subscribe;\n}\n\nexport function getMediaQuerySnapshot(query: string): boolean {\n return window.matchMedia(query).matches;\n}\n\nexport function maxWidthQuery(maxWidth: number): string {\n return `(max-width: ${maxWidth - 1}px)`;\n}\n\nexport function betweenWidthQuery(minWidth: number, maxWidth: number): string {\n return `(min-width: ${minWidth}px) and (max-width: ${maxWidth - 1}px)`;\n}\n","import { useCallback, useSyncExternalStore } from \"react\";\nimport { getMediaQuerySnapshot, getMediaQuerySubscribe } from \"./media-query\";\nimport type { UseMediaQueryOptions } from \"./types\";\n\nexport function useMediaQuery(\n query: string,\n options: UseMediaQueryOptions = {},\n): boolean {\n const ssrMatch = options.ssrMatch ?? false;\n const subscribe = getMediaQuerySubscribe(query);\n const getSnapshot = useCallback(\n () => getMediaQuerySnapshot(query),\n [query],\n );\n const getServerSnapshot = useCallback(() => ssrMatch, [ssrMatch]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import {\n DEFAULT_MOBILE_MAX_WIDTH,\n DEFAULT_TABLET_MAX_WIDTH,\n} from \"./constants\";\nimport { betweenWidthQuery, maxWidthQuery } from \"./media-query\";\nimport { useViewportConfig } from \"./provider\";\nimport type { BreakpointName, UseBreakpointOptions } from \"./types\";\nimport { useMediaQuery } from \"./use-media-query\";\n\nexport function useBreakpoint(\n options: UseBreakpointOptions = {},\n): {\n isMobile: boolean;\n isTablet: boolean;\n isDesktop: boolean;\n breakpoint: BreakpointName;\n} {\n const config = useViewportConfig();\n const mobileMaxWidth =\n options.mobileMaxWidth ?? config.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;\n const tabletMaxWidth =\n options.tabletMaxWidth ?? config.tabletMaxWidth ?? DEFAULT_TABLET_MAX_WIDTH;\n const ssrBreakpoint = options.ssrBreakpoint ?? config.ssrBreakpoint;\n\n const isMobile = useMediaQuery(maxWidthQuery(mobileMaxWidth), {\n ssrMatch: ssrBreakpoint === \"mobile\",\n });\n const isTablet = useMediaQuery(\n betweenWidthQuery(mobileMaxWidth, tabletMaxWidth),\n { ssrMatch: ssrBreakpoint === \"tablet\" },\n );\n\n const breakpoint: BreakpointName = isMobile\n ? \"mobile\"\n : isTablet\n ? \"tablet\"\n : \"desktop\";\n\n return {\n isMobile,\n isTablet,\n isDesktop: breakpoint === \"desktop\",\n breakpoint,\n };\n}\n","import { DEFAULT_MOBILE_MAX_WIDTH } from \"./constants\";\nimport { maxWidthQuery } from \"./media-query\";\nimport { useViewportConfig } from \"./provider\";\nimport type { UseIsMobileOptions } from \"./types\";\nimport { useMediaQuery } from \"./use-media-query\";\n\nexport function useIsMobile(options: UseIsMobileOptions = {}): boolean {\n const config = useViewportConfig();\n const breakpoint = options.breakpoint ?? config.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;\n const ssrIsMobile = options.ssrIsMobile ?? config.ssrIsMobile;\n\n return useMediaQuery(maxWidthQuery(breakpoint), { ssrMatch: ssrIsMobile });\n}\n","import { useCallback, useMemo, useSyncExternalStore } from \"react\";\nimport { useViewportConfig } from \"./provider\";\nimport type { UseViewportSizeOptions, ViewportSize } from \"./types\";\n\nlet cachedSize: ViewportSize = { width: 0, height: 0 };\n\nfunction readSize(): ViewportSize {\n const width = window.innerWidth;\n const height = window.innerHeight;\n\n if (cachedSize.width !== width || cachedSize.height !== height) {\n cachedSize = { width, height };\n }\n\n return cachedSize;\n}\n\nfunction subscribeToResize(onStoreChange: () => void): () => void {\n let frame = 0;\n\n const onResize = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(onStoreChange);\n };\n\n window.addEventListener(\"resize\", onResize);\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener(\"resize\", onResize);\n };\n}\n\nexport function useViewportSize(\n options: UseViewportSizeOptions = {},\n): ViewportSize {\n const config = useViewportConfig();\n const ssrWidth = options.ssrWidth ?? config.ssrWidth;\n const ssrHeight = options.ssrHeight ?? config.ssrHeight;\n const serverSnapshot = useMemo(\n (): ViewportSize => ({ width: ssrWidth, height: ssrHeight }),\n [ssrWidth, ssrHeight],\n );\n const getServerSnapshot = useCallback(() => serverSnapshot, [serverSnapshot]);\n\n return useSyncExternalStore(subscribeToResize, readSize, getServerSnapshot);\n}\n"]}
|
package/dist/script.cjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/constants.ts
|
|
4
|
+
var DEFAULT_MOBILE_MAX_WIDTH = 768;
|
|
5
|
+
var DEFAULT_VIEWPORT_COOKIE = "viewport";
|
|
6
|
+
|
|
7
|
+
// src/script.ts
|
|
8
|
+
function assertSafeToken(value, label) {
|
|
9
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
10
|
+
throw new Error(`Invalid ${label}: use only letters, numbers, _ or -`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function parseViewportCookie(value) {
|
|
14
|
+
if (value === "mobile") return true;
|
|
15
|
+
if (value === "desktop") return false;
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
function getViewportCookieScript(options = {}) {
|
|
19
|
+
const cookieName = options.cookieName ?? DEFAULT_VIEWPORT_COOKIE;
|
|
20
|
+
const breakpoint = options.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;
|
|
21
|
+
const maxAgeSeconds = options.maxAgeSeconds ?? 60 * 60 * 24 * 7;
|
|
22
|
+
assertSafeToken(cookieName, "cookieName");
|
|
23
|
+
if (!Number.isInteger(breakpoint) || breakpoint <= 0) {
|
|
24
|
+
throw new Error("breakpoint must be a positive integer");
|
|
25
|
+
}
|
|
26
|
+
if (!Number.isInteger(maxAgeSeconds) || maxAgeSeconds <= 0) {
|
|
27
|
+
throw new Error("maxAgeSeconds must be a positive integer");
|
|
28
|
+
}
|
|
29
|
+
return `(function(){try{var v=window.matchMedia("(max-width: ${breakpoint - 1}px)").matches?"mobile":"desktop";document.cookie="${cookieName}="+v+";path=/;max-age=${maxAgeSeconds};samesite=lax";}catch(e){}})();`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
exports.getViewportCookieScript = getViewportCookieScript;
|
|
33
|
+
exports.parseViewportCookie = parseViewportCookie;
|
|
34
|
+
//# sourceMappingURL=script.cjs.map
|
|
35
|
+
//# sourceMappingURL=script.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/script.ts"],"names":[],"mappings":";;;AAAO,IAAM,wBAAA,GAA2B,GAAA;AAEjC,IAAM,uBAAA,GAA0B,UAAA;;;ACSvC,SAAS,eAAA,CAAgB,OAAe,KAAA,EAAqB;AAC3D,EAAA,IAAI,CAAC,kBAAA,CAAmB,IAAA,CAAK,KAAK,CAAA,EAAG;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,KAAK,CAAA,mCAAA,CAAqC,CAAA;AAAA,EACvE;AACF;AAEO,SAAS,oBACd,KAAA,EACqB;AACrB,EAAA,IAAI,KAAA,KAAU,UAAU,OAAO,IAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,WAAW,OAAO,KAAA;AAChC,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,uBAAA,CACd,OAAA,GAAuC,EAAC,EAChC;AACR,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,uBAAA;AACzC,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,wBAAA;AACzC,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,aAAA,IAAiB,EAAA,GAAK,KAAK,EAAA,GAAK,CAAA;AAE9D,EAAA,eAAA,CAAgB,YAAY,YAAY,CAAA;AAExC,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,UAAU,CAAA,IAAK,cAAc,CAAA,EAAG;AACpD,IAAA,MAAM,IAAI,MAAM,uCAAuC,CAAA;AAAA,EACzD;AAEA,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,aAAa,CAAA,IAAK,iBAAiB,CAAA,EAAG;AAC1D,IAAA,MAAM,IAAI,MAAM,0CAA0C,CAAA;AAAA,EAC5D;AAEA,EAAA,OAAO,wDAAwD,UAAA,GAAa,CAAC,CAAA,kDAAA,EAAqD,UAAU,yBAAyB,aAAa,CAAA,+BAAA,CAAA;AACpL","file":"script.cjs","sourcesContent":["export const DEFAULT_MOBILE_MAX_WIDTH = 768;\nexport const DEFAULT_TABLET_MAX_WIDTH = 1024;\nexport const DEFAULT_VIEWPORT_COOKIE = \"viewport\";\n","import {\n DEFAULT_MOBILE_MAX_WIDTH,\n DEFAULT_VIEWPORT_COOKIE,\n} from \"./constants\";\n\nexport interface ViewportCookieScriptOptions {\n cookieName?: string;\n breakpoint?: number;\n maxAgeSeconds?: number;\n}\n\nfunction assertSafeToken(value: string, label: string): void {\n if (!/^[A-Za-z0-9_-]+$/.test(value)) {\n throw new Error(`Invalid ${label}: use only letters, numbers, _ or -`);\n }\n}\n\nexport function parseViewportCookie(\n value: string | undefined | null,\n): boolean | undefined {\n if (value === \"mobile\") return true;\n if (value === \"desktop\") return false;\n return undefined;\n}\n\nexport function getViewportCookieScript(\n options: ViewportCookieScriptOptions = {},\n): string {\n const cookieName = options.cookieName ?? DEFAULT_VIEWPORT_COOKIE;\n const breakpoint = options.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;\n const maxAgeSeconds = options.maxAgeSeconds ?? 60 * 60 * 24 * 7;\n\n assertSafeToken(cookieName, \"cookieName\");\n\n if (!Number.isInteger(breakpoint) || breakpoint <= 0) {\n throw new Error(\"breakpoint must be a positive integer\");\n }\n\n if (!Number.isInteger(maxAgeSeconds) || maxAgeSeconds <= 0) {\n throw new Error(\"maxAgeSeconds must be a positive integer\");\n }\n\n return `(function(){try{var v=window.matchMedia(\"(max-width: ${breakpoint - 1}px)\").matches?\"mobile\":\"desktop\";document.cookie=\"${cookieName}=\"+v+\";path=/;max-age=${maxAgeSeconds};samesite=lax\";}catch(e){}})();`;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface ViewportCookieScriptOptions {
|
|
2
|
+
cookieName?: string;
|
|
3
|
+
breakpoint?: number;
|
|
4
|
+
maxAgeSeconds?: number;
|
|
5
|
+
}
|
|
6
|
+
declare function parseViewportCookie(value: string | undefined | null): boolean | undefined;
|
|
7
|
+
declare function getViewportCookieScript(options?: ViewportCookieScriptOptions): string;
|
|
8
|
+
|
|
9
|
+
export { type ViewportCookieScriptOptions, getViewportCookieScript, parseViewportCookie };
|
package/dist/script.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface ViewportCookieScriptOptions {
|
|
2
|
+
cookieName?: string;
|
|
3
|
+
breakpoint?: number;
|
|
4
|
+
maxAgeSeconds?: number;
|
|
5
|
+
}
|
|
6
|
+
declare function parseViewportCookie(value: string | undefined | null): boolean | undefined;
|
|
7
|
+
declare function getViewportCookieScript(options?: ViewportCookieScriptOptions): string;
|
|
8
|
+
|
|
9
|
+
export { type ViewportCookieScriptOptions, getViewportCookieScript, parseViewportCookie };
|
package/dist/script.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var DEFAULT_MOBILE_MAX_WIDTH = 768;
|
|
3
|
+
var DEFAULT_VIEWPORT_COOKIE = "viewport";
|
|
4
|
+
|
|
5
|
+
// src/script.ts
|
|
6
|
+
function assertSafeToken(value, label) {
|
|
7
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
8
|
+
throw new Error(`Invalid ${label}: use only letters, numbers, _ or -`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function parseViewportCookie(value) {
|
|
12
|
+
if (value === "mobile") return true;
|
|
13
|
+
if (value === "desktop") return false;
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
function getViewportCookieScript(options = {}) {
|
|
17
|
+
const cookieName = options.cookieName ?? DEFAULT_VIEWPORT_COOKIE;
|
|
18
|
+
const breakpoint = options.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;
|
|
19
|
+
const maxAgeSeconds = options.maxAgeSeconds ?? 60 * 60 * 24 * 7;
|
|
20
|
+
assertSafeToken(cookieName, "cookieName");
|
|
21
|
+
if (!Number.isInteger(breakpoint) || breakpoint <= 0) {
|
|
22
|
+
throw new Error("breakpoint must be a positive integer");
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isInteger(maxAgeSeconds) || maxAgeSeconds <= 0) {
|
|
25
|
+
throw new Error("maxAgeSeconds must be a positive integer");
|
|
26
|
+
}
|
|
27
|
+
return `(function(){try{var v=window.matchMedia("(max-width: ${breakpoint - 1}px)").matches?"mobile":"desktop";document.cookie="${cookieName}="+v+";path=/;max-age=${maxAgeSeconds};samesite=lax";}catch(e){}})();`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { getViewportCookieScript, parseViewportCookie };
|
|
31
|
+
//# sourceMappingURL=script.js.map
|
|
32
|
+
//# sourceMappingURL=script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/script.ts"],"names":[],"mappings":";AAAO,IAAM,wBAAA,GAA2B,GAAA;AAEjC,IAAM,uBAAA,GAA0B,UAAA;;;ACSvC,SAAS,eAAA,CAAgB,OAAe,KAAA,EAAqB;AAC3D,EAAA,IAAI,CAAC,kBAAA,CAAmB,IAAA,CAAK,KAAK,CAAA,EAAG;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,KAAK,CAAA,mCAAA,CAAqC,CAAA;AAAA,EACvE;AACF;AAEO,SAAS,oBACd,KAAA,EACqB;AACrB,EAAA,IAAI,KAAA,KAAU,UAAU,OAAO,IAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,WAAW,OAAO,KAAA;AAChC,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,uBAAA,CACd,OAAA,GAAuC,EAAC,EAChC;AACR,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,uBAAA;AACzC,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,wBAAA;AACzC,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,aAAA,IAAiB,EAAA,GAAK,KAAK,EAAA,GAAK,CAAA;AAE9D,EAAA,eAAA,CAAgB,YAAY,YAAY,CAAA;AAExC,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,UAAU,CAAA,IAAK,cAAc,CAAA,EAAG;AACpD,IAAA,MAAM,IAAI,MAAM,uCAAuC,CAAA;AAAA,EACzD;AAEA,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,aAAa,CAAA,IAAK,iBAAiB,CAAA,EAAG;AAC1D,IAAA,MAAM,IAAI,MAAM,0CAA0C,CAAA;AAAA,EAC5D;AAEA,EAAA,OAAO,wDAAwD,UAAA,GAAa,CAAC,CAAA,kDAAA,EAAqD,UAAU,yBAAyB,aAAa,CAAA,+BAAA,CAAA;AACpL","file":"script.js","sourcesContent":["export const DEFAULT_MOBILE_MAX_WIDTH = 768;\nexport const DEFAULT_TABLET_MAX_WIDTH = 1024;\nexport const DEFAULT_VIEWPORT_COOKIE = \"viewport\";\n","import {\n DEFAULT_MOBILE_MAX_WIDTH,\n DEFAULT_VIEWPORT_COOKIE,\n} from \"./constants\";\n\nexport interface ViewportCookieScriptOptions {\n cookieName?: string;\n breakpoint?: number;\n maxAgeSeconds?: number;\n}\n\nfunction assertSafeToken(value: string, label: string): void {\n if (!/^[A-Za-z0-9_-]+$/.test(value)) {\n throw new Error(`Invalid ${label}: use only letters, numbers, _ or -`);\n }\n}\n\nexport function parseViewportCookie(\n value: string | undefined | null,\n): boolean | undefined {\n if (value === \"mobile\") return true;\n if (value === \"desktop\") return false;\n return undefined;\n}\n\nexport function getViewportCookieScript(\n options: ViewportCookieScriptOptions = {},\n): string {\n const cookieName = options.cookieName ?? DEFAULT_VIEWPORT_COOKIE;\n const breakpoint = options.breakpoint ?? DEFAULT_MOBILE_MAX_WIDTH;\n const maxAgeSeconds = options.maxAgeSeconds ?? 60 * 60 * 24 * 7;\n\n assertSafeToken(cookieName, \"cookieName\");\n\n if (!Number.isInteger(breakpoint) || breakpoint <= 0) {\n throw new Error(\"breakpoint must be a positive integer\");\n }\n\n if (!Number.isInteger(maxAgeSeconds) || maxAgeSeconds <= 0) {\n throw new Error(\"maxAgeSeconds must be a positive integer\");\n }\n\n return `(function(){try{var v=window.matchMedia(\"(max-width: ${breakpoint - 1}px)\").matches?\"mobile\":\"desktop\";document.cookie=\"${cookieName}=\"+v+\";path=/;max-age=${maxAgeSeconds};samesite=lax\";}catch(e){}})();`;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-mobile-viewport",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SSR-safe React hooks to detect mobile, tablet, and desktop viewports. Works with Next.js App Router.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Narges Abolhasan",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/nargesabolhasan/react-mobile-viewport.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/nargesabolhasan/react-mobile-viewport/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/nargesabolhasan/react-mobile-viewport#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"react",
|
|
17
|
+
"nextjs",
|
|
18
|
+
"next",
|
|
19
|
+
"viewport",
|
|
20
|
+
"mobile",
|
|
21
|
+
"media-query",
|
|
22
|
+
"matchMedia",
|
|
23
|
+
"ssr",
|
|
24
|
+
"hooks",
|
|
25
|
+
"responsive"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"main": "./dist/index.cjs",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./dist/index.d.cts",
|
|
40
|
+
"default": "./dist/index.cjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"./script": {
|
|
44
|
+
"import": {
|
|
45
|
+
"types": "./dist/script.d.ts",
|
|
46
|
+
"default": "./dist/script.js"
|
|
47
|
+
},
|
|
48
|
+
"require": {
|
|
49
|
+
"types": "./dist/script.d.cts",
|
|
50
|
+
"default": "./dist/script.cjs"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist",
|
|
56
|
+
"README.md",
|
|
57
|
+
"LICENSE"
|
|
58
|
+
],
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"dev": "tsup --watch",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest",
|
|
67
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.node.json",
|
|
68
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"react": ">=18",
|
|
72
|
+
"react-dom": ">=18"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@testing-library/react": "^16.3.0",
|
|
76
|
+
"@types/node": "^22.20.3",
|
|
77
|
+
"@types/react": "^19.1.8",
|
|
78
|
+
"@types/react-dom": "^19.1.6",
|
|
79
|
+
"jsdom": "^26.1.0",
|
|
80
|
+
"react": "^19.1.0",
|
|
81
|
+
"react-dom": "^19.1.0",
|
|
82
|
+
"tsup": "^8.5.0",
|
|
83
|
+
"typescript": "^5.8.3",
|
|
84
|
+
"vitest": "^3.2.4"
|
|
85
|
+
}
|
|
86
|
+
}
|