@mwakidenis/logx-react 0.0.0-stage → 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/CHANGELOG.md +18 -0
- package/LICENSE +23 -0
- package/README.md +151 -3
- package/dist/components/index.d.mts +71 -0
- package/dist/components/index.d.ts +71 -0
- package/dist/components/index.js +268 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +262 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +316 -0
- package/dist/hooks/index.d.ts +316 -0
- package/dist/hooks/index.js +638 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +615 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/hooks/storage/index.d.mts +40 -0
- package/dist/hooks/storage/index.d.ts +40 -0
- package/dist/hooks/storage/index.js +119 -0
- package/dist/hooks/storage/index.js.map +1 -0
- package/dist/hooks/storage/index.mjs +116 -0
- package/dist/hooks/storage/index.mjs.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1066 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +974 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers/LogxProvider.d.mts +134 -0
- package/dist/providers/LogxProvider.d.ts +134 -0
- package/dist/providers/LogxProvider.js +117 -0
- package/dist/providers/LogxProvider.js.map +1 -0
- package/dist/providers/LogxProvider.mjs +114 -0
- package/dist/providers/LogxProvider.mjs.map +1 -0
- package/dist/types/index.d.mts +87 -0
- package/dist/types/index.d.ts +87 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +3 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/utilities/index.d.mts +140 -0
- package/dist/utilities/index.d.ts +140 -0
- package/dist/utilities/index.js +167 -0
- package/dist/utilities/index.js.map +1 -0
- package/dist/utilities/index.mjs +104 -0
- package/dist/utilities/index.mjs.map +1 -0
- package/package.json +127 -6
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect, Component } from 'react';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/components/CopyButton/index.tsx
|
|
5
|
+
function useLatest(value) {
|
|
6
|
+
const ref = useRef(value);
|
|
7
|
+
ref.current = value;
|
|
8
|
+
return ref;
|
|
9
|
+
}
|
|
10
|
+
function useDebouncedValue(value, options = {}) {
|
|
11
|
+
const { delay = 300, leading = false } = options;
|
|
12
|
+
const [debounced, setDebounced] = useState(value);
|
|
13
|
+
const latest = useLatest(value);
|
|
14
|
+
const timerRef = useRef(null);
|
|
15
|
+
const lastLeadingRef = useRef(void 0);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (timerRef.current !== null) {
|
|
18
|
+
clearTimeout(timerRef.current);
|
|
19
|
+
timerRef.current = null;
|
|
20
|
+
}
|
|
21
|
+
timerRef.current = setTimeout(() => {
|
|
22
|
+
setDebounced(latest.current);
|
|
23
|
+
timerRef.current = null;
|
|
24
|
+
}, delay);
|
|
25
|
+
return () => {
|
|
26
|
+
if (timerRef.current !== null) {
|
|
27
|
+
clearTimeout(timerRef.current);
|
|
28
|
+
timerRef.current = null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}, [value, delay]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!leading) return;
|
|
34
|
+
const current = latest.current;
|
|
35
|
+
if (lastLeadingRef.current !== current) {
|
|
36
|
+
lastLeadingRef.current = current;
|
|
37
|
+
setDebounced(current);
|
|
38
|
+
}
|
|
39
|
+
}, [value, leading]);
|
|
40
|
+
return debounced;
|
|
41
|
+
}
|
|
42
|
+
function useMediaQuery(query) {
|
|
43
|
+
const [matches, setMatches] = useState(false);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (typeof window === "undefined") return;
|
|
46
|
+
const media = window.matchMedia(query);
|
|
47
|
+
setMatches(media.matches);
|
|
48
|
+
const handler = (event) => setMatches(event.matches);
|
|
49
|
+
media.addEventListener("change", handler);
|
|
50
|
+
return () => {
|
|
51
|
+
media.removeEventListener("change", handler);
|
|
52
|
+
};
|
|
53
|
+
}, [query]);
|
|
54
|
+
return matches;
|
|
55
|
+
}
|
|
56
|
+
function useClipboard() {
|
|
57
|
+
const isSupported = typeof navigator !== "undefined" && !!navigator.clipboard && !!navigator.clipboard.writeText;
|
|
58
|
+
const copy = useCallback(async (text) => {
|
|
59
|
+
try {
|
|
60
|
+
if (isSupported && typeof navigator !== "undefined") {
|
|
61
|
+
await navigator.clipboard.writeText(text);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (typeof document !== "undefined") {
|
|
65
|
+
const textarea = document.createElement("textarea");
|
|
66
|
+
textarea.value = text;
|
|
67
|
+
textarea.setAttribute("readonly", "");
|
|
68
|
+
textarea.style.position = "absolute";
|
|
69
|
+
textarea.style.position = "fixed";
|
|
70
|
+
textarea.style.opacity = "0";
|
|
71
|
+
document.body.appendChild(textarea);
|
|
72
|
+
textarea.select();
|
|
73
|
+
try {
|
|
74
|
+
document.execCommand("copy");
|
|
75
|
+
} finally {
|
|
76
|
+
document.body.removeChild(textarea);
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}, [isSupported]);
|
|
85
|
+
return { copy, isSupported };
|
|
86
|
+
}
|
|
87
|
+
function CopyButton({
|
|
88
|
+
text,
|
|
89
|
+
label = "Copy",
|
|
90
|
+
copyingLabel = "Copying...",
|
|
91
|
+
successLabel = "Copied!",
|
|
92
|
+
errorLabel = "Error",
|
|
93
|
+
className,
|
|
94
|
+
onCopy,
|
|
95
|
+
onError,
|
|
96
|
+
timeout = 2e3
|
|
97
|
+
}) {
|
|
98
|
+
const [status, setStatus] = useState("idle");
|
|
99
|
+
const { copy } = useClipboard();
|
|
100
|
+
const handleCopy = useCallback(async () => {
|
|
101
|
+
setStatus("copying");
|
|
102
|
+
try {
|
|
103
|
+
const ok = await copy(text);
|
|
104
|
+
if (ok) {
|
|
105
|
+
setStatus("success");
|
|
106
|
+
onCopy?.(text);
|
|
107
|
+
} else {
|
|
108
|
+
setStatus("error");
|
|
109
|
+
onError?.(new Error("Failed to copy to clipboard"));
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
setStatus("error");
|
|
113
|
+
onError?.(err);
|
|
114
|
+
}
|
|
115
|
+
setTimeout(() => setStatus("idle"), timeout);
|
|
116
|
+
}, [text, copy, onCopy, onError, timeout]);
|
|
117
|
+
const displayLabel = status === "copying" ? copyingLabel : status === "success" ? successLabel : status === "error" ? errorLabel : label;
|
|
118
|
+
return /* @__PURE__ */ jsx(
|
|
119
|
+
"button",
|
|
120
|
+
{
|
|
121
|
+
type: "button",
|
|
122
|
+
className,
|
|
123
|
+
disabled: status === "copying",
|
|
124
|
+
onClick: handleCopy,
|
|
125
|
+
"aria-label": status === "success" ? "Copied to clipboard" : label,
|
|
126
|
+
"aria-pressed": status === "success",
|
|
127
|
+
children: displayLabel
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
function DebouncedInput({
|
|
132
|
+
value,
|
|
133
|
+
defaultValue,
|
|
134
|
+
onChange,
|
|
135
|
+
onDebouncedChange,
|
|
136
|
+
delay = 300,
|
|
137
|
+
...rest
|
|
138
|
+
}) {
|
|
139
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? "");
|
|
140
|
+
const isControlled = value !== void 0;
|
|
141
|
+
const currentValue = isControlled ? value ?? "" : internalValue;
|
|
142
|
+
const handleChange = useCallback(
|
|
143
|
+
(event) => {
|
|
144
|
+
const next = event.target.value;
|
|
145
|
+
if (!isControlled) setInternalValue(next);
|
|
146
|
+
onChange?.(next);
|
|
147
|
+
},
|
|
148
|
+
[isControlled, onChange]
|
|
149
|
+
);
|
|
150
|
+
const debounced = useDebouncedValue(currentValue, { delay });
|
|
151
|
+
const initialized = useRef(false);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!initialized.current) {
|
|
154
|
+
initialized.current = true;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
onDebouncedChange?.(debounced);
|
|
158
|
+
}, [debounced, onDebouncedChange]);
|
|
159
|
+
return /* @__PURE__ */ jsx("input", { ...rest, value: currentValue, onChange: handleChange });
|
|
160
|
+
}
|
|
161
|
+
function PasswordInput({
|
|
162
|
+
value,
|
|
163
|
+
defaultValue,
|
|
164
|
+
onChange,
|
|
165
|
+
label = "Password",
|
|
166
|
+
placeholder,
|
|
167
|
+
disabled = false,
|
|
168
|
+
required = false,
|
|
169
|
+
name,
|
|
170
|
+
id,
|
|
171
|
+
showToggle = true,
|
|
172
|
+
className
|
|
173
|
+
}) {
|
|
174
|
+
const [show, setShow] = useState(false);
|
|
175
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? "");
|
|
176
|
+
const isControlled = value !== void 0;
|
|
177
|
+
const currentValue = isControlled ? value ?? "" : internalValue;
|
|
178
|
+
const handleChange = useCallback(
|
|
179
|
+
(event) => {
|
|
180
|
+
const next = event.target.value;
|
|
181
|
+
if (!isControlled) setInternalValue(next);
|
|
182
|
+
onChange?.(next);
|
|
183
|
+
},
|
|
184
|
+
[isControlled, onChange]
|
|
185
|
+
);
|
|
186
|
+
const toggle = useCallback(() => setShow((current) => !current), []);
|
|
187
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
188
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: id, children: label }),
|
|
189
|
+
/* @__PURE__ */ jsx(
|
|
190
|
+
"input",
|
|
191
|
+
{
|
|
192
|
+
id,
|
|
193
|
+
name,
|
|
194
|
+
type: show ? "text" : "password",
|
|
195
|
+
value: currentValue,
|
|
196
|
+
onChange: handleChange,
|
|
197
|
+
placeholder,
|
|
198
|
+
disabled,
|
|
199
|
+
required,
|
|
200
|
+
"aria-label": label
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
showToggle && /* @__PURE__ */ jsx(
|
|
204
|
+
"button",
|
|
205
|
+
{
|
|
206
|
+
type: "button",
|
|
207
|
+
onClick: toggle,
|
|
208
|
+
"aria-label": show ? "Hide password" : "Show password",
|
|
209
|
+
"aria-pressed": show,
|
|
210
|
+
children: show ? "Hide password" : "Show password"
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
] });
|
|
214
|
+
}
|
|
215
|
+
function Responsive({ children, query, fallback }) {
|
|
216
|
+
const matches = useMediaQuery(query);
|
|
217
|
+
const renderChildren = useCallback(
|
|
218
|
+
(m) => m ? children(m) : fallback ?? null,
|
|
219
|
+
[children, fallback]
|
|
220
|
+
);
|
|
221
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderChildren(matches) });
|
|
222
|
+
}
|
|
223
|
+
var ErrorBoundary = class extends Component {
|
|
224
|
+
constructor() {
|
|
225
|
+
super(...arguments);
|
|
226
|
+
this.state = { hasError: false, error: null, info: null };
|
|
227
|
+
}
|
|
228
|
+
static getDerivedStateFromError(error) {
|
|
229
|
+
return { hasError: true, error, info: null };
|
|
230
|
+
}
|
|
231
|
+
componentDidCatch(error, info) {
|
|
232
|
+
this.setState({ info });
|
|
233
|
+
this.props.onError?.(error, info);
|
|
234
|
+
}
|
|
235
|
+
componentDidUpdate(prevProps) {
|
|
236
|
+
if (this.props.resetKeys !== prevProps.resetKeys) {
|
|
237
|
+
const prev = JSON.stringify(prevProps.resetKeys);
|
|
238
|
+
const next = JSON.stringify(this.props.resetKeys);
|
|
239
|
+
if (prev !== next) {
|
|
240
|
+
this.setState({ hasError: false, error: null, info: null });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
render() {
|
|
245
|
+
if (this.state.hasError) {
|
|
246
|
+
const { fallback } = this.props;
|
|
247
|
+
if (fallback === void 0) return null;
|
|
248
|
+
if (typeof fallback === "function") {
|
|
249
|
+
return fallback(
|
|
250
|
+
this.state.error,
|
|
251
|
+
this.state.info
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return fallback;
|
|
255
|
+
}
|
|
256
|
+
return this.props.children;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export { CopyButton, DebouncedInput, ErrorBoundary, PasswordInput, Responsive };
|
|
261
|
+
//# sourceMappingURL=index.mjs.map
|
|
262
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/useLatest.ts","../../src/hooks/useDebouncedValue.ts","../../src/hooks/useMediaQuery.ts","../../src/hooks/useClipboard.ts","../../src/components/CopyButton/index.tsx","../../src/components/DebouncedInput/index.tsx","../../src/components/PasswordInput/index.tsx","../../src/components/Responsive/index.tsx","../../src/components/ErrorBoundary/index.tsx"],"names":["useRef","useState","useEffect","useCallback","jsx"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useRef } from \"react\";\r\n\r\n/**\r\n * Returns a ref whose `.current` always holds the latest value without\r\n * triggering re-renders. Useful for accessing the most recent value inside\r\n * timers, callbacks or event listeners without stale closures.\r\n *\r\n * @param value - The value to track\r\n */\r\nexport function useLatest<T>(value: T): { current: T } {\r\n const ref = useRef(value);\r\n ref.current = value;\r\n return ref;\r\n}\r\n","import { useEffect, useRef, useState } from \"react\";\r\nimport { useLatest } from \"./useLatest\";\r\nimport type { DebouncedValueOptions } from \"../types\";\r\n\r\n/**\r\n * Debounce a value.\r\n *\r\n * Returns the value after it has stopped changing for `delay` milliseconds.\r\n * Uses reference equality to detect changes; for objects/arrays pass a new\r\n * reference only when you want a debounce to restart.\r\n *\r\n * LOGX provides function debouncing (@mwakidenis/logx/function). This hook\r\n * implements value debouncing (a React-specific behaviour) on top of timers;\r\n * it does not map to an existing LOGX function.\r\n *\r\n * @param value - The value to debounce\r\n * @param options - delay (ms, default 300) and leading (default false)\r\n */\r\nexport function useDebouncedValue<T>(\r\n value: T,\r\n options: DebouncedValueOptions = {},\r\n): T {\r\n const { delay = 300, leading = false } = options;\r\n const [debounced, setDebounced] = useState(value);\r\n const latest = useLatest(value);\r\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const lastLeadingRef = useRef<T | undefined>(undefined);\r\n\r\n useEffect(() => {\r\n if (timerRef.current !== null) {\r\n clearTimeout(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n timerRef.current = setTimeout(() => {\r\n setDebounced(latest.current);\r\n timerRef.current = null;\r\n }, delay);\r\n return () => {\r\n if (timerRef.current !== null) {\r\n clearTimeout(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n };\r\n }, [value, delay]);\r\n\r\n useEffect(() => {\r\n if (!leading) return;\r\n const current = latest.current;\r\n if (lastLeadingRef.current !== current) {\r\n lastLeadingRef.current = current;\r\n setDebounced(current);\r\n }\r\n }, [value, leading]);\r\n\r\n return debounced;\r\n}\r\n","import { useEffect, useState } from \"react\";\r\n\r\n/**\r\n * Track whether a media query matches.\r\n *\r\n * SSR-safe: returns `false` on the server and before mount, then reflects the\r\n * real match status after mount. Listens for `change` events and cleans up on\r\n * unmount.\r\n *\r\n * @param query - A CSS media query string\r\n */\r\nexport function useMediaQuery(query: string): boolean {\r\n const [matches, setMatches] = useState(false);\r\n\r\n useEffect(() => {\r\n if (typeof window === \"undefined\") return;\r\n const media = window.matchMedia(query);\r\n setMatches(media.matches);\r\n const handler = (event: MediaQueryListEvent) => setMatches(event.matches);\r\n media.addEventListener(\"change\", handler);\r\n return () => {\r\n media.removeEventListener(\"change\", handler);\r\n };\r\n }, [query]);\r\n\r\n return matches;\r\n}\r\n","import { useCallback } from \"react\";\r\n\r\n/**\r\n * Copy text to the clipboard.\r\n *\r\n * Uses the Clipboard API when available, with a textarea fallback for older\r\n * browsers. Never throws; returns false on failure.\r\n */\r\nexport function useClipboard(): {\r\n copy: (text: string) => Promise<boolean>;\r\n isSupported: boolean;\r\n} {\r\n const isSupported =\r\n typeof navigator !== \"undefined\" && !!navigator.clipboard && !!navigator.clipboard.writeText;\r\n\r\n const copy = useCallback(async (text: string): Promise<boolean> => {\r\n try {\r\n if (isSupported && typeof navigator !== \"undefined\") {\r\n await navigator.clipboard.writeText(text);\r\n return true;\r\n }\r\n // Fallback for environments without the Clipboard API.\r\n if (typeof document !== \"undefined\") {\r\n const textarea = document.createElement(\"textarea\");\r\n textarea.value = text;\r\n textarea.setAttribute(\"readonly\", \"\");\r\n textarea.style.position = \"absolute\";\r\n textarea.style.position = \"fixed\";\r\n textarea.style.opacity = \"0\";\r\n document.body.appendChild(textarea);\r\n textarea.select();\r\n try {\r\n document.execCommand(\"copy\");\r\n } finally {\r\n document.body.removeChild(textarea);\r\n }\r\n return true;\r\n }\r\n return false;\r\n } catch {\r\n return false;\r\n }\r\n }, [isSupported]);\r\n\r\n return { copy, isSupported };\r\n}\r\n","import { useCallback, useState } from \"react\";\r\nimport { useClipboard } from \"../../hooks\";\r\nimport type { CopyButtonProps } from \"../../types\";\r\n\r\n/**\r\n * Accessible copy-to-clipboard button.\r\n *\r\n * - Copies the supplied `text` to the clipboard.\r\n * - Shows idle / copying / success / error states.\r\n * - Supports custom labels per state and a custom className.\r\n * - Keyboard accessible (native button). No icon library dependency.\r\n *\r\n * @param props - Component props\r\n */\r\nexport function CopyButton({\r\n text,\r\n label = \"Copy\",\r\n copyingLabel = \"Copying...\",\r\n successLabel = \"Copied!\",\r\n errorLabel = \"Error\",\r\n className,\r\n onCopy,\r\n onError,\r\n timeout = 2000,\r\n}: CopyButtonProps): JSX.Element {\r\n const [status, setStatus] = useState<\"idle\" | \"copying\" | \"success\" | \"error\">(\"idle\");\r\n const { copy } = useClipboard();\r\n\r\n const handleCopy = useCallback(async () => {\r\n setStatus(\"copying\");\r\n try {\r\n const ok = await copy(text);\r\n if (ok) {\r\n setStatus(\"success\");\r\n onCopy?.(text);\r\n } else {\r\n setStatus(\"error\");\r\n onError?.(new Error(\"Failed to copy to clipboard\"));\r\n }\r\n } catch (err) {\r\n setStatus(\"error\");\r\n onError?.(err as Error);\r\n }\r\n setTimeout(() => setStatus(\"idle\"), timeout);\r\n }, [text, copy, onCopy, onError, timeout]);\r\n\r\n const displayLabel =\r\n status === \"copying\"\r\n ? copyingLabel\r\n : status === \"success\"\r\n ? successLabel\r\n : status === \"error\"\r\n ? errorLabel\r\n : label;\r\n\r\n return (\r\n <button\r\n type=\"button\"\r\n className={className}\r\n disabled={status === \"copying\"}\r\n onClick={handleCopy}\r\n aria-label={status === \"success\" ? \"Copied to clipboard\" : label}\r\n aria-pressed={status === \"success\"}\r\n >\r\n {displayLabel}\r\n </button>\r\n );\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport type { ChangeEvent } from \"react\";\r\nimport { useDebouncedValue } from \"../../hooks\";\r\nimport type { DebouncedInputProps } from \"../../types\";\r\n\r\n/**\r\n * Controlled input with a separate debounced value.\r\n *\r\n * - `value`/`defaultValue` for controlled/uncontrolled usage.\r\n * - `delay` controls debounce duration.\r\n * - `onChange` fires immediately; `onDebouncedChange` fires after debounce.\r\n * - Accepts standard input attributes for form integration.\r\n *\r\n * @param props - Component props\r\n */\r\nexport function DebouncedInput({\r\n value,\r\n defaultValue,\r\n onChange,\r\n onDebouncedChange,\r\n delay = 300,\r\n ...rest\r\n}: DebouncedInputProps): JSX.Element {\r\n const [internalValue, setInternalValue] = useState(defaultValue ?? \"\");\r\n const isControlled = value !== undefined;\r\n const currentValue = isControlled ? (value ?? \"\") : internalValue;\r\n\r\n const handleChange = useCallback(\r\n (event: ChangeEvent<HTMLInputElement>) => {\r\n const next = event.target.value;\r\n if (!isControlled) setInternalValue(next);\r\n onChange?.(next);\r\n },\r\n [isControlled, onChange],\r\n );\r\n\r\n const debounced = useDebouncedValue(currentValue, { delay });\r\n const initialized = useRef(false);\r\n\r\n useEffect(() => {\r\n if (!initialized.current) {\r\n initialized.current = true;\r\n return;\r\n }\r\n onDebouncedChange?.(debounced);\r\n }, [debounced, onDebouncedChange]);\r\n\r\n return <input {...rest} value={currentValue} onChange={handleChange} />;\r\n}\r\n","import { useCallback, useState } from \"react\";\r\nimport type { ChangeEvent } from \"react\";\r\nimport type { PasswordInputProps } from \"../../types\";\r\n\r\n/**\r\n * Accessible show/hide password input.\r\n *\r\n * - Controlled/uncontrolled value.\r\n * - Accessible toggle button labels.\r\n * - Keyboard accessible; no external UI framework dependency.\r\n *\r\n * @param props - Component props\r\n */\r\nexport function PasswordInput({\r\n value,\r\n defaultValue,\r\n onChange,\r\n label = \"Password\",\r\n placeholder,\r\n disabled = false,\r\n required = false,\r\n name,\r\n id,\r\n showToggle = true,\r\n className,\r\n}: PasswordInputProps): JSX.Element {\r\n const [show, setShow] = useState(false);\r\n const [internalValue, setInternalValue] = useState(defaultValue ?? \"\");\r\n const isControlled = value !== undefined;\r\n const currentValue = isControlled ? (value ?? \"\") : internalValue;\r\n\r\n const handleChange = useCallback(\r\n (event: ChangeEvent<HTMLInputElement>) => {\r\n const next = event.target.value;\r\n if (!isControlled) setInternalValue(next);\r\n onChange?.(next);\r\n },\r\n [isControlled, onChange],\r\n );\r\n\r\n const toggle = useCallback(() => setShow((current) => !current), []);\r\n\r\n return (\r\n <div className={className}>\r\n {label && <label htmlFor={id}>{label}</label>}\r\n <input\r\n id={id}\r\n name={name}\r\n type={show ? \"text\" : \"password\"}\r\n value={currentValue}\r\n onChange={handleChange}\r\n placeholder={placeholder}\r\n disabled={disabled}\r\n required={required}\r\n aria-label={label}\r\n />\r\n {showToggle && (\r\n <button\r\n type=\"button\"\r\n onClick={toggle}\r\n aria-label={show ? \"Hide password\" : \"Show password\"}\r\n aria-pressed={show}\r\n >\r\n {show ? \"Hide password\" : \"Show password\"}\r\n </button>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useCallback } from \"react\";\r\nimport { useMediaQuery } from \"../../hooks\";\r\nimport type { ResponsiveProps } from \"../../types\";\r\n\r\n/**\r\n * Conditionally render content based on a media query.\r\n *\r\n * SSR-safe: on the server and before mount, renders `fallback` (or null),\r\n * avoiding hydration mismatches. After mount, renders `children(matches)` when\r\n * the query matches, otherwise `fallback`.\r\n *\r\n * @param props - Component props\r\n */\r\nexport function Responsive({ children, query, fallback }: ResponsiveProps): JSX.Element {\r\n const matches = useMediaQuery(query);\r\n const renderChildren = useCallback(\r\n (m: boolean) => (m ? children(m) : fallback ?? null),\r\n [children, fallback],\r\n );\r\n return <>{renderChildren(matches)}</>;\r\n}\r\n","import { Component, ReactNode } from \"react\";\r\nimport type { ErrorBoundaryProps, ErrorBoundaryState } from \"../../types\";\r\n\r\n/**\r\n * React error boundary.\r\n *\r\n * Captures descendant rendering errors, supports a fallback UI (element or\r\n * render function receiving the error and React error info), an optional\r\n * onError callback, and optional reset via `resetKeys` changes.\r\n *\r\n * Limitations: does not catch errors in event handlers, asynchronous code\r\n * (setTimeout, promises), or server-side rendering. Those must be handled\r\n * separately (try/catch, error-first callbacks, etc.).\r\n *\r\n * @param props - Component props\r\n */\r\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\r\n state: ErrorBoundaryState = { hasError: false, error: null, info: null };\r\n\r\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\r\n return { hasError: true, error, info: null };\r\n }\r\n\r\n componentDidCatch(error: Error, info: React.ErrorInfo): void {\r\n this.setState({ info });\r\n this.props.onError?.(error, info);\r\n }\r\n\r\n componentDidUpdate(prevProps: ErrorBoundaryProps): void {\r\n if (this.props.resetKeys !== prevProps.resetKeys) {\r\n const prev = JSON.stringify(prevProps.resetKeys);\r\n const next = JSON.stringify(this.props.resetKeys);\r\n if (prev !== next) {\r\n this.setState({ hasError: false, error: null, info: null });\r\n }\r\n }\r\n }\r\n\r\n render(): ReactNode {\r\n if (this.state.hasError) {\r\n const { fallback } = this.props;\r\n if (fallback === undefined) return null;\r\n if (typeof fallback === \"function\") {\r\n return (fallback as (error: Error, info: React.ErrorInfo) => ReactNode)(\r\n this.state.error!,\r\n this.state.info!,\r\n );\r\n }\r\n return fallback;\r\n }\r\n return this.props.children;\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { DebouncedValueOptions, AsyncState } from '../types/index.mjs';
|
|
2
|
+
export { useLocalStorage, useSessionStorage } from './storage/index.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A boolean toggle hook.
|
|
6
|
+
*
|
|
7
|
+
* Returns the current boolean value and a stable set of actions to mutate it.
|
|
8
|
+
* All callbacks are stable (no unnecessary re-renders of consumers).
|
|
9
|
+
*
|
|
10
|
+
* @param initialValue - Initial boolean value (defaults to false)
|
|
11
|
+
*/
|
|
12
|
+
declare function useToggle(initialValue?: boolean): [
|
|
13
|
+
boolean,
|
|
14
|
+
{
|
|
15
|
+
toggle: () => void;
|
|
16
|
+
set: (next?: boolean) => void;
|
|
17
|
+
setTrue: () => void;
|
|
18
|
+
setFalse: () => void;
|
|
19
|
+
}
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A boolean state hook with explicit setTrue/setFalse/toggle actions.
|
|
24
|
+
*
|
|
25
|
+
* Alias of useToggle with a more descriptive name. Provides stable callbacks.
|
|
26
|
+
*
|
|
27
|
+
* @param initialValue - Initial boolean value (defaults to false)
|
|
28
|
+
*/
|
|
29
|
+
declare function useBoolean(initialValue?: boolean): [
|
|
30
|
+
boolean,
|
|
31
|
+
{
|
|
32
|
+
setTrue: () => void;
|
|
33
|
+
setFalse: () => void;
|
|
34
|
+
toggle: () => void;
|
|
35
|
+
set: (next?: boolean) => void;
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A numeric counter hook.
|
|
41
|
+
*
|
|
42
|
+
* @param initialValue - Starting value (defaults to 0)
|
|
43
|
+
*/
|
|
44
|
+
declare function useCounter(initialValue?: number): {
|
|
45
|
+
count: number;
|
|
46
|
+
increment: (step?: number) => void;
|
|
47
|
+
decrement: (step?: number) => void;
|
|
48
|
+
reset: () => void;
|
|
49
|
+
set: (value: number) => void;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns the previous value of a tracked value.
|
|
54
|
+
*
|
|
55
|
+
* On first render returns undefined (there is no previous value yet).
|
|
56
|
+
* The returned ref updates after each render so the value is always the
|
|
57
|
+
* value from the previous committed render.
|
|
58
|
+
*
|
|
59
|
+
* @param value - The value to track
|
|
60
|
+
*/
|
|
61
|
+
declare function usePrevious<T>(value: T): T | undefined;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns a ref whose `.current` always holds the latest value without
|
|
65
|
+
* triggering re-renders. Useful for accessing the most recent value inside
|
|
66
|
+
* timers, callbacks or event listeners without stale closures.
|
|
67
|
+
*
|
|
68
|
+
* @param value - The value to track
|
|
69
|
+
*/
|
|
70
|
+
declare function useLatest<T>(value: T): {
|
|
71
|
+
current: T;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns whether the component is mounted.
|
|
76
|
+
*
|
|
77
|
+
* The value starts false (SSR-safe) and becomes true after mount. It returns
|
|
78
|
+
* to false on unmount. Use sparingly; prefer event-driven logic where possible.
|
|
79
|
+
*
|
|
80
|
+
* NOTE: Reading this during render can cause a flash of unmounted content on
|
|
81
|
+
* the client. Prefer using it inside effects or event handlers.
|
|
82
|
+
*/
|
|
83
|
+
declare function useMounted(): boolean;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Array state with immutable helpers.
|
|
87
|
+
*
|
|
88
|
+
* Several helpers delegate to @mwakidenis/logx array utilities (unique,
|
|
89
|
+
* compact, first, last, take, drop, shuffle, partition, difference,
|
|
90
|
+
* intersection, union, flatten). React-specific state management is kept in
|
|
91
|
+
* this hook; LOGX provides the pure array transformations.
|
|
92
|
+
*
|
|
93
|
+
* @param initialValue - Initial array (defaults to [])
|
|
94
|
+
*/
|
|
95
|
+
declare function useArray<T>(initialValue?: T[]): {
|
|
96
|
+
array: T[];
|
|
97
|
+
set: (value: T[] | ((prev: T[]) => T[])) => void;
|
|
98
|
+
push: (...items: T[]) => void;
|
|
99
|
+
unshift: (...items: T[]) => void;
|
|
100
|
+
remove: (index: number) => void;
|
|
101
|
+
removeBy: (predicate: (value: T, index: number) => unknown) => void;
|
|
102
|
+
update: (index: number, value: T) => void;
|
|
103
|
+
clear: () => void;
|
|
104
|
+
reset: () => void;
|
|
105
|
+
unique: () => void;
|
|
106
|
+
compact: () => void;
|
|
107
|
+
shuffle: () => void;
|
|
108
|
+
first: () => T | undefined;
|
|
109
|
+
last: () => T | undefined;
|
|
110
|
+
take: (count: number) => T[];
|
|
111
|
+
drop: (count: number) => T[];
|
|
112
|
+
difference: (...others: T[][]) => T[];
|
|
113
|
+
intersection: (...others: T[][]) => T[];
|
|
114
|
+
union: (...others: T[][]) => T[];
|
|
115
|
+
partition: (predicate: (value: T, index: number) => unknown) => [T[], T[]];
|
|
116
|
+
flatten: () => unknown[];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Object state with immutable helpers.
|
|
121
|
+
*
|
|
122
|
+
* Several helpers delegate to @mwakidenis/logx object utilities (get, set,
|
|
123
|
+
* pick, omit, merge, keys, values, entries, clone, deepClone, mapValues).
|
|
124
|
+
* React-specific state management is kept in this hook; LOGX provides the pure
|
|
125
|
+
* object transformations.
|
|
126
|
+
*
|
|
127
|
+
* @param initialValue - Initial object (defaults to {})
|
|
128
|
+
*/
|
|
129
|
+
declare function useObject<T extends Record<string, unknown>>(initialValue?: T): {
|
|
130
|
+
object: T;
|
|
131
|
+
set: (value: T | ((prev: T) => T)) => void;
|
|
132
|
+
setKey: (key: string, value: unknown) => void;
|
|
133
|
+
removeKey: (key: string) => void;
|
|
134
|
+
clear: () => void;
|
|
135
|
+
reset: () => void;
|
|
136
|
+
pick: (keys: readonly string[]) => Partial<T>;
|
|
137
|
+
omit: (keys: readonly string[]) => Partial<T>;
|
|
138
|
+
merge: (sources: Partial<T>[]) => T;
|
|
139
|
+
get: (path: string | Array<string | number>) => unknown;
|
|
140
|
+
keys: () => string[];
|
|
141
|
+
values: () => unknown[];
|
|
142
|
+
entries: () => [string, unknown][];
|
|
143
|
+
clone: () => T;
|
|
144
|
+
deepClone: () => T;
|
|
145
|
+
mapValues: <U>(iteratee: (value: unknown, key: string) => U) => Record<string, U>;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Debounce a value.
|
|
150
|
+
*
|
|
151
|
+
* Returns the value after it has stopped changing for `delay` milliseconds.
|
|
152
|
+
* Uses reference equality to detect changes; for objects/arrays pass a new
|
|
153
|
+
* reference only when you want a debounce to restart.
|
|
154
|
+
*
|
|
155
|
+
* LOGX provides function debouncing (@mwakidenis/logx/function). This hook
|
|
156
|
+
* implements value debouncing (a React-specific behaviour) on top of timers;
|
|
157
|
+
* it does not map to an existing LOGX function.
|
|
158
|
+
*
|
|
159
|
+
* @param value - The value to debounce
|
|
160
|
+
* @param options - delay (ms, default 300) and leading (default false)
|
|
161
|
+
*/
|
|
162
|
+
declare function useDebouncedValue<T>(value: T, options?: DebouncedValueOptions): T;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Debounce a callback using LOGX's `debounce` (@mwakidenis/logx/function).
|
|
166
|
+
*
|
|
167
|
+
* LOGX debounce is trailing-edge only: the function is invoked after `delay`
|
|
168
|
+
* milliseconds of inactivity. It exposes `cancel`, `flush` and `pending`.
|
|
169
|
+
*
|
|
170
|
+
* The latest callback reference is captured internally so the debounced
|
|
171
|
+
* function never holds a stale closure, even if `callback` changes every render.
|
|
172
|
+
*
|
|
173
|
+
* @param callback - Function to debounce
|
|
174
|
+
* @param delay - Delay in milliseconds
|
|
175
|
+
*/
|
|
176
|
+
declare function useDebouncedCallback<F extends (...args: unknown[]) => unknown>(callback: F, delay: number): {
|
|
177
|
+
(...args: Parameters<F>): void;
|
|
178
|
+
cancel: () => void;
|
|
179
|
+
flush: () => void;
|
|
180
|
+
pending: () => boolean;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Throttle a callback using LOGX's `throttle` (@mwakidenis/logx/function).
|
|
185
|
+
*
|
|
186
|
+
* LOGX throttle invokes on the leading edge and flushes the trailing edge at
|
|
187
|
+
* the end of the wait period. It exposes `cancel`, `flush` and `pending`.
|
|
188
|
+
*
|
|
189
|
+
* @param callback - Function to throttle
|
|
190
|
+
* @param wait - Wait period in milliseconds
|
|
191
|
+
*/
|
|
192
|
+
declare function useThrottle<F extends (...args: unknown[]) => unknown>(callback: F, wait: number): {
|
|
193
|
+
(...args: Parameters<F>): void;
|
|
194
|
+
cancel: () => void;
|
|
195
|
+
flush: () => void;
|
|
196
|
+
pending: () => boolean;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Track whether a media query matches.
|
|
201
|
+
*
|
|
202
|
+
* SSR-safe: returns `false` on the server and before mount, then reflects the
|
|
203
|
+
* real match status after mount. Listens for `change` events and cleans up on
|
|
204
|
+
* unmount.
|
|
205
|
+
*
|
|
206
|
+
* @param query - A CSS media query string
|
|
207
|
+
*/
|
|
208
|
+
declare function useMediaQuery(query: string): boolean;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Track the window size (innerWidth / innerHeight).
|
|
212
|
+
*
|
|
213
|
+
* SSR-safe: returns { width: 0, height: 0 } on the server and first client
|
|
214
|
+
* render, then updates on resize. Cleans up the listener on unmount.
|
|
215
|
+
*/
|
|
216
|
+
declare function useWindowSize(): {
|
|
217
|
+
width: number;
|
|
218
|
+
height: number;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Fire a handler when a click occurs outside a ref'd element.
|
|
223
|
+
*
|
|
224
|
+
* Uses useLatest internally so the handler is always current without
|
|
225
|
+
* re-subscribing the document listeners every render.
|
|
226
|
+
*
|
|
227
|
+
* @param ref - A ref to the element to ignore clicks for
|
|
228
|
+
* @param handler - Callback fired on outside click
|
|
229
|
+
*/
|
|
230
|
+
declare function useClickOutside(ref: {
|
|
231
|
+
current: HTMLElement | null | undefined;
|
|
232
|
+
}, handler: (event: MouseEvent | TouchEvent) => void): void;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Attach an event listener with automatic cleanup on unmount.
|
|
236
|
+
*
|
|
237
|
+
* Supports window, document, element, or Document/Window targets. The listener
|
|
238
|
+
* is captured via a ref so it stays current without re-attaching.
|
|
239
|
+
*
|
|
240
|
+
* @param target - EventTarget (window, document, element) or null/undefined
|
|
241
|
+
* @param type - Event type
|
|
242
|
+
* @param listener - Event listener
|
|
243
|
+
* @param options - AddEventListenerOptions or capture boolean
|
|
244
|
+
*/
|
|
245
|
+
declare function useEventListener(target: EventTarget | null | undefined, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions): void;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Copy text to the clipboard.
|
|
249
|
+
*
|
|
250
|
+
* Uses the Clipboard API when available, with a textarea fallback for older
|
|
251
|
+
* browsers. Never throws; returns false on failure.
|
|
252
|
+
*/
|
|
253
|
+
declare function useClipboard(): {
|
|
254
|
+
copy: (text: string) => Promise<boolean>;
|
|
255
|
+
isSupported: boolean;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Track online/offline status.
|
|
260
|
+
*
|
|
261
|
+
* SSR-safe: assumes `true` on the server, then reads `navigator.onLine` after
|
|
262
|
+
* mount and listens for `online`/`offline` events.
|
|
263
|
+
*/
|
|
264
|
+
declare function useOnlineStatus(): boolean;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* A controllable timeout hook.
|
|
268
|
+
*
|
|
269
|
+
* Provides start/stop/reset and clears the timer on unmount. The callback is
|
|
270
|
+
* captured via useLatest so it never goes stale. `active` reflects whether a
|
|
271
|
+
* timeout is currently scheduled.
|
|
272
|
+
*
|
|
273
|
+
* @param callback - Function to invoke after the delay
|
|
274
|
+
* @param delay - Delay in milliseconds, or null to disable
|
|
275
|
+
*/
|
|
276
|
+
declare function useTimeout(callback: () => void, delay: number | null): {
|
|
277
|
+
start: () => void;
|
|
278
|
+
stop: () => void;
|
|
279
|
+
reset: () => void;
|
|
280
|
+
active: boolean;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* A controllable interval hook.
|
|
285
|
+
*
|
|
286
|
+
* Provides start/stop and clears the interval on unmount. The callback is
|
|
287
|
+
* captured via useLatest so it never goes stale. `active` reflects whether an
|
|
288
|
+
* interval is currently scheduled.
|
|
289
|
+
*
|
|
290
|
+
* @param callback - Function to invoke every `delay` milliseconds
|
|
291
|
+
* @param delay - Delay in milliseconds, or null to disable
|
|
292
|
+
*/
|
|
293
|
+
declare function useInterval(callback: () => void, delay: number | null): {
|
|
294
|
+
start: () => void;
|
|
295
|
+
stop: () => void;
|
|
296
|
+
active: boolean;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Manage an asynchronous operation with loading, success and error states.
|
|
301
|
+
*
|
|
302
|
+
* Supports cancellation via AbortController, race-condition protection (only
|
|
303
|
+
* the latest invocation updates state), and manual execute / reset.
|
|
304
|
+
*
|
|
305
|
+
* @param asyncFn - Function returning a Promise
|
|
306
|
+
* @param immediate - Whether to invoke immediately on mount (default true)
|
|
307
|
+
*/
|
|
308
|
+
declare function useAsync<T>(asyncFn: () => Promise<T>, immediate?: boolean): AsyncState<T> & {
|
|
309
|
+
execute: () => Promise<void>;
|
|
310
|
+
setData: (data: T) => void;
|
|
311
|
+
setError: (error: Error) => void;
|
|
312
|
+
reset: () => void;
|
|
313
|
+
cancel: () => void;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export { useArray, useAsync, useBoolean, useClickOutside, useClipboard, useCounter, useDebouncedCallback, useDebouncedValue, useEventListener, useInterval, useLatest, useMediaQuery, useMounted, useObject, useOnlineStatus, usePrevious, useThrottle, useTimeout, useToggle, useWindowSize };
|