@pol-studios/hooks 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ import {
2
+ dateReviver,
3
+ downloadFile,
4
+ getFile,
5
+ getFilePost,
6
+ hasCameraAsync,
7
+ isDev,
8
+ isDevEnvironment,
9
+ isIphone,
10
+ isPwaLaunched,
11
+ isUsable,
12
+ parseWithDate,
13
+ withComponentMemo
14
+ } from "../chunk-MMIPLCL4.js";
15
+ import "../chunk-QGM4M3NI.js";
16
+ export {
17
+ dateReviver,
18
+ downloadFile,
19
+ getFile,
20
+ getFilePost,
21
+ hasCameraAsync,
22
+ isDev,
23
+ isDevEnvironment,
24
+ isIphone,
25
+ isPwaLaunched,
26
+ isUsable,
27
+ parseWithDate,
28
+ withComponentMemo
29
+ };
@@ -0,0 +1,151 @@
1
+ import {
2
+ isPwaLaunched
3
+ } from "./chunk-MMIPLCL4.js";
4
+
5
+ // src/device/useDevice.ts
6
+ import { useMemo } from "react";
7
+
8
+ // src/device/useWindowDimensions.ts
9
+ import { useState, useEffect } from "react";
10
+ function getWindowDimensions() {
11
+ return {
12
+ width: window.innerWidth,
13
+ height: window.innerHeight
14
+ };
15
+ }
16
+ function useWindowDimensions() {
17
+ const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
18
+ useEffect(() => {
19
+ function handleResize() {
20
+ const resize = getWindowDimensions();
21
+ setWindowDimensions(resize);
22
+ }
23
+ window.addEventListener("resize", handleResize);
24
+ return () => window.removeEventListener("resize", handleResize);
25
+ }, []);
26
+ return windowDimensions;
27
+ }
28
+
29
+ // src/device/useMobile.ts
30
+ import React from "react";
31
+ var MOBILE_BREAKPOINT = 768;
32
+ function useMobile() {
33
+ const [isMobile, setIsMobile] = React.useState(
34
+ void 0
35
+ );
36
+ React.useEffect(() => {
37
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
38
+ const onChange = () => {
39
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
40
+ };
41
+ mql.addEventListener("change", onChange);
42
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
43
+ return () => mql.removeEventListener("change", onChange);
44
+ }, []);
45
+ return !!isMobile;
46
+ }
47
+ var useIsMobile = useMobile;
48
+
49
+ // src/device/useDevice.ts
50
+ function useDevice() {
51
+ const { width, height } = useWindowDimensions();
52
+ const isMobile = useMobile();
53
+ const isPwa = isPwaLaunched();
54
+ return useMemo(() => {
55
+ return { isMobile, width, height, isPwa };
56
+ }, [isMobile, width, height, isPwa]);
57
+ }
58
+
59
+ // src/device/useTheme.ts
60
+ import { useState as useState2, useEffect as useEffect2, useLayoutEffect } from "react";
61
+ function useTheme() {
62
+ const [isDarkTheme, setIsDarkTheme] = useState2(false);
63
+ useLayoutEffect(() => {
64
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
65
+ if (mq.matches) {
66
+ setIsDarkTheme(true);
67
+ } else {
68
+ setIsDarkTheme(false);
69
+ }
70
+ mq.addEventListener("change", (evt) => {
71
+ setIsDarkTheme(evt.matches);
72
+ });
73
+ }, []);
74
+ useEffect2(() => {
75
+ if (isDarkTheme) {
76
+ document.documentElement.classList.add("dark");
77
+ } else {
78
+ document.documentElement.classList.remove("dark");
79
+ }
80
+ }, [isDarkTheme]);
81
+ return isDarkTheme;
82
+ }
83
+ var useThemeDetector = useTheme;
84
+
85
+ // src/device/useDimensions.ts
86
+ import { useEffect as useEffect3, useRef } from "react";
87
+ function useDimensions(ref) {
88
+ const dimensions = useRef({ width: 0, height: 0 });
89
+ useEffect3(() => {
90
+ if (ref.current) {
91
+ dimensions.current.width = ref.current.offsetWidth;
92
+ dimensions.current.height = ref.current.offsetHeight;
93
+ }
94
+ }, [ref]);
95
+ return dimensions.current;
96
+ }
97
+
98
+ // src/device/useReducedMotion.ts
99
+ import { useEffect as useEffect4, useState as useState3 } from "react";
100
+ function useReducedMotion() {
101
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState3(() => {
102
+ if (typeof window !== "undefined") {
103
+ const stored = localStorage.getItem("pol-reduced-motion");
104
+ if (stored === "true") return true;
105
+ if (stored === "false") return false;
106
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
107
+ }
108
+ return false;
109
+ });
110
+ useEffect4(() => {
111
+ if (typeof window === "undefined") return;
112
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
113
+ const handleChange = () => {
114
+ const stored = localStorage.getItem("pol-reduced-motion");
115
+ if (stored === null) {
116
+ setPrefersReducedMotion(mediaQuery.matches);
117
+ }
118
+ };
119
+ if (mediaQuery.addEventListener) {
120
+ mediaQuery.addEventListener("change", handleChange);
121
+ return () => mediaQuery.removeEventListener("change", handleChange);
122
+ } else {
123
+ mediaQuery.addListener(handleChange);
124
+ return () => mediaQuery.removeListener(handleChange);
125
+ }
126
+ }, []);
127
+ return prefersReducedMotion;
128
+ }
129
+ function setReducedMotionPreference(enabled) {
130
+ if (typeof window === "undefined") return;
131
+ if (enabled === null) {
132
+ localStorage.removeItem("pol-reduced-motion");
133
+ } else {
134
+ localStorage.setItem("pol-reduced-motion", enabled ? "true" : "false");
135
+ }
136
+ window.dispatchEvent(new CustomEvent("pol-reduced-motion-changed", {
137
+ detail: { enabled }
138
+ }));
139
+ }
140
+
141
+ export {
142
+ useWindowDimensions,
143
+ useMobile,
144
+ useIsMobile,
145
+ useDevice,
146
+ useTheme,
147
+ useThemeDetector,
148
+ useDimensions,
149
+ useReducedMotion,
150
+ setReducedMotionPreference
151
+ };
@@ -0,0 +1,29 @@
1
+ // src/form/OnValueChangedContext.tsx
2
+ import { createContext } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var OnValueChangedContext = createContext({
5
+ trigger: "change"
6
+ });
7
+ var OnValueChangedContextProvider = ({
8
+ children,
9
+ trigger
10
+ }) => {
11
+ return /* @__PURE__ */ jsx(
12
+ OnValueChangedContext.Provider,
13
+ {
14
+ value: {
15
+ trigger
16
+ },
17
+ children
18
+ }
19
+ );
20
+ };
21
+ var OnValueChangedTriggerContext = OnValueChangedContext;
22
+ var OnValueChangedTriggerContextProvider = OnValueChangedContextProvider;
23
+
24
+ export {
25
+ OnValueChangedContext,
26
+ OnValueChangedContextProvider,
27
+ OnValueChangedTriggerContext,
28
+ OnValueChangedTriggerContextProvider
29
+ };
@@ -0,0 +1,41 @@
1
+ import {
2
+ getFilePost
3
+ } from "./chunk-MMIPLCL4.js";
4
+
5
+ // src/file/useFileDialog.ts
6
+ import { useCallback } from "react";
7
+ function useFileDialog() {
8
+ const openFileDialog = useCallback(() => {
9
+ return new Promise((resolve) => {
10
+ const fileInput = document.createElement("input");
11
+ fileInput.type = "file";
12
+ fileInput.style.display = "none";
13
+ document.body.appendChild(fileInput);
14
+ fileInput.onchange = () => {
15
+ resolve(fileInput.files);
16
+ document.body.removeChild(fileInput);
17
+ };
18
+ fileInput.click();
19
+ });
20
+ }, []);
21
+ return { openFileDialog };
22
+ }
23
+
24
+ // src/file/useDownload.ts
25
+ import { useCallback as useCallback2 } from "react";
26
+ function useDownload() {
27
+ const download = useCallback2(
28
+ async ({ url, fileName, queryParams }) => {
29
+ await getFilePost(url, fileName, queryParams);
30
+ },
31
+ []
32
+ );
33
+ return download;
34
+ }
35
+ var useDownloadApiFile = useDownload;
36
+
37
+ export {
38
+ useFileDialog,
39
+ useDownload,
40
+ useDownloadApiFile
41
+ };
@@ -0,0 +1,67 @@
1
+ // src/lifecycle/useMounted.ts
2
+ import { useCallback, useEffect, useRef } from "react";
3
+ function useMounted() {
4
+ const isMounted = useRef(false);
5
+ useEffect(() => {
6
+ isMounted.current = true;
7
+ return () => {
8
+ isMounted.current = false;
9
+ };
10
+ }, []);
11
+ return useCallback(() => isMounted.current, []);
12
+ }
13
+
14
+ // src/lifecycle/useAfterMountedEffect.ts
15
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
16
+ function useAfterMountedEffect(effect, deps) {
17
+ const isMounted = useRef2(false);
18
+ const strictModeFirstRun = useRef2(true);
19
+ useEffect2(() => {
20
+ if (process.env.NODE_ENV !== "production" && strictModeFirstRun.current) {
21
+ strictModeFirstRun.current = false;
22
+ return;
23
+ }
24
+ if (isMounted.current) {
25
+ effect();
26
+ }
27
+ isMounted.current = true;
28
+ }, deps);
29
+ }
30
+
31
+ // src/lifecycle/useOnScreen.ts
32
+ import { useEffect as useEffect3, useRef as useRef3, useState } from "react";
33
+ function useOnScreen(ref, rootMargin = "0px") {
34
+ const [isIntersecting, setIntersecting] = useState(false);
35
+ const observerRef = useRef3(null);
36
+ useEffect3(() => {
37
+ if (observerRef.current) {
38
+ observerRef.current.disconnect();
39
+ }
40
+ const observer = new IntersectionObserver(
41
+ ([entry]) => {
42
+ setIntersecting(entry.isIntersecting);
43
+ },
44
+ {
45
+ rootMargin,
46
+ threshold: 0.1
47
+ // Trigger when 10% of element is visible
48
+ }
49
+ );
50
+ observerRef.current = observer;
51
+ if (ref.current) {
52
+ observer.observe(ref.current);
53
+ }
54
+ return () => {
55
+ if (observerRef.current) {
56
+ observerRef.current.disconnect();
57
+ }
58
+ };
59
+ }, [ref, rootMargin]);
60
+ return isIntersecting;
61
+ }
62
+
63
+ export {
64
+ useMounted,
65
+ useAfterMountedEffect,
66
+ useOnScreen
67
+ };
@@ -0,0 +1,370 @@
1
+ import {
2
+ indexDbContext,
3
+ useLocalStorageService
4
+ } from "./chunk-OF7JZIAL.js";
5
+ import {
6
+ useAfterMountedEffect
7
+ } from "./chunk-EVE7TM7O.js";
8
+ import {
9
+ dateReviver,
10
+ isUsable
11
+ } from "./chunk-MMIPLCL4.js";
12
+
13
+ // src/storage/useLocalStorage.ts
14
+ import {
15
+ useCallback,
16
+ useEffect,
17
+ useLayoutEffect,
18
+ useRef,
19
+ useState
20
+ } from "react";
21
+ function useLocalStorage(key, initialValue) {
22
+ const localStorageService = useLocalStorageService();
23
+ const [stateValue, setStateValue] = useState(initialValue);
24
+ const [isInitializing, setIsInitializing] = useState(true);
25
+ useLayoutEffect(() => {
26
+ setIsInitializing(true);
27
+ localStorageService.getItem(key).then((cachedValue) => {
28
+ if (isUsable(cachedValue) === false) {
29
+ setStateValue(initialValue);
30
+ setIsInitializing(false);
31
+ return;
32
+ }
33
+ if (cachedValue != stateValue) {
34
+ setStateValue(cachedValue);
35
+ setIsInitializing(false);
36
+ }
37
+ });
38
+ }, [key]);
39
+ const save = useRef(false);
40
+ const setLocalValue = useCallback(
41
+ (value) => {
42
+ save.current = true;
43
+ setStateValue(value);
44
+ },
45
+ [localStorageService]
46
+ );
47
+ useEffect(() => {
48
+ if (save.current === false) return;
49
+ if (isUsable(stateValue) == false) {
50
+ localStorageService.removeItem(key);
51
+ } else {
52
+ localStorageService.setItem(key, stateValue);
53
+ }
54
+ save.current = false;
55
+ }, [stateValue]);
56
+ const storageEventHandler = useCallback(() => {
57
+ localStorageService.getItem(key).then((cachedValue) => {
58
+ if (isUsable(cachedValue) === false) setStateValue(initialValue);
59
+ if (cachedValue != stateValue) {
60
+ setStateValue(cachedValue);
61
+ }
62
+ });
63
+ }, [setStateValue, key, stateValue]);
64
+ useEffect(() => {
65
+ const handler = localStorageService.addItemChangeListener(
66
+ key,
67
+ storageEventHandler
68
+ );
69
+ return () => {
70
+ localStorageService.removeItemChangeListener(
71
+ key,
72
+ handler
73
+ );
74
+ };
75
+ }, [storageEventHandler]);
76
+ return [stateValue, setLocalValue, isInitializing];
77
+ }
78
+ var useLocalStorageState = useLocalStorage;
79
+
80
+ // src/storage/useSessionStorage.ts
81
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
82
+ function replacer(key, value) {
83
+ if (value instanceof Map) {
84
+ return { dataType: "Map", value: Array.from(value.entries()) };
85
+ }
86
+ return value;
87
+ }
88
+ function reviver(key, value) {
89
+ if (value?.dataType === "Map") {
90
+ return new Map(value.value);
91
+ }
92
+ return dateReviver(key, value);
93
+ }
94
+ function useSessionStorage(key, initialValue) {
95
+ const isBrowser2 = typeof window !== "undefined" && !!window.sessionStorage;
96
+ const [state, setState] = useState2(() => {
97
+ if (!isBrowser2) return initialValue;
98
+ try {
99
+ const raw = sessionStorage.getItem(key);
100
+ if (!isUsable(raw)) return initialValue;
101
+ const parsed = JSON.parse(raw, reviver);
102
+ return isUsable(parsed) ? parsed : initialValue;
103
+ } catch {
104
+ return initialValue;
105
+ }
106
+ });
107
+ const lastRawRef = useRef2(
108
+ isBrowser2 ? sessionStorage.getItem(key) : null
109
+ );
110
+ const timeoutRef = useRef2(null);
111
+ useAfterMountedEffect(() => {
112
+ if (!isBrowser2) return;
113
+ const raw = sessionStorage.getItem(key);
114
+ if (raw === lastRawRef.current) return;
115
+ lastRawRef.current = raw;
116
+ let next = initialValue;
117
+ if (isUsable(raw)) {
118
+ try {
119
+ const parsed = JSON.parse(raw, reviver);
120
+ if (isUsable(parsed)) next = parsed;
121
+ } catch {
122
+ }
123
+ }
124
+ setState(next);
125
+ }, [key]);
126
+ useEffect2(() => {
127
+ if (!isBrowser2) return;
128
+ if (timeoutRef.current) {
129
+ clearTimeout(timeoutRef.current);
130
+ }
131
+ timeoutRef.current = setTimeout(() => {
132
+ try {
133
+ let serialized;
134
+ try {
135
+ serialized = isUsable(state) ? JSON.stringify(state, replacer) : null;
136
+ } catch {
137
+ serialized = null;
138
+ }
139
+ if (!serialized) {
140
+ sessionStorage.removeItem(key);
141
+ lastRawRef.current = null;
142
+ } else {
143
+ sessionStorage.setItem(key, serialized);
144
+ lastRawRef.current = serialized;
145
+ }
146
+ } catch (error) {
147
+ console.warn("Failed to save to session storage:", error);
148
+ }
149
+ timeoutRef.current = null;
150
+ }, 100);
151
+ return () => {
152
+ if (timeoutRef.current) {
153
+ clearTimeout(timeoutRef.current);
154
+ }
155
+ };
156
+ }, [key, state, isBrowser2]);
157
+ return [state, setState];
158
+ }
159
+ var useSessionStorageState = useSessionStorage;
160
+
161
+ // src/storage/useIndexedDB.ts
162
+ import { useContext } from "react";
163
+ function useIndexedDB(options) {
164
+ const context = useContext(indexDbContext);
165
+ return Array.from(context.services).find(
166
+ (x) => x[0].dbName === options.dbName && x[0].storeName === options.storeName
167
+ )[1];
168
+ }
169
+
170
+ // src/storage/useQueuedLocalStorage.ts
171
+ import { useCallback as useCallback2, useRef as useRef3 } from "react";
172
+ function useQueuedLocalStorage(storageKey, initialState) {
173
+ const [state, setState, isInitializing] = useLocalStorage(
174
+ storageKey,
175
+ initialState
176
+ );
177
+ const queue = useRef3([]);
178
+ const processing = useRef3(false);
179
+ const processQueue = useCallback2(async () => {
180
+ if (processing.current) return;
181
+ processing.current = true;
182
+ while (queue.current.length > 0) {
183
+ const updateFn = queue.current.shift();
184
+ setState(updateFn);
185
+ await new Promise(requestAnimationFrame);
186
+ }
187
+ processing.current = false;
188
+ }, []);
189
+ const enqueue = useCallback2(
190
+ (updateFn) => {
191
+ queue.current.push(updateFn);
192
+ processQueue();
193
+ },
194
+ [processQueue]
195
+ );
196
+ return [state, enqueue, isInitializing];
197
+ }
198
+ var useQueuedLocalStorageState = useQueuedLocalStorage;
199
+
200
+ // src/storage/LocalStorageService.ts
201
+ function replacer2(key, value) {
202
+ if (value instanceof Map) {
203
+ return {
204
+ dataType: "Map",
205
+ value: Array.from(value.entries())
206
+ };
207
+ } else {
208
+ return value;
209
+ }
210
+ }
211
+ function reviver2(key, value) {
212
+ if (typeof value === "object" && value !== null) {
213
+ if (value.dataType === "Map") {
214
+ return new Map(value.value);
215
+ }
216
+ }
217
+ return dateReviver(key, value);
218
+ }
219
+ var isBrowser = typeof window !== "undefined" && typeof window.addEventListener === "function" && typeof localStorage !== "undefined";
220
+ var LocalStorageService = class {
221
+ constructor() {
222
+ }
223
+ addItemChangeListener(key, handler) {
224
+ if (!isBrowser) return () => {
225
+ };
226
+ const response = () => {
227
+ const newValue = localStorage.getItem(key);
228
+ handler({ key, newValue });
229
+ };
230
+ window.addEventListener(key, response);
231
+ return response;
232
+ }
233
+ removeItemChangeListener(key, handler) {
234
+ if (!isBrowser) return;
235
+ window.removeEventListener(key, handler);
236
+ }
237
+ async setItem(key, value) {
238
+ if (!isBrowser) return;
239
+ localStorage.setItem(key, JSON.stringify(value, replacer2));
240
+ window.dispatchEvent(new Event(key));
241
+ }
242
+ async getItem(key) {
243
+ if (!isBrowser) return null;
244
+ const storedValue = localStorage.getItem(key);
245
+ try {
246
+ return storedValue ? JSON.parse(storedValue, reviver2) : null;
247
+ } catch (e) {
248
+ console.log("error", e, storedValue);
249
+ localStorage.removeItem(key);
250
+ return null;
251
+ }
252
+ }
253
+ removeItem(key) {
254
+ if (!isBrowser) return;
255
+ localStorage.removeItem(key);
256
+ window.dispatchEvent(new Event(key));
257
+ }
258
+ };
259
+
260
+ // src/storage/IndexedDBService.ts
261
+ var storeNames = ["cached-urls", "metadata"];
262
+ var version = 5;
263
+ var IndexedDBService = class {
264
+ dbName;
265
+ storeName;
266
+ db = null;
267
+ constructor(options) {
268
+ this.dbName = options.dbName;
269
+ this.storeName = options.storeName;
270
+ }
271
+ openDB() {
272
+ return new Promise((resolve, reject) => {
273
+ const request = indexedDB.open(this.dbName, version);
274
+ request.onupgradeneeded = (event) => {
275
+ const db = event.target.result;
276
+ storeNames.forEach((storeName) => {
277
+ if (!db.objectStoreNames.contains(storeName)) {
278
+ db.createObjectStore(storeName);
279
+ }
280
+ });
281
+ };
282
+ request.onsuccess = () => {
283
+ this.db = request.result;
284
+ resolve(this.db);
285
+ };
286
+ request.onerror = (event) => {
287
+ reject(event.target.error);
288
+ };
289
+ });
290
+ }
291
+ getDB() {
292
+ if (this.db) {
293
+ return Promise.resolve(this.db);
294
+ } else {
295
+ return this.openDB();
296
+ }
297
+ }
298
+ async setItem(key, record) {
299
+ const db = await this.getDB();
300
+ return new Promise((resolve, reject) => {
301
+ const transaction = db.transaction(this.storeName, "readwrite");
302
+ const store = transaction.objectStore(this.storeName);
303
+ try {
304
+ const request = store.put(record, key);
305
+ request.onsuccess = () => {
306
+ resolve();
307
+ };
308
+ request.onerror = () => {
309
+ reject(request.error);
310
+ };
311
+ } catch (error) {
312
+ reject(error);
313
+ }
314
+ });
315
+ }
316
+ async getItem(key) {
317
+ const db = await this.getDB();
318
+ return new Promise((resolve, reject) => {
319
+ const transaction = db.transaction(this.storeName, "readonly");
320
+ const store = transaction.objectStore(this.storeName);
321
+ const request = store.get(key);
322
+ request.onsuccess = () => {
323
+ resolve(request.result);
324
+ };
325
+ request.onerror = () => {
326
+ reject(request.error);
327
+ };
328
+ });
329
+ }
330
+ async removeItem(key) {
331
+ const db = await this.getDB();
332
+ return new Promise((resolve, reject) => {
333
+ const transaction = db.transaction(this.storeName, "readwrite");
334
+ const store = transaction.objectStore(this.storeName);
335
+ const request = store.delete(key);
336
+ request.onsuccess = () => {
337
+ resolve();
338
+ };
339
+ request.onerror = () => {
340
+ reject(request.error);
341
+ };
342
+ });
343
+ }
344
+ async getAllKeys() {
345
+ const db = await this.getDB();
346
+ return new Promise((resolve, reject) => {
347
+ const transaction = db.transaction(this.storeName, "readonly");
348
+ const store = transaction.objectStore(this.storeName);
349
+ const request = store.getAllKeys();
350
+ request.onsuccess = () => {
351
+ resolve(request.result);
352
+ };
353
+ request.onerror = () => {
354
+ reject(request.error);
355
+ };
356
+ });
357
+ }
358
+ };
359
+
360
+ export {
361
+ useLocalStorage,
362
+ useLocalStorageState,
363
+ useSessionStorage,
364
+ useSessionStorageState,
365
+ useIndexedDB,
366
+ useQueuedLocalStorage,
367
+ useQueuedLocalStorageState,
368
+ LocalStorageService,
369
+ IndexedDBService
370
+ };