@djangocfg/ui-nextjs 2.1.147 → 2.1.149
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/README.md +35 -10
- package/package.json +6 -8
- package/src/hooks/index.ts +19 -10
- package/src/hooks/useBrowserDetect.ts +0 -384
- package/src/hooks/useDeviceDetect.ts +0 -270
- package/src/hooks/useHotkey.ts +0 -104
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ pnpm add @djangocfg/ui-nextjs
|
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
@djangocfg/ui-nextjs
|
|
19
|
-
├── Re-exports everything from @djangocfg/ui-core (60 components,
|
|
19
|
+
├── Re-exports everything from @djangocfg/ui-core (60 components, 13 hooks)
|
|
20
20
|
├── + Next.js specific components (11)
|
|
21
|
-
├── + Browser storage hooks (
|
|
21
|
+
├── + Browser storage hooks (2)
|
|
22
22
|
├── + Blocks, Theme, Animations
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -60,26 +60,51 @@ import { Hero } from '@djangocfg/ui-nextjs/blocks';
|
|
|
60
60
|
|
|
61
61
|
## Hooks
|
|
62
62
|
|
|
63
|
-
### From ui-core (
|
|
64
|
-
`useMediaQuery` `useIsMobile` `useCopy` `useCountdown` `useDebounce` `useDebouncedCallback` `useImageLoader` `useToast` `useEventListener` `useDebugTools`
|
|
63
|
+
### From ui-core (13)
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
| Hook | Description |
|
|
66
|
+
|------|-------------|
|
|
67
|
+
| `useMediaQuery` | Responsive breakpoints |
|
|
68
|
+
| `useIsMobile` | Mobile detection |
|
|
69
|
+
| `useCopy` | Copy to clipboard |
|
|
70
|
+
| `useCountdown` | Countdown timer |
|
|
71
|
+
| `useDebounce` | Debounce values |
|
|
72
|
+
| `useDebouncedCallback` | Debounced callbacks |
|
|
73
|
+
| `useImageLoader` | Image loading state |
|
|
74
|
+
| `useToast` / `toast` | Toast notifications (Sonner) |
|
|
75
|
+
| `useEventListener` | Event bus |
|
|
76
|
+
| `useDebugTools` | Debug utilities |
|
|
77
|
+
| `useHotkey` | Keyboard shortcuts |
|
|
78
|
+
| `useBrowserDetect` | Browser detection |
|
|
79
|
+
| `useDeviceDetect` | Device detection |
|
|
80
|
+
|
|
81
|
+
### Next.js Specific (2)
|
|
67
82
|
|
|
68
83
|
| Hook | Description |
|
|
69
84
|
|------|-------------|
|
|
85
|
+
| `useResolvedTheme` | Light/dark theme resolution (next-themes) |
|
|
70
86
|
| `useLocalStorage` | Persistent state in localStorage |
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
|
|
88
|
+
Note: `useSessionStorage` is also available in ui-core.
|
|
89
|
+
|
|
90
|
+
## Dialog Service
|
|
91
|
+
|
|
92
|
+
The dialog service from ui-core is available for replacing native dialogs:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { DialogProvider, useDialog, dialog } from '@djangocfg/ui-core/lib/dialog-service';
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See ui-core README for full documentation.
|
|
75
99
|
|
|
76
100
|
## Usage
|
|
77
101
|
|
|
78
102
|
```tsx
|
|
79
103
|
import {
|
|
80
104
|
Button, Card, Input, // from ui-core
|
|
81
|
-
NextButtonLink, Sidebar
|
|
105
|
+
NextButtonLink, Sidebar // Next.js specific
|
|
82
106
|
} from '@djangocfg/ui-nextjs';
|
|
107
|
+
import { useLocalStorage } from '@djangocfg/ui-nextjs/hooks';
|
|
83
108
|
|
|
84
109
|
function Example() {
|
|
85
110
|
const [saved, setSaved] = useLocalStorage('form', null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-nextjs",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.149",
|
|
4
4
|
"description": "Next.js UI component library with Radix UI primitives, Tailwind CSS styling, charts, and form components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -80,9 +80,9 @@
|
|
|
80
80
|
"check": "tsc --noEmit"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@djangocfg/api": "^2.1.
|
|
84
|
-
"@djangocfg/ui-core": "^2.1.
|
|
85
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
83
|
+
"@djangocfg/api": "^2.1.149",
|
|
84
|
+
"@djangocfg/ui-core": "^2.1.149",
|
|
85
|
+
"@djangocfg/ui-tools": "^2.1.149",
|
|
86
86
|
"@types/react": "^19.1.0",
|
|
87
87
|
"@types/react-dom": "^19.1.0",
|
|
88
88
|
"consola": "^3.4.2",
|
|
@@ -101,12 +101,10 @@
|
|
|
101
101
|
"cytoscape": "^3.33.1",
|
|
102
102
|
"cytoscape-cose-bilkent": "^4.1.0",
|
|
103
103
|
"next-themes": "^0.4.6",
|
|
104
|
-
"react-chartjs-2": "^5.3.0"
|
|
105
|
-
"react-device-detect": "^2.2.3",
|
|
106
|
-
"react-hotkeys-hook": "^5.2.1"
|
|
104
|
+
"react-chartjs-2": "^5.3.0"
|
|
107
105
|
},
|
|
108
106
|
"devDependencies": {
|
|
109
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
107
|
+
"@djangocfg/typescript-config": "^2.1.149",
|
|
110
108
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
111
109
|
"@radix-ui/react-slot": "^1.2.4",
|
|
112
110
|
"@types/node": "^24.7.2",
|
package/src/hooks/index.ts
CHANGED
|
@@ -9,14 +9,23 @@
|
|
|
9
9
|
export { useResolvedTheme } from './useResolvedTheme';
|
|
10
10
|
export type { ResolvedTheme } from './useResolvedTheme';
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
export {
|
|
14
|
-
|
|
12
|
+
// Re-export from ui-core
|
|
13
|
+
export {
|
|
14
|
+
// Keyboard shortcuts
|
|
15
|
+
useHotkey,
|
|
16
|
+
useHotkeysContext,
|
|
17
|
+
HotkeysProvider,
|
|
18
|
+
isHotkeyPressed,
|
|
19
|
+
// Device detection
|
|
20
|
+
useDeviceDetect,
|
|
21
|
+
// Browser detection
|
|
22
|
+
useBrowserDetect,
|
|
23
|
+
} from '@djangocfg/ui-core/hooks';
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
export type {
|
|
26
|
+
UseHotkeyOptions,
|
|
27
|
+
HotkeyCallback,
|
|
28
|
+
Keys,
|
|
29
|
+
DeviceDetectResult,
|
|
30
|
+
BrowserInfo,
|
|
31
|
+
} from '@djangocfg/ui-core/hooks';
|
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Advanced browser detection hook
|
|
3
|
-
*
|
|
4
|
-
* Detects modern browsers including Chromium-based browsers that may
|
|
5
|
-
* incorrectly report as Safari (Arc, Brave, Vivaldi, Comet, etc.)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import { useMemo } from 'react';
|
|
11
|
-
|
|
12
|
-
export interface BrowserInfo {
|
|
13
|
-
// Core browser types
|
|
14
|
-
isChrome: boolean;
|
|
15
|
-
isChromium: boolean; // Any Chromium-based browser
|
|
16
|
-
isSafari: boolean; // Real Safari (WebKit on macOS/iOS)
|
|
17
|
-
isFirefox: boolean;
|
|
18
|
-
isEdge: boolean;
|
|
19
|
-
isOpera: boolean;
|
|
20
|
-
|
|
21
|
-
// Modern Chromium-based browsers
|
|
22
|
-
isBrave: boolean;
|
|
23
|
-
isArc: boolean;
|
|
24
|
-
isVivaldi: boolean;
|
|
25
|
-
isYandex: boolean;
|
|
26
|
-
isSamsungBrowser: boolean;
|
|
27
|
-
isUCBrowser: boolean;
|
|
28
|
-
|
|
29
|
-
// Additional browsers
|
|
30
|
-
isComet: boolean; // Perplexity's Comet browser (Chromium-based, supports push)
|
|
31
|
-
isOperaMini: boolean; // Opera Mini (does NOT support push notifications)
|
|
32
|
-
isIE: boolean; // Internet Explorer (does NOT support push notifications)
|
|
33
|
-
|
|
34
|
-
// In-App Browsers (WebViews) - typically do NOT support push notifications
|
|
35
|
-
isFacebookInApp: boolean; // Facebook's in-app browser
|
|
36
|
-
isInstagramInApp: boolean; // Instagram's in-app browser
|
|
37
|
-
isTikTokInApp: boolean; // TikTok's in-app browser
|
|
38
|
-
isSnapchatInApp: boolean; // Snapchat's in-app browser
|
|
39
|
-
isWeChatInApp: boolean; // WeChat's in-app browser
|
|
40
|
-
isThreadsInApp: boolean; // Threads' in-app browser
|
|
41
|
-
isLinkedInInApp: boolean; // LinkedIn's in-app browser (uses Chrome WebView - supports push on Android)
|
|
42
|
-
isTwitterInApp: boolean; // Twitter/X's in-app browser (uses Chrome WebView - supports push on Android)
|
|
43
|
-
isInAppBrowser: boolean; // Any in-app browser detected
|
|
44
|
-
isWebView: boolean; // Generic WebView detection
|
|
45
|
-
|
|
46
|
-
// Browser name
|
|
47
|
-
browserName: string;
|
|
48
|
-
|
|
49
|
-
// Engine
|
|
50
|
-
isWebKit: boolean; // Safari's engine
|
|
51
|
-
isBlink: boolean; // Chromium's engine
|
|
52
|
-
isGecko: boolean; // Firefox's engine
|
|
53
|
-
|
|
54
|
-
// Push notification support
|
|
55
|
-
/**
|
|
56
|
-
* Whether the browser supports Web Push Notifications.
|
|
57
|
-
* Returns false for browsers known to NOT support push:
|
|
58
|
-
* - Opera Mini (no service worker support)
|
|
59
|
-
* - Internet Explorer (deprecated, no Push API)
|
|
60
|
-
* - UC Browser (unreliable push support)
|
|
61
|
-
* - In-App browsers (Facebook, Instagram, TikTok, Snapchat, etc.)
|
|
62
|
-
* - Generic WebViews (except Twitter/LinkedIn on Android which use Chrome WebView)
|
|
63
|
-
*
|
|
64
|
-
* Note: Comet (Perplexity) is Chromium-based and DOES support push notifications.
|
|
65
|
-
* Note: This is a browser-level check. For full push support,
|
|
66
|
-
* also check 'serviceWorker' in navigator && 'PushManager' in window
|
|
67
|
-
*/
|
|
68
|
-
supportsPushNotifications: boolean;
|
|
69
|
-
|
|
70
|
-
// iOS specific
|
|
71
|
-
isIOSBrowser: boolean; // Any browser on iOS (all use WebKit, limited push support)
|
|
72
|
-
|
|
73
|
-
// For debugging
|
|
74
|
-
userAgent: string;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Detect browser with improved accuracy for Chromium-based browsers
|
|
79
|
-
*
|
|
80
|
-
* @example
|
|
81
|
-
* ```tsx
|
|
82
|
-
* const browser = useBrowserDetect();
|
|
83
|
-
*
|
|
84
|
-
* if (browser.isSafari && !browser.isChromium) {
|
|
85
|
-
* // Real Safari
|
|
86
|
-
* }
|
|
87
|
-
*
|
|
88
|
-
* if (browser.isChromium) {
|
|
89
|
-
* // Any Chromium-based browser (Chrome, Edge, Brave, Arc, etc.)
|
|
90
|
-
* }
|
|
91
|
-
* ```
|
|
92
|
-
*/
|
|
93
|
-
export function useBrowserDetect(): BrowserInfo {
|
|
94
|
-
return useMemo(() => {
|
|
95
|
-
if (typeof window === 'undefined') {
|
|
96
|
-
return {
|
|
97
|
-
isChrome: false,
|
|
98
|
-
isChromium: false,
|
|
99
|
-
isSafari: false,
|
|
100
|
-
isFirefox: false,
|
|
101
|
-
isEdge: false,
|
|
102
|
-
isOpera: false,
|
|
103
|
-
isBrave: false,
|
|
104
|
-
isArc: false,
|
|
105
|
-
isVivaldi: false,
|
|
106
|
-
isYandex: false,
|
|
107
|
-
isSamsungBrowser: false,
|
|
108
|
-
isUCBrowser: false,
|
|
109
|
-
isComet: false,
|
|
110
|
-
isOperaMini: false,
|
|
111
|
-
isIE: false,
|
|
112
|
-
isFacebookInApp: false,
|
|
113
|
-
isInstagramInApp: false,
|
|
114
|
-
isTikTokInApp: false,
|
|
115
|
-
isSnapchatInApp: false,
|
|
116
|
-
isWeChatInApp: false,
|
|
117
|
-
isThreadsInApp: false,
|
|
118
|
-
isLinkedInInApp: false,
|
|
119
|
-
isTwitterInApp: false,
|
|
120
|
-
isInAppBrowser: false,
|
|
121
|
-
isWebView: false,
|
|
122
|
-
browserName: 'unknown',
|
|
123
|
-
isWebKit: false,
|
|
124
|
-
isBlink: false,
|
|
125
|
-
isGecko: false,
|
|
126
|
-
supportsPushNotifications: false,
|
|
127
|
-
isIOSBrowser: false,
|
|
128
|
-
userAgent: '',
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const ua = window.navigator.userAgent.toLowerCase();
|
|
133
|
-
|
|
134
|
-
// Check for specific browsers first (most specific to least specific)
|
|
135
|
-
|
|
136
|
-
// Edge (Chromium-based)
|
|
137
|
-
const isEdge = ua.includes('edg/') || ua.includes('edge/');
|
|
138
|
-
|
|
139
|
-
// Brave (check for Brave-specific API)
|
|
140
|
-
const isBrave = !!(window.navigator as any).brave;
|
|
141
|
-
|
|
142
|
-
// Arc (check for Arc-specific markers in UA)
|
|
143
|
-
const isArc = ua.includes('arc/');
|
|
144
|
-
|
|
145
|
-
// Vivaldi
|
|
146
|
-
const isVivaldi = ua.includes('vivaldi');
|
|
147
|
-
|
|
148
|
-
// Yandex Browser
|
|
149
|
-
const isYandex = ua.includes('yabrowser');
|
|
150
|
-
|
|
151
|
-
// Samsung Internet
|
|
152
|
-
const isSamsungBrowser = ua.includes('samsungbrowser');
|
|
153
|
-
|
|
154
|
-
// UC Browser
|
|
155
|
-
const isUCBrowser = ua.includes('ucbrowser') || ua.includes('uc browser');
|
|
156
|
-
|
|
157
|
-
// Comet Browser (Perplexity's AI browser, Chromium-based)
|
|
158
|
-
// May have 'comet' in UA or can be detected by specific markers
|
|
159
|
-
const isComet = ua.includes('comet') || ua.includes('perplexity');
|
|
160
|
-
|
|
161
|
-
// Opera Mini (does NOT support service workers or push)
|
|
162
|
-
const isOperaMini = ua.includes('opera mini') || ua.includes('opios');
|
|
163
|
-
|
|
164
|
-
// Internet Explorer (deprecated, no Push API support)
|
|
165
|
-
const isIE = ua.includes('msie') || ua.includes('trident/');
|
|
166
|
-
|
|
167
|
-
// ============================================================================
|
|
168
|
-
// In-App Browsers Detection (WebViews)
|
|
169
|
-
// These browsers typically do NOT support web push notifications
|
|
170
|
-
// ============================================================================
|
|
171
|
-
|
|
172
|
-
// Facebook In-App Browser
|
|
173
|
-
// UA contains: FBAN (Facebook App Name) or FBAV (Facebook App Version)
|
|
174
|
-
const isFacebookInApp = ua.includes('fban') || ua.includes('fbav') || ua.includes('fb_iab');
|
|
175
|
-
|
|
176
|
-
// Instagram In-App Browser
|
|
177
|
-
// UA contains: Instagram
|
|
178
|
-
const isInstagramInApp = ua.includes('instagram');
|
|
179
|
-
|
|
180
|
-
// TikTok In-App Browser
|
|
181
|
-
// UA contains: TikTok or BytedanceWebview or ByteLocale
|
|
182
|
-
const isTikTokInApp = ua.includes('tiktok') || ua.includes('bytedancewebview') || ua.includes('bytelocale');
|
|
183
|
-
|
|
184
|
-
// Snapchat In-App Browser
|
|
185
|
-
// UA contains: Snapchat
|
|
186
|
-
const isSnapchatInApp = ua.includes('snapchat');
|
|
187
|
-
|
|
188
|
-
// WeChat In-App Browser
|
|
189
|
-
// UA contains: MicroMessenger
|
|
190
|
-
const isWeChatInApp = ua.includes('micromessenger');
|
|
191
|
-
|
|
192
|
-
// Threads In-App Browser (Meta's app)
|
|
193
|
-
// UA contains: Threads or uses same markers as Instagram
|
|
194
|
-
const isThreadsInApp = ua.includes('barcelona'); // Threads codename is Barcelona
|
|
195
|
-
|
|
196
|
-
// LinkedIn In-App Browser
|
|
197
|
-
// UA contains: LinkedIn - NOTE: Uses Chrome WebView on Android, may support push
|
|
198
|
-
const isLinkedInInApp = ua.includes('linkedinapp');
|
|
199
|
-
|
|
200
|
-
// Twitter/X In-App Browser
|
|
201
|
-
// UA contains: Twitter - NOTE: Uses Chrome WebView on Android, may support push
|
|
202
|
-
const isTwitterInApp = ua.includes('twitter');
|
|
203
|
-
|
|
204
|
-
// Pinterest In-App Browser
|
|
205
|
-
const isPinterestInApp = ua.includes('pinterest');
|
|
206
|
-
|
|
207
|
-
// Telegram In-App Browser
|
|
208
|
-
const isTelegramInApp = ua.includes('telegram');
|
|
209
|
-
|
|
210
|
-
// Line In-App Browser
|
|
211
|
-
const isLineInApp = ua.includes('line/');
|
|
212
|
-
|
|
213
|
-
// Kakao In-App Browser (Korea)
|
|
214
|
-
const isKakaoInApp = ua.includes('kakaotalk');
|
|
215
|
-
|
|
216
|
-
// Generic WebView detection
|
|
217
|
-
// Android WebView: contains 'wv' in UA or specific WebView markers
|
|
218
|
-
// iOS WebView: WKWebView doesn't have distinct UA, but some markers exist
|
|
219
|
-
const isWebView = ua.includes('wv)') || // Android WebView marker
|
|
220
|
-
ua.includes('webview') ||
|
|
221
|
-
ua.includes('; wv') ||
|
|
222
|
-
(ua.includes('iphone') && !ua.includes('safari')) || // iOS WebView (no Safari)
|
|
223
|
-
(ua.includes('ipad') && !ua.includes('safari'));
|
|
224
|
-
|
|
225
|
-
// Aggregate: Any in-app browser
|
|
226
|
-
const isInAppBrowser = isFacebookInApp ||
|
|
227
|
-
isInstagramInApp ||
|
|
228
|
-
isTikTokInApp ||
|
|
229
|
-
isSnapchatInApp ||
|
|
230
|
-
isWeChatInApp ||
|
|
231
|
-
isThreadsInApp ||
|
|
232
|
-
isLinkedInInApp ||
|
|
233
|
-
isTwitterInApp ||
|
|
234
|
-
isPinterestInApp ||
|
|
235
|
-
isTelegramInApp ||
|
|
236
|
-
isLineInApp ||
|
|
237
|
-
isKakaoInApp;
|
|
238
|
-
|
|
239
|
-
// iOS detection (all iOS browsers use WebKit, limited push support)
|
|
240
|
-
const isIOSBrowser = ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod');
|
|
241
|
-
|
|
242
|
-
// Opera (modern Chromium-based)
|
|
243
|
-
const isOpera = (ua.includes('opr/') || ua.includes('opera')) && !isOperaMini;
|
|
244
|
-
|
|
245
|
-
// Chrome (not Edge, not other Chromium browsers)
|
|
246
|
-
const isChrome = ua.includes('chrome') &&
|
|
247
|
-
!isEdge &&
|
|
248
|
-
!isOpera &&
|
|
249
|
-
!isYandex &&
|
|
250
|
-
!isSamsungBrowser &&
|
|
251
|
-
!isVivaldi &&
|
|
252
|
-
!isArc &&
|
|
253
|
-
!isBrave &&
|
|
254
|
-
!isComet;
|
|
255
|
-
|
|
256
|
-
// Firefox
|
|
257
|
-
const isFirefox = ua.includes('firefox') && !ua.includes('seamonkey');
|
|
258
|
-
|
|
259
|
-
// Safari (real Safari, not Chromium pretending to be Safari)
|
|
260
|
-
// Safari will have 'safari' in UA but NOT 'chrome' or 'chromium'
|
|
261
|
-
// Real Safari uses WebKit engine
|
|
262
|
-
// Additional check: Safari has 'version/' in UA, Chromium browsers don't combine it with Safari
|
|
263
|
-
const hasSafariUA = ua.includes('safari');
|
|
264
|
-
const hasChrome = ua.includes('chrome') || ua.includes('crios');
|
|
265
|
-
const hasVersion = ua.includes('version/'); // Real Safari includes Version/XX.X
|
|
266
|
-
const isSafari = hasSafariUA && !hasChrome && hasVersion;
|
|
267
|
-
|
|
268
|
-
// Chromium detection (any browser using Chromium/Blink engine)
|
|
269
|
-
// If it has "chrome" in UA or is one of the known Chromium browsers
|
|
270
|
-
const isChromium = hasChrome ||
|
|
271
|
-
isEdge ||
|
|
272
|
-
isOpera ||
|
|
273
|
-
isYandex ||
|
|
274
|
-
isSamsungBrowser ||
|
|
275
|
-
isVivaldi ||
|
|
276
|
-
isArc ||
|
|
277
|
-
isBrave ||
|
|
278
|
-
isUCBrowser ||
|
|
279
|
-
isComet;
|
|
280
|
-
|
|
281
|
-
// Engine detection
|
|
282
|
-
const isWebKit = !isChromium && isSafari;
|
|
283
|
-
const isBlink = isChromium;
|
|
284
|
-
const isGecko = isFirefox;
|
|
285
|
-
|
|
286
|
-
// Determine browser name
|
|
287
|
-
// In-app browsers take priority in naming
|
|
288
|
-
let browserName = 'unknown';
|
|
289
|
-
if (isFacebookInApp) browserName = 'Facebook In-App';
|
|
290
|
-
else if (isInstagramInApp) browserName = 'Instagram In-App';
|
|
291
|
-
else if (isTikTokInApp) browserName = 'TikTok In-App';
|
|
292
|
-
else if (isSnapchatInApp) browserName = 'Snapchat In-App';
|
|
293
|
-
else if (isWeChatInApp) browserName = 'WeChat In-App';
|
|
294
|
-
else if (isThreadsInApp) browserName = 'Threads In-App';
|
|
295
|
-
else if (isLinkedInInApp) browserName = 'LinkedIn In-App';
|
|
296
|
-
else if (isTwitterInApp) browserName = 'Twitter In-App';
|
|
297
|
-
else if (isPinterestInApp) browserName = 'Pinterest In-App';
|
|
298
|
-
else if (isTelegramInApp) browserName = 'Telegram In-App';
|
|
299
|
-
else if (isLineInApp) browserName = 'Line In-App';
|
|
300
|
-
else if (isKakaoInApp) browserName = 'KakaoTalk In-App';
|
|
301
|
-
else if (isComet) browserName = 'Comet';
|
|
302
|
-
else if (isOperaMini) browserName = 'Opera Mini';
|
|
303
|
-
else if (isIE) browserName = 'Internet Explorer';
|
|
304
|
-
else if (isBrave) browserName = 'Brave';
|
|
305
|
-
else if (isArc) browserName = 'Arc';
|
|
306
|
-
else if (isVivaldi) browserName = 'Vivaldi';
|
|
307
|
-
else if (isYandex) browserName = 'Yandex';
|
|
308
|
-
else if (isSamsungBrowser) browserName = 'Samsung Internet';
|
|
309
|
-
else if (isUCBrowser) browserName = 'UC Browser';
|
|
310
|
-
else if (isEdge) browserName = 'Edge';
|
|
311
|
-
else if (isOpera) browserName = 'Opera';
|
|
312
|
-
else if (isChrome) browserName = 'Chrome';
|
|
313
|
-
else if (isSafari) browserName = 'Safari';
|
|
314
|
-
else if (isFirefox) browserName = 'Firefox';
|
|
315
|
-
else if (isWebView) browserName = 'WebView';
|
|
316
|
-
|
|
317
|
-
// Determine push notification support
|
|
318
|
-
// These browsers are known to NOT support Web Push:
|
|
319
|
-
// - Opera Mini: no service worker support
|
|
320
|
-
// - Internet Explorer: deprecated, no Push API
|
|
321
|
-
// - UC Browser: unreliable push support on many versions
|
|
322
|
-
// - Most In-App browsers (Facebook, Instagram, TikTok, etc.)
|
|
323
|
-
// - Generic WebViews (except Twitter/LinkedIn on Android)
|
|
324
|
-
//
|
|
325
|
-
// Note: Comet (Perplexity) is Chromium-based and DOES support push notifications.
|
|
326
|
-
// Note: Twitter and LinkedIn on Android use Chrome WebView and DO support push.
|
|
327
|
-
// However, we err on the side of caution and disable for all in-app browsers.
|
|
328
|
-
const browserBlocksPush = isOperaMini ||
|
|
329
|
-
isIE ||
|
|
330
|
-
isUCBrowser ||
|
|
331
|
-
isFacebookInApp ||
|
|
332
|
-
isInstagramInApp ||
|
|
333
|
-
isTikTokInApp ||
|
|
334
|
-
isSnapchatInApp ||
|
|
335
|
-
isWeChatInApp ||
|
|
336
|
-
isThreadsInApp ||
|
|
337
|
-
isPinterestInApp ||
|
|
338
|
-
isTelegramInApp ||
|
|
339
|
-
isLineInApp ||
|
|
340
|
-
isKakaoInApp ||
|
|
341
|
-
isWebView;
|
|
342
|
-
|
|
343
|
-
// Twitter and LinkedIn on Android use Chrome WebView - they actually support push
|
|
344
|
-
// But only on Android, not iOS
|
|
345
|
-
const twitterLinkedInAndroid = (isTwitterInApp || isLinkedInInApp) && !isIOSBrowser;
|
|
346
|
-
|
|
347
|
-
const supportsPushNotifications = !browserBlocksPush || twitterLinkedInAndroid;
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
isChrome,
|
|
351
|
-
isChromium,
|
|
352
|
-
isSafari,
|
|
353
|
-
isFirefox,
|
|
354
|
-
isEdge,
|
|
355
|
-
isOpera,
|
|
356
|
-
isBrave,
|
|
357
|
-
isArc,
|
|
358
|
-
isVivaldi,
|
|
359
|
-
isYandex,
|
|
360
|
-
isSamsungBrowser,
|
|
361
|
-
isUCBrowser,
|
|
362
|
-
isComet,
|
|
363
|
-
isOperaMini,
|
|
364
|
-
isIE,
|
|
365
|
-
isFacebookInApp,
|
|
366
|
-
isInstagramInApp,
|
|
367
|
-
isTikTokInApp,
|
|
368
|
-
isSnapchatInApp,
|
|
369
|
-
isWeChatInApp,
|
|
370
|
-
isThreadsInApp,
|
|
371
|
-
isLinkedInInApp,
|
|
372
|
-
isTwitterInApp,
|
|
373
|
-
isInAppBrowser,
|
|
374
|
-
isWebView,
|
|
375
|
-
browserName,
|
|
376
|
-
isWebKit,
|
|
377
|
-
isBlink,
|
|
378
|
-
isGecko,
|
|
379
|
-
supportsPushNotifications,
|
|
380
|
-
isIOSBrowser,
|
|
381
|
-
userAgent: window.navigator.userAgent,
|
|
382
|
-
};
|
|
383
|
-
}, []);
|
|
384
|
-
}
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
// Safe defaults for SSR
|
|
6
|
-
const defaultSelectors = {
|
|
7
|
-
isMobile: false,
|
|
8
|
-
isTablet: false,
|
|
9
|
-
isDesktop: false,
|
|
10
|
-
isBrowser: false,
|
|
11
|
-
isMobileOnly: false,
|
|
12
|
-
isSmartTV: false,
|
|
13
|
-
isConsole: false,
|
|
14
|
-
isWearable: false,
|
|
15
|
-
isEmbedded: false,
|
|
16
|
-
isAndroid: false,
|
|
17
|
-
isIOS: false,
|
|
18
|
-
isWindows: false,
|
|
19
|
-
isMacOs: false,
|
|
20
|
-
isWinPhone: false,
|
|
21
|
-
isChrome: false,
|
|
22
|
-
isFirefox: false,
|
|
23
|
-
isSafari: false,
|
|
24
|
-
isOpera: false,
|
|
25
|
-
isIE: false,
|
|
26
|
-
isEdge: false,
|
|
27
|
-
isEdgeChromium: false,
|
|
28
|
-
isLegacyEdge: false,
|
|
29
|
-
isChromium: false,
|
|
30
|
-
isMobileSafari: false,
|
|
31
|
-
isYandex: false,
|
|
32
|
-
isMIUI: false,
|
|
33
|
-
isSamsungBrowser: false,
|
|
34
|
-
isElectron: false,
|
|
35
|
-
osVersion: 'unknown',
|
|
36
|
-
osName: 'unknown',
|
|
37
|
-
fullBrowserVersion: 'unknown',
|
|
38
|
-
browserVersion: 'unknown',
|
|
39
|
-
browserName: 'unknown',
|
|
40
|
-
mobileVendor: 'unknown',
|
|
41
|
-
mobileModel: 'unknown',
|
|
42
|
-
engineName: 'unknown',
|
|
43
|
-
engineVersion: 'unknown',
|
|
44
|
-
getUA: '',
|
|
45
|
-
deviceType: 'unknown',
|
|
46
|
-
isIOS13: false,
|
|
47
|
-
isIPad13: false,
|
|
48
|
-
isIPhone13: false,
|
|
49
|
-
isIPod13: false,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const defaultDeviceData = {
|
|
53
|
-
deviceType: 'unknown',
|
|
54
|
-
osName: 'unknown',
|
|
55
|
-
osVersion: 'unknown',
|
|
56
|
-
browserName: 'unknown',
|
|
57
|
-
browserVersion: 'unknown',
|
|
58
|
-
fullBrowserVersion: 'unknown',
|
|
59
|
-
mobileVendor: 'unknown',
|
|
60
|
-
mobileModel: 'unknown',
|
|
61
|
-
engineName: 'unknown',
|
|
62
|
-
engineVersion: 'unknown',
|
|
63
|
-
getUA: '',
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const defaultOrientation = {
|
|
67
|
-
isPortrait: false,
|
|
68
|
-
isLandscape: false,
|
|
69
|
-
orientation: 'portrait' as 'portrait' | 'landscape',
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Device detection hook wrapper for react-device-detect
|
|
74
|
-
*
|
|
75
|
-
* Provides a convenient interface to access device information including:
|
|
76
|
-
* - Device type (mobile, tablet, desktop, etc.)
|
|
77
|
-
* - Browser information (name, version, etc.)
|
|
78
|
-
* - OS information (name, version, etc.)
|
|
79
|
-
* - Orientation (portrait/landscape)
|
|
80
|
-
*
|
|
81
|
-
* @param userAgent - Optional user agent string (useful for SSR)
|
|
82
|
-
* @returns Device detection object with all available information
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```tsx
|
|
86
|
-
* const device = useDeviceDetect();
|
|
87
|
-
*
|
|
88
|
-
* if (device.isMobile) {
|
|
89
|
-
* return <MobileView />;
|
|
90
|
-
* }
|
|
91
|
-
*
|
|
92
|
-
* return <DesktopView />;
|
|
93
|
-
* ```
|
|
94
|
-
*/
|
|
95
|
-
export function useDeviceDetect(userAgent?: string) {
|
|
96
|
-
const [deviceInfo, setDeviceInfo] = useState<{
|
|
97
|
-
selectors: typeof defaultSelectors;
|
|
98
|
-
deviceData: typeof defaultDeviceData;
|
|
99
|
-
orientation: {
|
|
100
|
-
isPortrait: boolean;
|
|
101
|
-
isLandscape: boolean;
|
|
102
|
-
orientation: 'portrait' | 'landscape';
|
|
103
|
-
};
|
|
104
|
-
}>({
|
|
105
|
-
selectors: defaultSelectors,
|
|
106
|
-
deviceData: defaultDeviceData,
|
|
107
|
-
orientation: defaultOrientation,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
// Only run on client side
|
|
112
|
-
if (typeof window === 'undefined') return;
|
|
113
|
-
|
|
114
|
-
// Dynamic import to avoid SSR issues
|
|
115
|
-
import('react-device-detect').then((deviceDetect) => {
|
|
116
|
-
// Get user agent string
|
|
117
|
-
const ua = userAgent || (typeof window !== 'undefined' ? window.navigator.userAgent : '');
|
|
118
|
-
|
|
119
|
-
if (!ua) {
|
|
120
|
-
console.warn('No user agent available');
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Parse user agent using library's parseUserAgent function
|
|
125
|
-
const parsed = deviceDetect.parseUserAgent(ua);
|
|
126
|
-
|
|
127
|
-
if (!parsed) {
|
|
128
|
-
console.warn('Failed to parse user agent');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Build selectors using library's buildSelectorsObject
|
|
133
|
-
// We need to import buildSelectorsObject, but it's not exported
|
|
134
|
-
// So we'll use getSelectorsByUserAgent which is exported
|
|
135
|
-
const selectors = deviceDetect.getSelectorsByUserAgent(ua) || defaultSelectors;
|
|
136
|
-
|
|
137
|
-
// Extract device data from parsed result
|
|
138
|
-
const deviceData = {
|
|
139
|
-
deviceType: parsed.device?.type || 'unknown',
|
|
140
|
-
osName: parsed.os?.name || 'unknown',
|
|
141
|
-
osVersion: parsed.os?.version || 'unknown',
|
|
142
|
-
browserName: parsed.browser?.name || 'unknown',
|
|
143
|
-
browserVersion: parsed.browser?.version || 'unknown',
|
|
144
|
-
fullBrowserVersion: parsed.browser?.version || 'unknown',
|
|
145
|
-
mobileVendor: parsed.device?.vendor || 'unknown',
|
|
146
|
-
mobileModel: parsed.device?.model || 'unknown',
|
|
147
|
-
engineName: parsed.engine?.name || 'unknown',
|
|
148
|
-
engineVersion: parsed.engine?.version || 'unknown',
|
|
149
|
-
getUA: parsed.ua || ua,
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
// Get orientation - use library's hook if available, otherwise calculate
|
|
153
|
-
let orientation = defaultOrientation;
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
// Try to use the hook, but we can't call hooks conditionally
|
|
157
|
-
// So we'll calculate orientation manually
|
|
158
|
-
if (typeof window !== 'undefined') {
|
|
159
|
-
const isPortrait = window.innerHeight > window.innerWidth;
|
|
160
|
-
orientation = {
|
|
161
|
-
isPortrait,
|
|
162
|
-
isLandscape: !isPortrait,
|
|
163
|
-
orientation: (isPortrait ? 'portrait' : 'landscape') as 'portrait' | 'landscape',
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
console.warn('Failed to get orientation:', error);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
setDeviceInfo({ selectors, deviceData, orientation });
|
|
171
|
-
}).catch((error) => {
|
|
172
|
-
console.warn('Failed to load device detection:', error);
|
|
173
|
-
});
|
|
174
|
-
}, [userAgent]);
|
|
175
|
-
|
|
176
|
-
// Update orientation on window resize
|
|
177
|
-
useEffect(() => {
|
|
178
|
-
if (typeof window === 'undefined') return;
|
|
179
|
-
|
|
180
|
-
const handleResize = () => {
|
|
181
|
-
const isPortrait = window.innerHeight > window.innerWidth;
|
|
182
|
-
setDeviceInfo((prev) => ({
|
|
183
|
-
...prev,
|
|
184
|
-
orientation: {
|
|
185
|
-
isPortrait,
|
|
186
|
-
isLandscape: !isPortrait,
|
|
187
|
-
orientation: (isPortrait ? 'portrait' : 'landscape') as 'portrait' | 'landscape',
|
|
188
|
-
},
|
|
189
|
-
}));
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
window.addEventListener('resize', handleResize);
|
|
193
|
-
window.addEventListener('orientationchange', handleResize);
|
|
194
|
-
|
|
195
|
-
return () => {
|
|
196
|
-
window.removeEventListener('resize', handleResize);
|
|
197
|
-
window.removeEventListener('orientationchange', handleResize);
|
|
198
|
-
};
|
|
199
|
-
}, []);
|
|
200
|
-
|
|
201
|
-
const { selectors, deviceData, orientation } = deviceInfo;
|
|
202
|
-
|
|
203
|
-
return useMemo(() => {
|
|
204
|
-
return {
|
|
205
|
-
// Device type selectors
|
|
206
|
-
isMobile: selectors.isMobile ?? false,
|
|
207
|
-
isTablet: selectors.isTablet ?? false,
|
|
208
|
-
isDesktop: selectors.isDesktop ?? false,
|
|
209
|
-
isBrowser: selectors.isBrowser ?? false,
|
|
210
|
-
isMobileOnly: selectors.isMobileOnly ?? false,
|
|
211
|
-
isSmartTV: selectors.isSmartTV ?? false,
|
|
212
|
-
isConsole: selectors.isConsole ?? false,
|
|
213
|
-
isWearable: selectors.isWearable ?? false,
|
|
214
|
-
isEmbedded: selectors.isEmbedded ?? false,
|
|
215
|
-
|
|
216
|
-
// OS selectors
|
|
217
|
-
isAndroid: selectors.isAndroid ?? false,
|
|
218
|
-
isIOS: selectors.isIOS ?? false,
|
|
219
|
-
isWindows: selectors.isWindows ?? false,
|
|
220
|
-
isMacOs: selectors.isMacOs ?? false,
|
|
221
|
-
isWinPhone: selectors.isWinPhone ?? false,
|
|
222
|
-
|
|
223
|
-
// Browser selectors
|
|
224
|
-
isChrome: selectors.isChrome ?? false,
|
|
225
|
-
isFirefox: selectors.isFirefox ?? false,
|
|
226
|
-
isSafari: selectors.isSafari ?? false,
|
|
227
|
-
isOpera: selectors.isOpera ?? false,
|
|
228
|
-
isIE: selectors.isIE ?? false,
|
|
229
|
-
isEdge: selectors.isEdge ?? false,
|
|
230
|
-
isEdgeChromium: selectors.isEdgeChromium ?? false,
|
|
231
|
-
isLegacyEdge: selectors.isLegacyEdge ?? false,
|
|
232
|
-
isChromium: selectors.isChromium ?? false,
|
|
233
|
-
isMobileSafari: selectors.isMobileSafari ?? false,
|
|
234
|
-
isYandex: selectors.isYandex ?? false,
|
|
235
|
-
isMIUI: selectors.isMIUI ?? false,
|
|
236
|
-
isSamsungBrowser: selectors.isSamsungBrowser ?? false,
|
|
237
|
-
isElectron: selectors.isElectron ?? false,
|
|
238
|
-
|
|
239
|
-
// iOS version selectors
|
|
240
|
-
isIOS13: selectors.isIOS13 ?? false,
|
|
241
|
-
isIPad13: selectors.isIPad13 ?? false,
|
|
242
|
-
isIPhone13: selectors.isIPhone13 ?? false,
|
|
243
|
-
isIPod13: selectors.isIPod13 ?? false,
|
|
244
|
-
|
|
245
|
-
// Device information
|
|
246
|
-
deviceType: deviceData.deviceType ?? 'unknown',
|
|
247
|
-
osName: deviceData.osName ?? 'unknown',
|
|
248
|
-
osVersion: deviceData.osVersion ?? 'unknown',
|
|
249
|
-
browserName: deviceData.browserName ?? 'unknown',
|
|
250
|
-
browserVersion: deviceData.browserVersion ?? 'unknown',
|
|
251
|
-
fullBrowserVersion: deviceData.fullBrowserVersion ?? 'unknown',
|
|
252
|
-
mobileVendor: deviceData.mobileVendor ?? 'unknown',
|
|
253
|
-
mobileModel: deviceData.mobileModel ?? 'unknown',
|
|
254
|
-
engineName: deviceData.engineName ?? 'unknown',
|
|
255
|
-
engineVersion: deviceData.engineVersion ?? 'unknown',
|
|
256
|
-
getUA: deviceData.getUA ?? '',
|
|
257
|
-
|
|
258
|
-
// Orientation
|
|
259
|
-
isPortrait: orientation.isPortrait,
|
|
260
|
-
isLandscape: orientation.isLandscape,
|
|
261
|
-
orientation: orientation.orientation,
|
|
262
|
-
|
|
263
|
-
// Raw data (for advanced usage)
|
|
264
|
-
selectors,
|
|
265
|
-
deviceData,
|
|
266
|
-
};
|
|
267
|
-
}, [selectors, deviceData, orientation]);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export type DeviceDetectResult = ReturnType<typeof useDeviceDetect>;
|
package/src/hooks/useHotkey.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import type { RefObject } from 'react';
|
|
4
|
-
import { Options as HotkeysOptions, useHotkeys } from 'react-hotkeys-hook';
|
|
5
|
-
|
|
6
|
-
import type { HotkeyCallback, Keys } from 'react-hotkeys-hook';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Options for the useHotkey hook
|
|
10
|
-
*/
|
|
11
|
-
export interface UseHotkeyOptions extends Omit<HotkeysOptions, 'enabled'> {
|
|
12
|
-
/** Whether the hotkey is enabled (default: true) */
|
|
13
|
-
enabled?: boolean;
|
|
14
|
-
/** Scope for the hotkey - useful for context-specific shortcuts */
|
|
15
|
-
scope?: string;
|
|
16
|
-
/** Only trigger when focus is within a specific element */
|
|
17
|
-
scopes?: string[];
|
|
18
|
-
/** Prevent default browser behavior */
|
|
19
|
-
preventDefault?: boolean;
|
|
20
|
-
/** Enable in input fields and textareas */
|
|
21
|
-
enableOnFormTags?: boolean | readonly ('input' | 'textarea' | 'select')[];
|
|
22
|
-
/** Enable when contentEditable element is focused */
|
|
23
|
-
enableOnContentEditable?: boolean;
|
|
24
|
-
/** Split key for multiple hotkey combinations (default: ',') */
|
|
25
|
-
splitKey?: string;
|
|
26
|
-
/** Key up/down events */
|
|
27
|
-
keyup?: boolean;
|
|
28
|
-
keydown?: boolean;
|
|
29
|
-
/** Description for the hotkey (useful for help dialogs) */
|
|
30
|
-
description?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Simple wrapper hook for react-hotkeys-hook
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* // Single key
|
|
38
|
-
* useHotkey('escape', () => closeModal());
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* // Key combination
|
|
42
|
-
* useHotkey('ctrl+s', (e) => {
|
|
43
|
-
* e.preventDefault();
|
|
44
|
-
* saveDocument();
|
|
45
|
-
* });
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* // Multiple keys (any of them will trigger)
|
|
49
|
-
* useHotkey(['ArrowLeft', '['], () => goToPrevious());
|
|
50
|
-
* useHotkey(['ArrowRight', ']'], () => goToNext());
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* // With options
|
|
54
|
-
* useHotkey('/', () => focusSearch(), {
|
|
55
|
-
* preventDefault: true,
|
|
56
|
-
* enableOnFormTags: false,
|
|
57
|
-
* description: 'Focus search input'
|
|
58
|
-
* });
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* // Scoped hotkeys
|
|
62
|
-
* useHotkey('delete', () => deleteItem(), { scopes: ['list-view'] });
|
|
63
|
-
*
|
|
64
|
-
* @param keys - Hotkey or array of hotkeys (e.g., 'ctrl+s', 'ArrowLeft', ['[', 'ArrowLeft'])
|
|
65
|
-
* @param callback - Function to call when hotkey is pressed
|
|
66
|
-
* @param options - Configuration options
|
|
67
|
-
* @returns Ref to attach to element for scoped hotkeys
|
|
68
|
-
*/
|
|
69
|
-
export function useHotkey<T extends HTMLElement = HTMLElement>(
|
|
70
|
-
keys: Keys,
|
|
71
|
-
callback: HotkeyCallback,
|
|
72
|
-
options: UseHotkeyOptions = {}
|
|
73
|
-
): RefObject<T | null> {
|
|
74
|
-
const {
|
|
75
|
-
enabled = true,
|
|
76
|
-
preventDefault = false,
|
|
77
|
-
enableOnFormTags = false,
|
|
78
|
-
enableOnContentEditable = false,
|
|
79
|
-
description: _description,
|
|
80
|
-
...restOptions
|
|
81
|
-
} = options;
|
|
82
|
-
|
|
83
|
-
return useHotkeys<T>(
|
|
84
|
-
keys,
|
|
85
|
-
(event, handler) => {
|
|
86
|
-
if (preventDefault) {
|
|
87
|
-
event.preventDefault();
|
|
88
|
-
}
|
|
89
|
-
callback(event, handler);
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
enabled,
|
|
93
|
-
enableOnFormTags,
|
|
94
|
-
enableOnContentEditable,
|
|
95
|
-
...restOptions,
|
|
96
|
-
}
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Re-export useful utilities from react-hotkeys-hook
|
|
101
|
-
export { useHotkeysContext, HotkeysProvider, isHotkeyPressed } from 'react-hotkeys-hook';
|
|
102
|
-
|
|
103
|
-
// Re-export types
|
|
104
|
-
export type { HotkeyCallback, Keys } from 'react-hotkeys-hook';
|