@pelatform/ui.hook 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/dist/index.js ADDED
@@ -0,0 +1,876 @@
1
+ "use client";
2
+
3
+ // src/use-analytics.ts
4
+ import { useCallback } from "react";
5
+ var useAnalytics = () => {
6
+ const trackEvent = useCallback(
7
+ ({
8
+ event_name,
9
+ module,
10
+ submodule,
11
+ item_type,
12
+ item_id,
13
+ action,
14
+ delete_type
15
+ }) => {
16
+ if (typeof window !== "undefined" && window.gtag) {
17
+ window.gtag("event", event_name, {
18
+ module,
19
+ submodule,
20
+ module_path: `${module}/${submodule}`,
21
+ item_type,
22
+ item_id,
23
+ action,
24
+ delete_type,
25
+ category: "crud",
26
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
27
+ });
28
+ }
29
+ },
30
+ []
31
+ );
32
+ const trackCreate = useCallback(
33
+ (module, submodule, itemType, itemId) => {
34
+ trackEvent({
35
+ event_name: "crud_create",
36
+ module,
37
+ submodule,
38
+ item_type: itemType,
39
+ item_id: itemId,
40
+ action: "create"
41
+ });
42
+ },
43
+ [trackEvent]
44
+ );
45
+ const trackUpdate = useCallback(
46
+ (module, submodule, itemType, itemId) => {
47
+ trackEvent({
48
+ event_name: "crud_update",
49
+ module,
50
+ submodule,
51
+ item_type: itemType,
52
+ item_id: itemId,
53
+ action: "update"
54
+ });
55
+ },
56
+ [trackEvent]
57
+ );
58
+ const trackDelete = useCallback(
59
+ (module, submodule, itemType, itemId, isHardDelete = false) => {
60
+ trackEvent({
61
+ event_name: "crud_delete",
62
+ module,
63
+ submodule,
64
+ item_type: itemType,
65
+ item_id: itemId,
66
+ action: "delete",
67
+ delete_type: isHardDelete ? "hard" : "soft"
68
+ });
69
+ },
70
+ [trackEvent]
71
+ );
72
+ return {
73
+ trackCreate,
74
+ trackUpdate,
75
+ trackDelete
76
+ };
77
+ };
78
+
79
+ // src/use-body-class.ts
80
+ import { useEffect } from "react";
81
+ var useBodyClasses = (className) => {
82
+ useEffect(() => {
83
+ if (!className.trim()) {
84
+ return;
85
+ }
86
+ const classList = className.split(/\s+/).filter(Boolean);
87
+ classList.forEach((cls) => {
88
+ document.body.classList.add(cls);
89
+ });
90
+ return () => {
91
+ classList.forEach((cls) => {
92
+ document.body.classList.remove(cls);
93
+ });
94
+ };
95
+ }, [className]);
96
+ };
97
+
98
+ // src/use-copy-to-clipboard.ts
99
+ import * as React from "react";
100
+ function useCopyToClipboard({
101
+ timeout = 2e3,
102
+ onCopy
103
+ } = {}) {
104
+ const [copied, setCopied] = React.useState(false);
105
+ const copy = React.useCallback(
106
+ (value) => {
107
+ if (typeof window === "undefined" || !navigator.clipboard?.writeText) {
108
+ console.warn("Clipboard API not supported in this environment");
109
+ return;
110
+ }
111
+ if (!value) {
112
+ console.warn("Cannot copy empty value to clipboard");
113
+ return;
114
+ }
115
+ navigator.clipboard.writeText(value).then(
116
+ () => {
117
+ setCopied(true);
118
+ if (onCopy) {
119
+ onCopy();
120
+ }
121
+ setTimeout(() => {
122
+ setCopied(false);
123
+ }, timeout);
124
+ },
125
+ (error) => {
126
+ console.error("Failed to copy text to clipboard:", error);
127
+ }
128
+ );
129
+ },
130
+ [timeout, onCopy]
131
+ );
132
+ return {
133
+ /** Whether text was recently copied (true for timeout duration) */
134
+ copied,
135
+ /** Function to copy text to clipboard */
136
+ copy
137
+ };
138
+ }
139
+
140
+ // src/use-file-upload.ts
141
+ import { useCallback as useCallback3, useRef, useState as useState2 } from "react";
142
+ var useFileUpload = (options = {}) => {
143
+ const {
144
+ maxFiles = Number.POSITIVE_INFINITY,
145
+ maxSize = Number.POSITIVE_INFINITY,
146
+ accept = "*",
147
+ multiple = false,
148
+ initialFiles = [],
149
+ onFilesChange,
150
+ onFilesAdded,
151
+ onError
152
+ } = options;
153
+ const [state, setState] = useState2({
154
+ files: initialFiles.map((file) => ({
155
+ file,
156
+ id: file.id,
157
+ preview: file.url
158
+ })),
159
+ isDragging: false,
160
+ errors: []
161
+ });
162
+ const inputRef = useRef(null);
163
+ const validateFile = useCallback3(
164
+ (file) => {
165
+ if (file instanceof File) {
166
+ if (file.size > maxSize) {
167
+ return `File "${file.name}" exceeds the maximum size of ${formatBytes(maxSize)}.`;
168
+ }
169
+ } else if (file.size > maxSize) {
170
+ return `File "${file.name}" exceeds the maximum size of ${formatBytes(maxSize)}.`;
171
+ }
172
+ if (accept !== "*") {
173
+ const acceptedTypes = accept.split(",").map((type) => type.trim());
174
+ const fileType = file instanceof File ? file.type || "" : file.type;
175
+ const fileExtension = `.${file instanceof File ? file.name.split(".").pop() : file.name.split(".").pop()}`;
176
+ const isAccepted = acceptedTypes.some((type) => {
177
+ if (type.startsWith(".")) {
178
+ return fileExtension.toLowerCase() === type.toLowerCase();
179
+ }
180
+ if (type.endsWith("/*")) {
181
+ const baseType = type.split("/")[0];
182
+ return fileType.startsWith(`${baseType}/`);
183
+ }
184
+ return fileType === type;
185
+ });
186
+ if (!isAccepted) {
187
+ return `File "${file instanceof File ? file.name : file.name}" is not an accepted file type.`;
188
+ }
189
+ }
190
+ return null;
191
+ },
192
+ [accept, maxSize]
193
+ );
194
+ const createPreview = useCallback3(
195
+ (file) => {
196
+ if (file instanceof File) {
197
+ return URL.createObjectURL(file);
198
+ }
199
+ return file.url;
200
+ },
201
+ []
202
+ );
203
+ const generateUniqueId = useCallback3((file) => {
204
+ if (file instanceof File) {
205
+ return `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
206
+ }
207
+ return file.id;
208
+ }, []);
209
+ const clearFiles = useCallback3(() => {
210
+ setState((prev) => {
211
+ for (const file of prev.files) {
212
+ if (file.preview && file.file instanceof File && file.file.type.startsWith("image/")) {
213
+ URL.revokeObjectURL(file.preview);
214
+ }
215
+ }
216
+ if (inputRef.current) {
217
+ inputRef.current.value = "";
218
+ }
219
+ const newState = {
220
+ ...prev,
221
+ files: [],
222
+ errors: []
223
+ };
224
+ onFilesChange?.(newState.files);
225
+ return newState;
226
+ });
227
+ }, [onFilesChange]);
228
+ const addFiles = useCallback3(
229
+ (newFiles) => {
230
+ if (!newFiles || newFiles.length === 0) {
231
+ return;
232
+ }
233
+ const newFilesArray = Array.from(newFiles);
234
+ const errors = [];
235
+ setState((prev) => ({ ...prev, errors: [] }));
236
+ if (!multiple) {
237
+ clearFiles();
238
+ }
239
+ if (multiple && maxFiles !== Number.POSITIVE_INFINITY && state.files.length + newFilesArray.length > maxFiles) {
240
+ errors.push(`You can only upload a maximum of ${maxFiles} files.`);
241
+ onError?.(errors);
242
+ setState((prev) => ({ ...prev, errors }));
243
+ return;
244
+ }
245
+ const validFiles = [];
246
+ for (const file of newFilesArray) {
247
+ if (multiple) {
248
+ const isDuplicate = state.files.some(
249
+ (existingFile) => existingFile.file.name === file.name && existingFile.file.size === file.size
250
+ );
251
+ if (isDuplicate) {
252
+ return;
253
+ }
254
+ }
255
+ if (file.size > maxSize) {
256
+ errors.push(
257
+ multiple ? `Some files exceed the maximum size of ${formatBytes(maxSize)}.` : `File exceeds the maximum size of ${formatBytes(maxSize)}.`
258
+ );
259
+ continue;
260
+ }
261
+ const error = validateFile(file);
262
+ if (error) {
263
+ errors.push(error);
264
+ } else {
265
+ validFiles.push({
266
+ file,
267
+ id: generateUniqueId(file),
268
+ preview: createPreview(file)
269
+ });
270
+ }
271
+ }
272
+ if (validFiles.length > 0) {
273
+ onFilesAdded?.(validFiles);
274
+ setState((prev) => {
275
+ const newFiles2 = !multiple ? validFiles : [...prev.files, ...validFiles];
276
+ onFilesChange?.(newFiles2);
277
+ return {
278
+ ...prev,
279
+ files: newFiles2,
280
+ errors
281
+ };
282
+ });
283
+ } else if (errors.length > 0) {
284
+ onError?.(errors);
285
+ setState((prev) => ({
286
+ ...prev,
287
+ errors
288
+ }));
289
+ }
290
+ if (inputRef.current) {
291
+ inputRef.current.value = "";
292
+ }
293
+ },
294
+ [
295
+ state.files,
296
+ maxFiles,
297
+ multiple,
298
+ maxSize,
299
+ validateFile,
300
+ createPreview,
301
+ generateUniqueId,
302
+ clearFiles,
303
+ onFilesChange,
304
+ onFilesAdded,
305
+ onError
306
+ ]
307
+ );
308
+ const removeFile = useCallback3(
309
+ (id) => {
310
+ setState((prev) => {
311
+ const fileToRemove = prev.files.find((file) => file.id === id);
312
+ if (fileToRemove?.preview && fileToRemove.file instanceof File && fileToRemove.file.type.startsWith("image/")) {
313
+ URL.revokeObjectURL(fileToRemove.preview);
314
+ }
315
+ const newFiles = prev.files.filter((file) => file.id !== id);
316
+ onFilesChange?.(newFiles);
317
+ return {
318
+ ...prev,
319
+ files: newFiles,
320
+ errors: []
321
+ };
322
+ });
323
+ },
324
+ [onFilesChange]
325
+ );
326
+ const clearErrors = useCallback3(() => {
327
+ setState((prev) => ({
328
+ ...prev,
329
+ errors: []
330
+ }));
331
+ }, []);
332
+ const handleDragEnter = useCallback3((e) => {
333
+ e.preventDefault();
334
+ e.stopPropagation();
335
+ setState((prev) => ({ ...prev, isDragging: true }));
336
+ }, []);
337
+ const handleDragLeave = useCallback3((e) => {
338
+ e.preventDefault();
339
+ e.stopPropagation();
340
+ if (e.currentTarget.contains(e.relatedTarget)) {
341
+ return;
342
+ }
343
+ setState((prev) => ({ ...prev, isDragging: false }));
344
+ }, []);
345
+ const handleDragOver = useCallback3((e) => {
346
+ e.preventDefault();
347
+ e.stopPropagation();
348
+ }, []);
349
+ const handleDrop = useCallback3(
350
+ (e) => {
351
+ e.preventDefault();
352
+ e.stopPropagation();
353
+ setState((prev) => ({ ...prev, isDragging: false }));
354
+ if (inputRef.current?.disabled) {
355
+ return;
356
+ }
357
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
358
+ if (!multiple) {
359
+ const file = e.dataTransfer.files[0];
360
+ addFiles([file]);
361
+ } else {
362
+ addFiles(e.dataTransfer.files);
363
+ }
364
+ }
365
+ },
366
+ [addFiles, multiple]
367
+ );
368
+ const handleFileChange = useCallback3(
369
+ (e) => {
370
+ if (e.target.files && e.target.files.length > 0) {
371
+ addFiles(e.target.files);
372
+ }
373
+ },
374
+ [addFiles]
375
+ );
376
+ const openFileDialog = useCallback3(() => {
377
+ if (inputRef.current) {
378
+ inputRef.current.click();
379
+ }
380
+ }, []);
381
+ const getInputProps = useCallback3(
382
+ (props = {}) => {
383
+ return {
384
+ ...props,
385
+ type: "file",
386
+ onChange: handleFileChange,
387
+ accept: props.accept || accept,
388
+ multiple: props.multiple !== void 0 ? props.multiple : multiple,
389
+ ref: inputRef
390
+ };
391
+ },
392
+ [accept, multiple, handleFileChange]
393
+ );
394
+ return [
395
+ state,
396
+ {
397
+ addFiles,
398
+ removeFile,
399
+ clearFiles,
400
+ clearErrors,
401
+ handleDragEnter,
402
+ handleDragLeave,
403
+ handleDragOver,
404
+ handleDrop,
405
+ handleFileChange,
406
+ openFileDialog,
407
+ getInputProps
408
+ }
409
+ ];
410
+ };
411
+ var formatBytes = (bytes, decimals = 2) => {
412
+ if (bytes === 0) {
413
+ return "0 Bytes";
414
+ }
415
+ const k = 1024;
416
+ const dm = decimals < 0 ? 0 : decimals;
417
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
418
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
419
+ const idx = Math.min(i, sizes.length - 1);
420
+ return `${Number.parseFloat((bytes / k ** idx).toFixed(dm))} ${sizes[idx]}`;
421
+ };
422
+
423
+ // src/use-hydrated.tsx
424
+ import { useSyncExternalStore } from "react";
425
+ function useHydrated() {
426
+ return useSyncExternalStore(
427
+ subscribe,
428
+ () => true,
429
+ () => false
430
+ );
431
+ }
432
+ function subscribe() {
433
+ return () => {
434
+ };
435
+ }
436
+
437
+ // src/use-media-query.ts
438
+ import { useEffect as useEffect2, useState as useState3 } from "react";
439
+ var getMatches = (query) => {
440
+ if (typeof window !== "undefined") {
441
+ return window.matchMedia(query).matches;
442
+ }
443
+ return false;
444
+ };
445
+ var useMediaQuery = (query) => {
446
+ const [matches, setMatches] = useState3(getMatches(query));
447
+ useEffect2(() => {
448
+ function handleChange() {
449
+ setMatches(getMatches(query));
450
+ }
451
+ const matchMedia = window.matchMedia(query);
452
+ handleChange();
453
+ matchMedia.addEventListener("change", handleChange);
454
+ return () => {
455
+ matchMedia.removeEventListener("change", handleChange);
456
+ };
457
+ }, [query]);
458
+ return matches;
459
+ };
460
+
461
+ // src/use-menu.ts
462
+ var useMenu = (pathname) => {
463
+ const isActive = (path) => {
464
+ if (path && path === "/") {
465
+ return path === pathname;
466
+ }
467
+ return !!path && pathname.startsWith(path);
468
+ };
469
+ const hasActiveChild = (children) => {
470
+ if (!(children && Array.isArray(children))) {
471
+ return false;
472
+ }
473
+ return children.some(
474
+ (child) => child.path && isActive(child.path) || child.children && hasActiveChild(child.children)
475
+ );
476
+ };
477
+ const isItemActive = (item) => {
478
+ return (item.path ? isActive(item.path) : false) || (item.children ? hasActiveChild(item.children) : false);
479
+ };
480
+ const getCurrentItem = (items) => {
481
+ for (const item of items) {
482
+ if (item.path && isActive(item.path)) {
483
+ if (item.children && item.children.length > 0) {
484
+ const childMatch = getCurrentItem(item.children);
485
+ return childMatch || item;
486
+ }
487
+ return item;
488
+ }
489
+ if (item.children && item.children.length > 0) {
490
+ const childMatch = getCurrentItem(item.children);
491
+ if (childMatch) {
492
+ return childMatch;
493
+ }
494
+ }
495
+ }
496
+ return void 0;
497
+ };
498
+ const getBreadcrumb = (items) => {
499
+ const findBreadcrumb = (nodes, breadcrumb2 = []) => {
500
+ for (const item of nodes) {
501
+ const currentBreadcrumb = [...breadcrumb2, item];
502
+ if (item.path && isActive(item.path)) {
503
+ return currentBreadcrumb;
504
+ }
505
+ if (item.children && item.children.length > 0) {
506
+ const childBreadcrumb = findBreadcrumb(
507
+ item.children,
508
+ currentBreadcrumb
509
+ );
510
+ if (childBreadcrumb.length > currentBreadcrumb.length) {
511
+ return childBreadcrumb;
512
+ }
513
+ }
514
+ }
515
+ return breadcrumb2;
516
+ };
517
+ const breadcrumb = findBreadcrumb(items);
518
+ return breadcrumb.length > 0 ? breadcrumb : [];
519
+ };
520
+ const getChildren = (items, level) => {
521
+ const hasActiveChildAtLevel = (items2) => {
522
+ for (const item of items2) {
523
+ if (item.path && (item.path === pathname || item.path !== "/" && item.path !== "" && pathname.startsWith(item.path)) || item.children && hasActiveChildAtLevel(item.children)) {
524
+ return true;
525
+ }
526
+ }
527
+ return false;
528
+ };
529
+ const findChildren = (items2, targetLevel, currentLevel = 0) => {
530
+ for (const item of items2) {
531
+ if (item.children) {
532
+ if (targetLevel === currentLevel && hasActiveChildAtLevel(item.children)) {
533
+ return item.children;
534
+ }
535
+ const children = findChildren(
536
+ item.children,
537
+ targetLevel,
538
+ currentLevel + 1
539
+ );
540
+ if (children) {
541
+ return children;
542
+ }
543
+ } else if (targetLevel === currentLevel && item.path && (item.path === pathname || item.path !== "/" && item.path !== "" && pathname.startsWith(item.path))) {
544
+ return items2;
545
+ }
546
+ }
547
+ return null;
548
+ };
549
+ return findChildren(items, level);
550
+ };
551
+ return {
552
+ isActive,
553
+ hasActiveChild,
554
+ isItemActive,
555
+ getCurrentItem,
556
+ getBreadcrumb,
557
+ getChildren
558
+ };
559
+ };
560
+
561
+ // src/use-mobile.ts
562
+ import * as React2 from "react";
563
+ var DEFAULT_MOBILE_BREAKPOINT = 1024;
564
+ function useIsMobile(breakpoint = DEFAULT_MOBILE_BREAKPOINT) {
565
+ const [isMobile, setIsMobile] = React2.useState(
566
+ void 0
567
+ );
568
+ React2.useEffect(() => {
569
+ const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
570
+ const onChange = () => {
571
+ setIsMobile(window.innerWidth < breakpoint);
572
+ };
573
+ mql.addEventListener("change", onChange);
574
+ setIsMobile(window.innerWidth < breakpoint);
575
+ return () => mql.removeEventListener("change", onChange);
576
+ }, [breakpoint]);
577
+ return !!isMobile;
578
+ }
579
+
580
+ // src/use-mounted.ts
581
+ import * as React3 from "react";
582
+ function useMounted() {
583
+ const [mounted, setMounted] = React3.useState(false);
584
+ React3.useEffect(() => {
585
+ setMounted(true);
586
+ }, []);
587
+ return mounted;
588
+ }
589
+
590
+ // src/use-mutation-observer.ts
591
+ import * as React4 from "react";
592
+ var DEFAULT_OPTIONS = {
593
+ /** Watch for attribute changes */
594
+ attributes: true,
595
+ /** Watch for text content changes */
596
+ characterData: true,
597
+ /** Watch for child element additions/removals */
598
+ childList: true,
599
+ /** Watch for changes in descendant elements */
600
+ subtree: true
601
+ };
602
+ var useMutationObserver = (ref, callback, options = DEFAULT_OPTIONS) => {
603
+ React4.useEffect(() => {
604
+ if (ref.current) {
605
+ const observer = new MutationObserver(callback);
606
+ observer.observe(ref.current, options);
607
+ return () => {
608
+ observer.disconnect();
609
+ };
610
+ }
611
+ }, [ref, callback, options]);
612
+ };
613
+
614
+ // src/use-recaptcha-v2.ts
615
+ import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef2 } from "react";
616
+ var RECAPTCHA_SCRIPT_ID = "recaptcha-v2-script";
617
+ var scriptLoadPromise = null;
618
+ function loadRecaptchaScript() {
619
+ if (scriptLoadPromise) {
620
+ return scriptLoadPromise;
621
+ }
622
+ scriptLoadPromise = new Promise((resolve) => {
623
+ if (document.getElementById(RECAPTCHA_SCRIPT_ID) && window.grecaptcha) {
624
+ resolve();
625
+ return;
626
+ }
627
+ window.onRecaptchaLoaded = () => {
628
+ resolve();
629
+ };
630
+ const script = document.createElement("script");
631
+ script.id = RECAPTCHA_SCRIPT_ID;
632
+ script.src = "https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit";
633
+ script.async = true;
634
+ script.defer = true;
635
+ script.onerror = () => {
636
+ console.error("Failed to load reCAPTCHA script");
637
+ scriptLoadPromise = null;
638
+ };
639
+ document.head.appendChild(script);
640
+ });
641
+ return scriptLoadPromise;
642
+ }
643
+ function useRecaptchaV2(siteKey) {
644
+ const widgetId = useRef2(null);
645
+ const containerRef = useRef2(null);
646
+ const isRendered = useRef2(false);
647
+ const isInitializing = useRef2(false);
648
+ const initializeRecaptcha = useCallback4(async () => {
649
+ if (isInitializing.current) {
650
+ return;
651
+ }
652
+ if (!(containerRef.current && siteKey)) {
653
+ return;
654
+ }
655
+ isInitializing.current = true;
656
+ try {
657
+ if (widgetId.current !== null) {
658
+ const grecaptcha2 = window.grecaptcha;
659
+ if (grecaptcha2) {
660
+ try {
661
+ grecaptcha2.reset(widgetId.current);
662
+ } catch (error) {
663
+ console.error("Error resetting reCAPTCHA:", error);
664
+ }
665
+ }
666
+ widgetId.current = null;
667
+ isRendered.current = false;
668
+ }
669
+ await loadRecaptchaScript();
670
+ const grecaptcha = window.grecaptcha;
671
+ if (!grecaptcha) {
672
+ throw new Error("reCAPTCHA failed to load");
673
+ }
674
+ await new Promise((resolve) => {
675
+ grecaptcha.ready(() => resolve());
676
+ });
677
+ if (containerRef.current && !isRendered.current) {
678
+ widgetId.current = grecaptcha.render(containerRef.current, {
679
+ sitekey: siteKey,
680
+ size: "normal",
681
+ // Can be 'normal', 'compact', or 'invisible'
682
+ theme: "light"
683
+ // Can be 'light' or 'dark'
684
+ });
685
+ isRendered.current = true;
686
+ }
687
+ } catch (error) {
688
+ console.error("Error initializing reCAPTCHA:", error);
689
+ } finally {
690
+ isInitializing.current = false;
691
+ }
692
+ }, [siteKey]);
693
+ useEffect6(() => {
694
+ if (containerRef.current) {
695
+ void initializeRecaptcha();
696
+ }
697
+ return () => {
698
+ if (widgetId.current !== null) {
699
+ const grecaptcha = window.grecaptcha;
700
+ if (grecaptcha) {
701
+ try {
702
+ grecaptcha.reset(widgetId.current);
703
+ } catch (error) {
704
+ console.error("Error resetting reCAPTCHA on cleanup:", error);
705
+ }
706
+ }
707
+ widgetId.current = null;
708
+ isRendered.current = false;
709
+ }
710
+ };
711
+ }, [initializeRecaptcha]);
712
+ const getToken = () => {
713
+ const grecaptcha = window.grecaptcha;
714
+ if (!grecaptcha || widgetId.current === null) {
715
+ throw new Error("reCAPTCHA not initialized");
716
+ }
717
+ const token = grecaptcha.getResponse(widgetId.current);
718
+ if (!token) {
719
+ throw new Error("reCAPTCHA not completed");
720
+ }
721
+ return token;
722
+ };
723
+ const resetCaptcha = () => {
724
+ const grecaptcha = window.grecaptcha;
725
+ if (!grecaptcha || widgetId.current === null) {
726
+ return;
727
+ }
728
+ try {
729
+ grecaptcha.reset(widgetId.current);
730
+ } catch (error) {
731
+ console.error("Error resetting reCAPTCHA:", error);
732
+ }
733
+ };
734
+ return {
735
+ /** Ref to attach to the container div where reCAPTCHA will be rendered */
736
+ containerRef,
737
+ /** Function to get the reCAPTCHA response token */
738
+ getToken,
739
+ /** Function to reset the reCAPTCHA widget */
740
+ resetCaptcha,
741
+ /** Function to manually initialize reCAPTCHA (usually not needed) */
742
+ initializeRecaptcha
743
+ };
744
+ }
745
+
746
+ // src/use-remove-ga-params.ts
747
+ import { useEffect as useEffect7 } from "react";
748
+ function useRemoveGAParams() {
749
+ useEffect7(() => {
750
+ const url = new URL(window.location.href);
751
+ if (url.searchParams.has("_gl")) {
752
+ const timer = setTimeout(() => {
753
+ url.searchParams.delete("_gl");
754
+ window.history.replaceState({}, "", url.toString());
755
+ }, 2e3);
756
+ return () => clearTimeout(timer);
757
+ }
758
+ }, []);
759
+ }
760
+
761
+ // src/use-scroll-position.ts
762
+ import { useEffect as useEffect8, useState as useState6 } from "react";
763
+ var useScrollPosition = ({
764
+ targetRef
765
+ } = {}) => {
766
+ const [scrollPosition, setScrollPosition] = useState6(0);
767
+ useEffect8(() => {
768
+ const target = targetRef?.current || document;
769
+ const scrollable = target === document ? window : target;
770
+ const updatePosition = () => {
771
+ const scrollY = target === document ? window.scrollY || window.pageYOffset : target.scrollTop;
772
+ setScrollPosition(scrollY);
773
+ };
774
+ scrollable.addEventListener("scroll", updatePosition, { passive: true });
775
+ updatePosition();
776
+ return () => {
777
+ scrollable.removeEventListener("scroll", updatePosition);
778
+ };
779
+ }, [targetRef]);
780
+ return scrollPosition;
781
+ };
782
+
783
+ // src/use-slider-input.ts
784
+ import { useCallback as useCallback5, useState as useState7 } from "react";
785
+ function useSliderInput({
786
+ minValue,
787
+ maxValue,
788
+ initialValue
789
+ }) {
790
+ const [sliderValues, setSliderValues] = useState7(initialValue);
791
+ const [inputValues, setInputValues] = useState7(initialValue);
792
+ const handleSliderChange = useCallback5((values) => {
793
+ setSliderValues(values);
794
+ setInputValues(values);
795
+ }, []);
796
+ const handleInputChange = useCallback5(
797
+ (e, index) => {
798
+ const newValue = Number.parseFloat(e.target.value);
799
+ if (!Number.isNaN(newValue)) {
800
+ const updatedInputs = [...inputValues];
801
+ updatedInputs[index] = newValue;
802
+ setInputValues(updatedInputs);
803
+ }
804
+ },
805
+ [inputValues]
806
+ );
807
+ const validateAndUpdateValue = useCallback5(
808
+ (value, index) => {
809
+ const updatedSlider = [...sliderValues];
810
+ if (index === 0) {
811
+ updatedSlider[0] = Math.max(minValue, Math.min(value, sliderValues[1]));
812
+ } else {
813
+ updatedSlider[1] = Math.min(maxValue, Math.max(value, sliderValues[0]));
814
+ }
815
+ setSliderValues(updatedSlider);
816
+ setInputValues(updatedSlider);
817
+ },
818
+ [sliderValues, minValue, maxValue]
819
+ );
820
+ return {
821
+ /** Function to manually set slider values */
822
+ setSliderValues,
823
+ /** Function to manually set input values */
824
+ setInputValues,
825
+ /** Current slider values [min, max] */
826
+ sliderValues,
827
+ /** Current input values [min, max] */
828
+ inputValues,
829
+ /** Handler for slider value changes */
830
+ handleSliderChange,
831
+ /** Handler for input field changes */
832
+ handleInputChange,
833
+ /** Function to validate and update values from inputs */
834
+ validateAndUpdateValue
835
+ };
836
+ }
837
+
838
+ // src/use-viewport.ts
839
+ import { useEffect as useEffect9, useState as useState8 } from "react";
840
+ var useViewport = () => {
841
+ const [dimensions, setDimensions] = useState8(() => {
842
+ if (typeof window !== "undefined") {
843
+ return [window.innerHeight, window.innerWidth];
844
+ }
845
+ return [0, 0];
846
+ });
847
+ useEffect9(() => {
848
+ const handleResize = () => {
849
+ setDimensions([window.innerHeight, window.innerWidth]);
850
+ };
851
+ handleResize();
852
+ window.addEventListener("resize", handleResize, { passive: true });
853
+ return () => {
854
+ window.removeEventListener("resize", handleResize);
855
+ };
856
+ }, []);
857
+ return dimensions;
858
+ };
859
+ export {
860
+ formatBytes,
861
+ useAnalytics,
862
+ useBodyClasses,
863
+ useCopyToClipboard,
864
+ useFileUpload,
865
+ useHydrated,
866
+ useIsMobile,
867
+ useMediaQuery,
868
+ useMenu,
869
+ useMounted,
870
+ useMutationObserver,
871
+ useRecaptchaV2,
872
+ useRemoveGAParams,
873
+ useScrollPosition,
874
+ useSliderInput,
875
+ useViewport
876
+ };