preact-missing-hooks 3.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +1 -0
- package/.husky/pre-push +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +6 -0
- package/Readme.md +149 -136
- package/dist/entry.cjs +21 -0
- package/dist/entry.js +2 -0
- package/dist/entry.js.map +1 -0
- package/dist/entry.modern.mjs +2 -0
- package/dist/entry.modern.mjs.map +1 -0
- package/dist/entry.module.js +2 -0
- package/dist/entry.module.js.map +1 -0
- package/dist/entry.umd.js +2 -0
- package/dist/entry.umd.js.map +1 -0
- package/dist/index.d.ts +13 -13
- package/dist/index.js.map +1 -1
- package/dist/index.modern.mjs +2 -0
- package/dist/index.modern.mjs.map +1 -0
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/indexedDB/dbController.d.ts +2 -2
- package/dist/indexedDB/index.d.ts +6 -6
- package/dist/indexedDB/openDB.d.ts +1 -1
- package/dist/indexedDB/tableController.d.ts +1 -1
- package/dist/indexedDB/types.d.ts +1 -2
- package/dist/react.js +1 -0
- package/dist/react.modern.mjs +1 -0
- package/dist/react.module.js +1 -0
- package/dist/react.umd.js +1 -0
- package/dist/useEventBus.d.ts +1 -1
- package/dist/useIndexedDB.d.ts +3 -3
- package/dist/useMutationObserver.d.ts +1 -1
- package/dist/useNetworkState.d.ts +3 -3
- package/dist/usePreferredTheme.d.ts +1 -1
- package/dist/useRageClick.d.ts +1 -1
- package/dist/useThreadedWorker.d.ts +1 -1
- package/dist/useTransition.d.ts +4 -1
- package/dist/useWorkerNotifications.d.ts +1 -1
- package/dist/useWrappedChildren.d.ts +3 -3
- package/eslint.config.mjs +10 -0
- package/package.json +60 -6
- package/scripts/generate-entry.cjs +34 -0
- package/src/index.ts +13 -13
- package/src/indexedDB/dbController.ts +101 -92
- package/src/indexedDB/index.ts +16 -11
- package/src/indexedDB/openDB.ts +49 -49
- package/src/indexedDB/requestToPromise.ts +17 -16
- package/src/indexedDB/tableController.ts +331 -257
- package/src/indexedDB/types.ts +35 -35
- package/src/useClipboard.ts +99 -97
- package/src/useEventBus.ts +39 -36
- package/src/useIndexedDB.ts +111 -111
- package/src/useMutationObserver.ts +26 -26
- package/src/useNetworkState.ts +124 -122
- package/src/usePreferredTheme.ts +68 -68
- package/src/useRageClick.ts +103 -103
- package/src/useThreadedWorker.ts +165 -165
- package/src/useTransition.ts +22 -19
- package/src/useWasmCompute.ts +209 -204
- package/src/useWebRTCIP.ts +181 -176
- package/src/useWorkerNotifications.ts +28 -20
- package/src/useWrappedChildren.ts +72 -58
- package/tests/react-adapter.tsx +12 -0
- package/tests/setup-react.ts +4 -0
- package/tests/useClipboard.test.tsx +4 -2
- package/tests/useThreadedWorker.test.tsx +3 -1
- package/tests/useWasmCompute.test.tsx +1 -1
- package/tests/useWebRTCIP.test.tsx +3 -1
- package/vite.config.ts +11 -4
- package/vitest.config.preact.ts +20 -0
- package/vitest.config.react.ts +36 -0
- package/vitest.workspace.ts +6 -0
package/src/useClipboard.ts
CHANGED
|
@@ -1,97 +1,99 @@
|
|
|
1
|
-
import { useCallback, useState } from
|
|
2
|
-
|
|
3
|
-
export interface UseClipboardOptions {
|
|
4
|
-
/** Duration in ms to keep `copied` true before resetting. Default: 2000 */
|
|
5
|
-
resetDelay?: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface UseClipboardReturn {
|
|
9
|
-
/** Copy text to the clipboard. Returns true on success. */
|
|
10
|
-
copy: (text: string) => Promise<boolean>;
|
|
11
|
-
/** Read text from the clipboard. Returns empty string if denied or unavailable. */
|
|
12
|
-
paste: () => Promise<string>;
|
|
13
|
-
/** Whether the last copy operation succeeded (resets after resetDelay) */
|
|
14
|
-
copied: boolean;
|
|
15
|
-
/** Error from the last failed operation, or null */
|
|
16
|
-
error: Error | null;
|
|
17
|
-
/** Manually reset copied and error state */
|
|
18
|
-
reset: () => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A Preact hook for reading and writing the clipboard. Uses the async
|
|
23
|
-
* Clipboard API when available (requires secure context and user gesture).
|
|
24
|
-
*
|
|
25
|
-
* @param options - Optional configuration (e.g., resetDelay for copied state)
|
|
26
|
-
* @returns Object with copy, paste, copied, error, and reset
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```tsx
|
|
30
|
-
* function CopyButton() {
|
|
31
|
-
* const { copy, copied, error } = useClipboard();
|
|
32
|
-
* return (
|
|
33
|
-
* <button onClick={() => copy('Hello!')}>
|
|
34
|
-
* {copied ? 'Copied!' : 'Copy'}
|
|
35
|
-
* </button>
|
|
36
|
-
* );
|
|
37
|
-
* }
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export function useClipboard(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
import { useCallback, useState } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
export interface UseClipboardOptions {
|
|
4
|
+
/** Duration in ms to keep `copied` true before resetting. Default: 2000 */
|
|
5
|
+
resetDelay?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface UseClipboardReturn {
|
|
9
|
+
/** Copy text to the clipboard. Returns true on success. */
|
|
10
|
+
copy: (text: string) => Promise<boolean>;
|
|
11
|
+
/** Read text from the clipboard. Returns empty string if denied or unavailable. */
|
|
12
|
+
paste: () => Promise<string>;
|
|
13
|
+
/** Whether the last copy operation succeeded (resets after resetDelay) */
|
|
14
|
+
copied: boolean;
|
|
15
|
+
/** Error from the last failed operation, or null */
|
|
16
|
+
error: Error | null;
|
|
17
|
+
/** Manually reset copied and error state */
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A Preact hook for reading and writing the clipboard. Uses the async
|
|
23
|
+
* Clipboard API when available (requires secure context and user gesture).
|
|
24
|
+
*
|
|
25
|
+
* @param options - Optional configuration (e.g., resetDelay for copied state)
|
|
26
|
+
* @returns Object with copy, paste, copied, error, and reset
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* function CopyButton() {
|
|
31
|
+
* const { copy, copied, error } = useClipboard();
|
|
32
|
+
* return (
|
|
33
|
+
* <button onClick={() => copy('Hello!')}>
|
|
34
|
+
* {copied ? 'Copied!' : 'Copy'}
|
|
35
|
+
* </button>
|
|
36
|
+
* );
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function useClipboard(
|
|
41
|
+
options: UseClipboardOptions = {}
|
|
42
|
+
): UseClipboardReturn {
|
|
43
|
+
const { resetDelay = 2000 } = options;
|
|
44
|
+
|
|
45
|
+
const [copied, setCopied] = useState(false);
|
|
46
|
+
const [error, setError] = useState<Error | null>(null);
|
|
47
|
+
|
|
48
|
+
const reset = useCallback(() => {
|
|
49
|
+
setCopied(false);
|
|
50
|
+
setError(null);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const copy = useCallback(
|
|
54
|
+
async (text: string): Promise<boolean> => {
|
|
55
|
+
setError(null);
|
|
56
|
+
|
|
57
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) {
|
|
58
|
+
const err = new Error("Clipboard API is not available");
|
|
59
|
+
setError(err);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await navigator.clipboard.writeText(text);
|
|
65
|
+
setCopied(true);
|
|
66
|
+
if (resetDelay > 0) {
|
|
67
|
+
setTimeout(() => setCopied(false), resetDelay);
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
72
|
+
setError(err);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[resetDelay]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const paste = useCallback(async (): Promise<string> => {
|
|
80
|
+
setError(null);
|
|
81
|
+
|
|
82
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) {
|
|
83
|
+
const err = new Error("Clipboard API is not available");
|
|
84
|
+
setError(err);
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const text = await navigator.clipboard.readText();
|
|
90
|
+
return text;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
93
|
+
setError(err);
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
return { copy, paste, copied, error, reset };
|
|
99
|
+
}
|
package/src/useEventBus.ts
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
|
-
import { useCallback
|
|
2
|
-
|
|
3
|
-
type EventMap = Record<string, (...args:
|
|
4
|
-
|
|
5
|
-
const listeners = new Map<string, Set<(...args:
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A Preact hook to publish and subscribe to custom events across components.
|
|
9
|
-
* @returns An object with `emit` and `on` methods.
|
|
10
|
-
*/
|
|
11
|
-
export function useEventBus<T extends EventMap>() {
|
|
12
|
-
const emit = useCallback(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
handlers
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
1
|
+
import { useCallback } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
type EventMap = Record<string, (...args: unknown[]) => void>;
|
|
4
|
+
|
|
5
|
+
const listeners = new Map<string, Set<(...args: unknown[]) => void>>();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A Preact hook to publish and subscribe to custom events across components.
|
|
9
|
+
* @returns An object with `emit` and `on` methods.
|
|
10
|
+
*/
|
|
11
|
+
export function useEventBus<T extends EventMap>() {
|
|
12
|
+
const emit = useCallback(
|
|
13
|
+
<K extends keyof T>(event: K, ...args: Parameters<T[K]>) => {
|
|
14
|
+
const handlers = listeners.get(event as string);
|
|
15
|
+
if (handlers) {
|
|
16
|
+
handlers.forEach((handler) => handler(...args));
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
[]
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const on = useCallback(<K extends keyof T>(event: K, handler: T[K]) => {
|
|
23
|
+
let handlers = listeners.get(event as string);
|
|
24
|
+
if (!handlers) {
|
|
25
|
+
handlers = new Set();
|
|
26
|
+
listeners.set(event as string, handlers);
|
|
27
|
+
}
|
|
28
|
+
handlers.add(handler);
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
handlers!.delete(handler);
|
|
32
|
+
if (handlers!.size === 0) {
|
|
33
|
+
listeners.delete(event as string);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
return { emit, on };
|
|
39
|
+
}
|
package/src/useIndexedDB.ts
CHANGED
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preact hook for IndexedDB: open database, create stores/indexes, return a database controller.
|
|
3
|
-
* Uses a singleton connection per (name, version).
|
|
4
|
-
* @module useIndexedDB
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useState, useEffect, useRef } from
|
|
8
|
-
import type { IndexedDBConfig } from
|
|
9
|
-
import { openDB } from
|
|
10
|
-
import { createDBController } from
|
|
11
|
-
import type { IDBController } from
|
|
12
|
-
|
|
13
|
-
export type { IndexedDBConfig, IDBController } from
|
|
14
|
-
|
|
15
|
-
export interface UseIndexedDBReturn {
|
|
16
|
-
/** Database controller (table, transaction). Null until the database is open. */
|
|
17
|
-
db: IDBController | null;
|
|
18
|
-
/** True once the database is open and ready. */
|
|
19
|
-
isReady: boolean;
|
|
20
|
-
/** Error from opening the database, if any. */
|
|
21
|
-
error: DOMException | null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Opens an IndexedDB database and returns a controller for tables and transactions.
|
|
26
|
-
* Handles onupgradeneeded: creates object stores and indexes from config.
|
|
27
|
-
* Connection is a singleton per (config.name, config.version).
|
|
28
|
-
*
|
|
29
|
-
* @param config - Database name, version, and table schemas (keyPath, autoIncrement, indexes).
|
|
30
|
-
* @returns { db, isReady, error }. Use db.table(name) and db.transaction(...) when isReady is true.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* const { db, isReady, error } = useIndexedDB({
|
|
34
|
-
* name: 'my-db',
|
|
35
|
-
* version: 1,
|
|
36
|
-
* tables: {
|
|
37
|
-
* users: { keyPath: 'id', autoIncrement: true, indexes: ['email'] },
|
|
38
|
-
* },
|
|
39
|
-
* })
|
|
40
|
-
* if (isReady && db) {
|
|
41
|
-
* const users = db.table('users')
|
|
42
|
-
* await users.insert({ email: 'a@b.com' })
|
|
43
|
-
* await db.transaction(['users'], 'readwrite', (tx) => tx.table('users').insert({ email: 'b@b.com' }))
|
|
44
|
-
* }
|
|
45
|
-
*/
|
|
46
|
-
export function useIndexedDB(config: IndexedDBConfig): UseIndexedDBReturn {
|
|
47
|
-
const [db, setDb] = useState<IDBController | null>(null);
|
|
48
|
-
const [error, setError] = useState<DOMException | null>(null);
|
|
49
|
-
const [isReady, setIsReady] = useState(false);
|
|
50
|
-
const configRef = useRef(config);
|
|
51
|
-
configRef.current = config;
|
|
52
|
-
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
let cancelled = false;
|
|
55
|
-
setError(null);
|
|
56
|
-
setIsReady(false);
|
|
57
|
-
setDb(null);
|
|
58
|
-
|
|
59
|
-
const { name, version, tables } = configRef.current;
|
|
60
|
-
openDB({ name, version, tables })
|
|
61
|
-
.then((database) => {
|
|
62
|
-
if (cancelled) {
|
|
63
|
-
database.close();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const controller = createDBController(database, configRef.current);
|
|
67
|
-
setDb(controller);
|
|
68
|
-
setIsReady(true);
|
|
69
|
-
})
|
|
70
|
-
.catch((err: DOMException) => {
|
|
71
|
-
if (!cancelled) setError(err);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
return () => {
|
|
75
|
-
cancelled = true;
|
|
76
|
-
};
|
|
77
|
-
}, [config.name, config.version]);
|
|
78
|
-
|
|
79
|
-
return { db, isReady, error };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/*
|
|
83
|
-
* Usage example:
|
|
84
|
-
*
|
|
85
|
-
* const { db, isReady, error } = useIndexedDB({
|
|
86
|
-
* name: 'my-app-db',
|
|
87
|
-
* version: 1,
|
|
88
|
-
* tables: {
|
|
89
|
-
* users: { keyPath: 'id', autoIncrement: true, indexes: ['email'] },
|
|
90
|
-
* settings: { keyPath: 'key' },
|
|
91
|
-
* },
|
|
92
|
-
* })
|
|
93
|
-
*
|
|
94
|
-
* if (error) return <div>Failed to open database</div>
|
|
95
|
-
* if (!isReady || !db) return <div>Loading...</div>
|
|
96
|
-
*
|
|
97
|
-
* const users = db.table('users')
|
|
98
|
-
* await users.insert({ email: 'a@b.com', name: 'Alice' })
|
|
99
|
-
* await users.update(1, { name: 'Alice Smith' })
|
|
100
|
-
* const found = await users.query((u) => u.email.startsWith('a@'))
|
|
101
|
-
* const n = await users.count()
|
|
102
|
-
* await users.delete(1)
|
|
103
|
-
* await users.upsert({ id: 2, email: 'b@b.com' })
|
|
104
|
-
* await users.bulkInsert([{ email: 'c@b.com' }, { email: 'd@b.com' }])
|
|
105
|
-
* await users.clear({ onSuccess: () => console.log('cleared') })
|
|
106
|
-
*
|
|
107
|
-
* await db.transaction(['users', 'settings'], 'readwrite', async (tx) => {
|
|
108
|
-
* await tx.table('users').insert({ email: 'e@b.com' })
|
|
109
|
-
* await tx.table('settings').upsert({ key: 'theme', value: 'dark' })
|
|
110
|
-
* }, { onSuccess: () => console.log('transaction done') })
|
|
111
|
-
*/
|
|
1
|
+
/**
|
|
2
|
+
* Preact hook for IndexedDB: open database, create stores/indexes, return a database controller.
|
|
3
|
+
* Uses a singleton connection per (name, version).
|
|
4
|
+
* @module useIndexedDB
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect, useRef } from "preact/hooks";
|
|
8
|
+
import type { IndexedDBConfig } from "./indexedDB/types";
|
|
9
|
+
import { openDB } from "./indexedDB/openDB";
|
|
10
|
+
import { createDBController } from "./indexedDB/dbController";
|
|
11
|
+
import type { IDBController } from "./indexedDB/dbController";
|
|
12
|
+
|
|
13
|
+
export type { IndexedDBConfig, IDBController } from "./indexedDB";
|
|
14
|
+
|
|
15
|
+
export interface UseIndexedDBReturn {
|
|
16
|
+
/** Database controller (table, transaction). Null until the database is open. */
|
|
17
|
+
db: IDBController | null;
|
|
18
|
+
/** True once the database is open and ready. */
|
|
19
|
+
isReady: boolean;
|
|
20
|
+
/** Error from opening the database, if any. */
|
|
21
|
+
error: DOMException | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Opens an IndexedDB database and returns a controller for tables and transactions.
|
|
26
|
+
* Handles onupgradeneeded: creates object stores and indexes from config.
|
|
27
|
+
* Connection is a singleton per (config.name, config.version).
|
|
28
|
+
*
|
|
29
|
+
* @param config - Database name, version, and table schemas (keyPath, autoIncrement, indexes).
|
|
30
|
+
* @returns { db, isReady, error }. Use db.table(name) and db.transaction(...) when isReady is true.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const { db, isReady, error } = useIndexedDB({
|
|
34
|
+
* name: 'my-db',
|
|
35
|
+
* version: 1,
|
|
36
|
+
* tables: {
|
|
37
|
+
* users: { keyPath: 'id', autoIncrement: true, indexes: ['email'] },
|
|
38
|
+
* },
|
|
39
|
+
* })
|
|
40
|
+
* if (isReady && db) {
|
|
41
|
+
* const users = db.table('users')
|
|
42
|
+
* await users.insert({ email: 'a@b.com' })
|
|
43
|
+
* await db.transaction(['users'], 'readwrite', (tx) => tx.table('users').insert({ email: 'b@b.com' }))
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
export function useIndexedDB(config: IndexedDBConfig): UseIndexedDBReturn {
|
|
47
|
+
const [db, setDb] = useState<IDBController | null>(null);
|
|
48
|
+
const [error, setError] = useState<DOMException | null>(null);
|
|
49
|
+
const [isReady, setIsReady] = useState(false);
|
|
50
|
+
const configRef = useRef(config);
|
|
51
|
+
configRef.current = config;
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
let cancelled = false;
|
|
55
|
+
setError(null);
|
|
56
|
+
setIsReady(false);
|
|
57
|
+
setDb(null);
|
|
58
|
+
|
|
59
|
+
const { name, version, tables } = configRef.current;
|
|
60
|
+
openDB({ name, version, tables })
|
|
61
|
+
.then((database) => {
|
|
62
|
+
if (cancelled) {
|
|
63
|
+
database.close();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const controller = createDBController(database, configRef.current);
|
|
67
|
+
setDb(controller);
|
|
68
|
+
setIsReady(true);
|
|
69
|
+
})
|
|
70
|
+
.catch((err: DOMException) => {
|
|
71
|
+
if (!cancelled) setError(err);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return () => {
|
|
75
|
+
cancelled = true;
|
|
76
|
+
};
|
|
77
|
+
}, [config.name, config.version]);
|
|
78
|
+
|
|
79
|
+
return { db, isReady, error };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
* Usage example:
|
|
84
|
+
*
|
|
85
|
+
* const { db, isReady, error } = useIndexedDB({
|
|
86
|
+
* name: 'my-app-db',
|
|
87
|
+
* version: 1,
|
|
88
|
+
* tables: {
|
|
89
|
+
* users: { keyPath: 'id', autoIncrement: true, indexes: ['email'] },
|
|
90
|
+
* settings: { keyPath: 'key' },
|
|
91
|
+
* },
|
|
92
|
+
* })
|
|
93
|
+
*
|
|
94
|
+
* if (error) return <div>Failed to open database</div>
|
|
95
|
+
* if (!isReady || !db) return <div>Loading...</div>
|
|
96
|
+
*
|
|
97
|
+
* const users = db.table('users')
|
|
98
|
+
* await users.insert({ email: 'a@b.com', name: 'Alice' })
|
|
99
|
+
* await users.update(1, { name: 'Alice Smith' })
|
|
100
|
+
* const found = await users.query((u) => u.email.startsWith('a@'))
|
|
101
|
+
* const n = await users.count()
|
|
102
|
+
* await users.delete(1)
|
|
103
|
+
* await users.upsert({ id: 2, email: 'b@b.com' })
|
|
104
|
+
* await users.bulkInsert([{ email: 'c@b.com' }, { email: 'd@b.com' }])
|
|
105
|
+
* await users.clear({ onSuccess: () => console.log('cleared') })
|
|
106
|
+
*
|
|
107
|
+
* await db.transaction(['users', 'settings'], 'readwrite', async (tx) => {
|
|
108
|
+
* await tx.table('users').insert({ email: 'e@b.com' })
|
|
109
|
+
* await tx.table('settings').upsert({ key: 'theme', value: 'dark' })
|
|
110
|
+
* }, { onSuccess: () => console.log('transaction done') })
|
|
111
|
+
*/
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { RefObject } from
|
|
2
|
-
import { useEffect } from
|
|
3
|
-
|
|
4
|
-
export type UseMutationObserverOptions = MutationObserverInit
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* A Preact hook to observe DOM mutations using MutationObserver.
|
|
8
|
-
* @param target - The element to observe.
|
|
9
|
-
* @param callback - Function to call on mutation.
|
|
10
|
-
* @param options - MutationObserver options.
|
|
11
|
-
*/
|
|
12
|
-
export function useMutationObserver(
|
|
13
|
-
targetRef: RefObject<HTMLElement | null>,
|
|
14
|
-
callback: MutationCallback,
|
|
15
|
-
options: MutationObserverInit
|
|
16
|
-
) {
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const node = targetRef.current
|
|
19
|
-
if (!node) return
|
|
20
|
-
|
|
21
|
-
const observer = new MutationObserver(callback)
|
|
22
|
-
observer.observe(node, options)
|
|
23
|
-
|
|
24
|
-
return () => observer.disconnect()
|
|
25
|
-
}, [targetRef, callback, options])
|
|
26
|
-
}
|
|
1
|
+
import { RefObject } from "preact";
|
|
2
|
+
import { useEffect } from "preact/hooks";
|
|
3
|
+
|
|
4
|
+
export type UseMutationObserverOptions = MutationObserverInit;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Preact hook to observe DOM mutations using MutationObserver.
|
|
8
|
+
* @param target - The element to observe.
|
|
9
|
+
* @param callback - Function to call on mutation.
|
|
10
|
+
* @param options - MutationObserver options.
|
|
11
|
+
*/
|
|
12
|
+
export function useMutationObserver(
|
|
13
|
+
targetRef: RefObject<HTMLElement | null>,
|
|
14
|
+
callback: MutationCallback,
|
|
15
|
+
options: MutationObserverInit
|
|
16
|
+
) {
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const node = targetRef.current;
|
|
19
|
+
if (!node) return;
|
|
20
|
+
|
|
21
|
+
const observer = new MutationObserver(callback);
|
|
22
|
+
observer.observe(node, options);
|
|
23
|
+
|
|
24
|
+
return () => observer.disconnect();
|
|
25
|
+
}, [targetRef, callback, options]);
|
|
26
|
+
}
|