usethishook 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/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/index.cjs +857 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +252 -0
- package/dist/index.d.ts +252 -0
- package/dist/index.js +824 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useRef, useSyncExternalStore, useMemo } from 'react';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/hooks/useToggle.ts
|
|
5
|
+
var useToggle = (initialValue = false) => {
|
|
6
|
+
const [value, setValue] = useState(initialValue);
|
|
7
|
+
const toggle = useCallback(() => {
|
|
8
|
+
setValue((previous) => !previous);
|
|
9
|
+
}, []);
|
|
10
|
+
const setTrue = useCallback(() => {
|
|
11
|
+
setValue(true);
|
|
12
|
+
}, []);
|
|
13
|
+
const setFalse = useCallback(() => {
|
|
14
|
+
setValue(false);
|
|
15
|
+
}, []);
|
|
16
|
+
return { value, toggle, setTrue, setFalse, setValue };
|
|
17
|
+
};
|
|
18
|
+
var useCounter = (initialValue = 0, step = 1) => {
|
|
19
|
+
const [count, setCount] = useState(initialValue);
|
|
20
|
+
const increment = useCallback(() => {
|
|
21
|
+
setCount((previous) => previous + step);
|
|
22
|
+
}, [step]);
|
|
23
|
+
const decrement = useCallback(() => {
|
|
24
|
+
setCount((previous) => previous - step);
|
|
25
|
+
}, [step]);
|
|
26
|
+
const reset = useCallback(() => {
|
|
27
|
+
setCount(initialValue);
|
|
28
|
+
}, [initialValue]);
|
|
29
|
+
return { count, increment, decrement, reset, setCount };
|
|
30
|
+
};
|
|
31
|
+
var useDisclosure = (initialOpen = false) => {
|
|
32
|
+
const [isOpen, setIsOpen] = useState(initialOpen);
|
|
33
|
+
const open = useCallback(() => {
|
|
34
|
+
setIsOpen(true);
|
|
35
|
+
}, []);
|
|
36
|
+
const close = useCallback(() => {
|
|
37
|
+
setIsOpen(false);
|
|
38
|
+
}, []);
|
|
39
|
+
const toggle = useCallback(() => {
|
|
40
|
+
setIsOpen((previous) => !previous);
|
|
41
|
+
}, []);
|
|
42
|
+
return { isOpen, open, close, toggle };
|
|
43
|
+
};
|
|
44
|
+
var useDebounce = (value, delayMs = 300) => {
|
|
45
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const timeoutId = window.setTimeout(() => {
|
|
48
|
+
setDebouncedValue(value);
|
|
49
|
+
}, delayMs);
|
|
50
|
+
return () => {
|
|
51
|
+
window.clearTimeout(timeoutId);
|
|
52
|
+
};
|
|
53
|
+
}, [value, delayMs]);
|
|
54
|
+
return debouncedValue;
|
|
55
|
+
};
|
|
56
|
+
var usePrevious = (value) => {
|
|
57
|
+
const currentRef = useRef(value);
|
|
58
|
+
const previousRef = useRef(void 0);
|
|
59
|
+
if (currentRef.current !== value) {
|
|
60
|
+
previousRef.current = currentRef.current;
|
|
61
|
+
currentRef.current = value;
|
|
62
|
+
}
|
|
63
|
+
return previousRef.current;
|
|
64
|
+
};
|
|
65
|
+
var useInterval = (callback, delayMs) => {
|
|
66
|
+
const savedCallback = useRef(callback);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
savedCallback.current = callback;
|
|
69
|
+
}, [callback]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (delayMs === null) return;
|
|
72
|
+
const intervalId = window.setInterval(() => {
|
|
73
|
+
savedCallback.current();
|
|
74
|
+
}, delayMs);
|
|
75
|
+
return () => {
|
|
76
|
+
window.clearInterval(intervalId);
|
|
77
|
+
};
|
|
78
|
+
}, [delayMs]);
|
|
79
|
+
};
|
|
80
|
+
function subscribe(onStoreChange) {
|
|
81
|
+
window.addEventListener("online", onStoreChange);
|
|
82
|
+
window.addEventListener("offline", onStoreChange);
|
|
83
|
+
return () => {
|
|
84
|
+
window.removeEventListener("online", onStoreChange);
|
|
85
|
+
window.removeEventListener("offline", onStoreChange);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function getSnapshot() {
|
|
89
|
+
return navigator.onLine;
|
|
90
|
+
}
|
|
91
|
+
function getServerSnapshot() {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
var useOnlineStatus = () => useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
95
|
+
var useMediaQuery = (query) => {
|
|
96
|
+
const [matches, setMatches] = useState(() => {
|
|
97
|
+
if (typeof window === "undefined") return false;
|
|
98
|
+
return window.matchMedia(query).matches;
|
|
99
|
+
});
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const mediaQueryList = window.matchMedia(query);
|
|
102
|
+
const handleChange = (event) => {
|
|
103
|
+
setMatches(event.matches);
|
|
104
|
+
};
|
|
105
|
+
setMatches(mediaQueryList.matches);
|
|
106
|
+
mediaQueryList.addEventListener("change", handleChange);
|
|
107
|
+
return () => {
|
|
108
|
+
mediaQueryList.removeEventListener("change", handleChange);
|
|
109
|
+
};
|
|
110
|
+
}, [query]);
|
|
111
|
+
return matches;
|
|
112
|
+
};
|
|
113
|
+
var useWindowSize = () => {
|
|
114
|
+
const [size, setSize] = useState(() => {
|
|
115
|
+
if (typeof window === "undefined") return { width: 0, height: 0 };
|
|
116
|
+
return { width: window.innerWidth, height: window.innerHeight };
|
|
117
|
+
});
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
const handleResize = () => {
|
|
120
|
+
setSize({ width: window.innerWidth, height: window.innerHeight });
|
|
121
|
+
};
|
|
122
|
+
window.addEventListener("resize", handleResize);
|
|
123
|
+
return () => {
|
|
124
|
+
window.removeEventListener("resize", handleResize);
|
|
125
|
+
};
|
|
126
|
+
}, []);
|
|
127
|
+
return size;
|
|
128
|
+
};
|
|
129
|
+
var useOnClickOutside = (ref, handler) => {
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const listener = (event) => {
|
|
132
|
+
const element = ref.current;
|
|
133
|
+
if (!element || element.contains(event.target)) return;
|
|
134
|
+
handler(event);
|
|
135
|
+
};
|
|
136
|
+
document.addEventListener("mousedown", listener);
|
|
137
|
+
document.addEventListener("touchstart", listener);
|
|
138
|
+
return () => {
|
|
139
|
+
document.removeEventListener("mousedown", listener);
|
|
140
|
+
document.removeEventListener("touchstart", listener);
|
|
141
|
+
};
|
|
142
|
+
}, [handler, ref]);
|
|
143
|
+
};
|
|
144
|
+
var useCopyToClipboard = () => {
|
|
145
|
+
const [copiedText, setCopiedText] = useState(null);
|
|
146
|
+
const copy = useCallback(async (text) => {
|
|
147
|
+
if (!navigator.clipboard) return false;
|
|
148
|
+
try {
|
|
149
|
+
await navigator.clipboard.writeText(text);
|
|
150
|
+
setCopiedText(text);
|
|
151
|
+
return true;
|
|
152
|
+
} catch {
|
|
153
|
+
setCopiedText(null);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}, []);
|
|
157
|
+
const reset = useCallback(() => {
|
|
158
|
+
setCopiedText(null);
|
|
159
|
+
}, []);
|
|
160
|
+
return { copiedText, copy, reset };
|
|
161
|
+
};
|
|
162
|
+
function readValue(key, fallback) {
|
|
163
|
+
if (typeof window === "undefined") return fallback;
|
|
164
|
+
try {
|
|
165
|
+
const item = window.localStorage.getItem(key);
|
|
166
|
+
return item === null ? fallback : JSON.parse(item);
|
|
167
|
+
} catch {
|
|
168
|
+
return fallback;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
var useLocalStorage = (key, initialValue) => {
|
|
172
|
+
const initialValueRef = useRef(initialValue);
|
|
173
|
+
initialValueRef.current = initialValue;
|
|
174
|
+
const [storedValue, setStoredValue] = useState(() => readValue(key, initialValue));
|
|
175
|
+
const setValue = useCallback(
|
|
176
|
+
(value) => {
|
|
177
|
+
setStoredValue((previous) => {
|
|
178
|
+
const nextValue = value instanceof Function ? value(previous) : value;
|
|
179
|
+
try {
|
|
180
|
+
window.localStorage.setItem(key, JSON.stringify(nextValue));
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
return nextValue;
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
[key]
|
|
187
|
+
);
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
setStoredValue(readValue(key, initialValueRef.current));
|
|
190
|
+
}, [key]);
|
|
191
|
+
return [storedValue, setValue];
|
|
192
|
+
};
|
|
193
|
+
var useDocumentTitle = (title) => {
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
const previousTitle = document.title;
|
|
196
|
+
document.title = title;
|
|
197
|
+
return () => {
|
|
198
|
+
document.title = previousTitle;
|
|
199
|
+
};
|
|
200
|
+
}, [title]);
|
|
201
|
+
};
|
|
202
|
+
var useOverlay = (Overlay) => {
|
|
203
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
204
|
+
const [props, setProps] = useState(null);
|
|
205
|
+
const resolverRef = useRef(null);
|
|
206
|
+
const previousOverflowRef = useRef("");
|
|
207
|
+
const open = useCallback((data) => {
|
|
208
|
+
setProps(data ?? {});
|
|
209
|
+
setIsOpen(true);
|
|
210
|
+
previousOverflowRef.current = document.body.style.overflow;
|
|
211
|
+
document.body.style.overflow = "hidden";
|
|
212
|
+
return new Promise((resolve) => {
|
|
213
|
+
resolverRef.current = resolve;
|
|
214
|
+
});
|
|
215
|
+
}, []);
|
|
216
|
+
const close = useCallback((result) => {
|
|
217
|
+
setIsOpen(false);
|
|
218
|
+
document.body.style.overflow = previousOverflowRef.current;
|
|
219
|
+
if (!resolverRef.current) return;
|
|
220
|
+
resolverRef.current(result);
|
|
221
|
+
resolverRef.current = null;
|
|
222
|
+
}, []);
|
|
223
|
+
const render = () => {
|
|
224
|
+
if (!isOpen && !props) return null;
|
|
225
|
+
const overlayProps = props ?? {};
|
|
226
|
+
return /* @__PURE__ */ jsx(Overlay, { ...overlayProps, isOpen, close });
|
|
227
|
+
};
|
|
228
|
+
return { open, close, render };
|
|
229
|
+
};
|
|
230
|
+
var useStepFlow = (steps) => {
|
|
231
|
+
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
232
|
+
const [isActive, setIsActive] = useState(false);
|
|
233
|
+
const resultsRef = useRef([]);
|
|
234
|
+
const resolverRef = useRef(
|
|
235
|
+
null
|
|
236
|
+
);
|
|
237
|
+
const next = useCallback(() => {
|
|
238
|
+
setIsActive(true);
|
|
239
|
+
setCurrentStepIndex(0);
|
|
240
|
+
resultsRef.current = [];
|
|
241
|
+
return new Promise((resolve) => {
|
|
242
|
+
resolverRef.current = resolve;
|
|
243
|
+
});
|
|
244
|
+
}, []);
|
|
245
|
+
const completeStep = useCallback(
|
|
246
|
+
(stepData) => {
|
|
247
|
+
resultsRef.current = [...resultsRef.current, stepData];
|
|
248
|
+
setCurrentStepIndex((previous) => {
|
|
249
|
+
if (previous + 1 < steps.length) return previous + 1;
|
|
250
|
+
if (resolverRef.current) {
|
|
251
|
+
resolverRef.current(resultsRef.current);
|
|
252
|
+
resolverRef.current = null;
|
|
253
|
+
}
|
|
254
|
+
setIsActive(false);
|
|
255
|
+
return 0;
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
[steps.length]
|
|
259
|
+
);
|
|
260
|
+
const cancel = useCallback(() => {
|
|
261
|
+
if (resolverRef.current) {
|
|
262
|
+
resolverRef.current(null);
|
|
263
|
+
resolverRef.current = null;
|
|
264
|
+
}
|
|
265
|
+
setIsActive(false);
|
|
266
|
+
setCurrentStepIndex(0);
|
|
267
|
+
resultsRef.current = [];
|
|
268
|
+
}, []);
|
|
269
|
+
const renderCurrentStep = useCallback(
|
|
270
|
+
(extraProps = {}) => {
|
|
271
|
+
if (!isActive) return null;
|
|
272
|
+
const StepComponent = steps[currentStepIndex];
|
|
273
|
+
if (!StepComponent) return null;
|
|
274
|
+
return /* @__PURE__ */ jsx(
|
|
275
|
+
StepComponent,
|
|
276
|
+
{
|
|
277
|
+
...extraProps,
|
|
278
|
+
stepIndex: currentStepIndex,
|
|
279
|
+
isLastStep: currentStepIndex === steps.length - 1,
|
|
280
|
+
onNext: completeStep,
|
|
281
|
+
onCancel: cancel
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
},
|
|
285
|
+
[cancel, completeStep, currentStepIndex, isActive, steps]
|
|
286
|
+
);
|
|
287
|
+
return { next, cancel, completeStep, renderCurrentStep, currentStepIndex, isActive };
|
|
288
|
+
};
|
|
289
|
+
var useAsyncSelect = () => {
|
|
290
|
+
const selectFile = useCallback((options = {}) => {
|
|
291
|
+
const { accept = "*", multiple = false } = options;
|
|
292
|
+
return new Promise((resolve) => {
|
|
293
|
+
const input = document.createElement("input");
|
|
294
|
+
input.type = "file";
|
|
295
|
+
input.accept = accept;
|
|
296
|
+
input.multiple = multiple;
|
|
297
|
+
input.style.display = "none";
|
|
298
|
+
let isSettled = false;
|
|
299
|
+
const settle = (value) => {
|
|
300
|
+
if (isSettled) return;
|
|
301
|
+
isSettled = true;
|
|
302
|
+
window.removeEventListener("focus", handleCancel);
|
|
303
|
+
input.remove();
|
|
304
|
+
resolve(value);
|
|
305
|
+
};
|
|
306
|
+
const handleChange = (event) => {
|
|
307
|
+
const files = event.target.files;
|
|
308
|
+
if (!files || files.length === 0) {
|
|
309
|
+
settle(null);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
settle(multiple ? Array.from(files) : files[0] ?? null);
|
|
313
|
+
};
|
|
314
|
+
const handleCancel = () => {
|
|
315
|
+
window.setTimeout(() => {
|
|
316
|
+
if (!input.files || input.files.length === 0) settle(null);
|
|
317
|
+
}, 300);
|
|
318
|
+
};
|
|
319
|
+
input.addEventListener("change", handleChange);
|
|
320
|
+
input.addEventListener("cancel", () => settle(null));
|
|
321
|
+
window.addEventListener("focus", handleCancel);
|
|
322
|
+
document.body.appendChild(input);
|
|
323
|
+
input.click();
|
|
324
|
+
});
|
|
325
|
+
}, []);
|
|
326
|
+
return selectFile;
|
|
327
|
+
};
|
|
328
|
+
var useStableCallback = (fn) => {
|
|
329
|
+
const fnRef = useRef(fn);
|
|
330
|
+
fnRef.current = fn;
|
|
331
|
+
const stable = useCallback((...args) => fnRef.current(...args), []);
|
|
332
|
+
return stable;
|
|
333
|
+
};
|
|
334
|
+
var useOnChange = (value, callback) => {
|
|
335
|
+
const callbackRef = useStableCallback(callback);
|
|
336
|
+
const previousRef = useRef(value);
|
|
337
|
+
const isFirstRender = useRef(true);
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
if (isFirstRender.current) {
|
|
340
|
+
isFirstRender.current = false;
|
|
341
|
+
previousRef.current = value;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (Object.is(previousRef.current, value)) return;
|
|
345
|
+
const previous = previousRef.current;
|
|
346
|
+
previousRef.current = value;
|
|
347
|
+
callbackRef(value, previous);
|
|
348
|
+
}, [callbackRef, value]);
|
|
349
|
+
};
|
|
350
|
+
var useResetState = (source, initialValue) => {
|
|
351
|
+
const [state, setState] = useState(initialValue);
|
|
352
|
+
const [trackedSource, setTrackedSource] = useState(source);
|
|
353
|
+
if (trackedSource !== source) {
|
|
354
|
+
setTrackedSource(source);
|
|
355
|
+
setState(initialValue);
|
|
356
|
+
}
|
|
357
|
+
return [state, setState];
|
|
358
|
+
};
|
|
359
|
+
var useAsyncAction = (action) => {
|
|
360
|
+
const [isPending, setIsPending] = useState(false);
|
|
361
|
+
const [error, setError] = useState(null);
|
|
362
|
+
const [data, setData] = useState(void 0);
|
|
363
|
+
const runIdRef = useRef(0);
|
|
364
|
+
const stableAction = useStableCallback(action);
|
|
365
|
+
const run = useCallback(
|
|
366
|
+
async (...args) => {
|
|
367
|
+
const runId = ++runIdRef.current;
|
|
368
|
+
setIsPending(true);
|
|
369
|
+
setError(null);
|
|
370
|
+
try {
|
|
371
|
+
const result = await stableAction(...args);
|
|
372
|
+
if (runId !== runIdRef.current) return result;
|
|
373
|
+
setData(result);
|
|
374
|
+
setIsPending(false);
|
|
375
|
+
return result;
|
|
376
|
+
} catch (caught) {
|
|
377
|
+
if (runId !== runIdRef.current) throw caught;
|
|
378
|
+
setError(caught);
|
|
379
|
+
setIsPending(false);
|
|
380
|
+
throw caught;
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
[stableAction]
|
|
384
|
+
);
|
|
385
|
+
const reset = useCallback(() => {
|
|
386
|
+
runIdRef.current += 1;
|
|
387
|
+
setIsPending(false);
|
|
388
|
+
setError(null);
|
|
389
|
+
setData(void 0);
|
|
390
|
+
}, []);
|
|
391
|
+
return { run, isPending, error, data, reset };
|
|
392
|
+
};
|
|
393
|
+
var useDebouncedCallback = (callback, delayMs) => {
|
|
394
|
+
const stableCallback = useStableCallback(callback);
|
|
395
|
+
const timeoutRef = useRef(0);
|
|
396
|
+
const cancel = useCallback(() => {
|
|
397
|
+
window.clearTimeout(timeoutRef.current);
|
|
398
|
+
}, []);
|
|
399
|
+
const run = useCallback(
|
|
400
|
+
(...args) => {
|
|
401
|
+
cancel();
|
|
402
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
403
|
+
stableCallback(...args);
|
|
404
|
+
}, delayMs);
|
|
405
|
+
},
|
|
406
|
+
[cancel, delayMs, stableCallback]
|
|
407
|
+
);
|
|
408
|
+
useEffect(() => cancel, [cancel]);
|
|
409
|
+
return [run, cancel];
|
|
410
|
+
};
|
|
411
|
+
function collectErrors(issues) {
|
|
412
|
+
const errors = {};
|
|
413
|
+
for (const issue of issues) {
|
|
414
|
+
const key = issue.path[0];
|
|
415
|
+
if (typeof key !== "string" && typeof key !== "number") continue;
|
|
416
|
+
const field = key;
|
|
417
|
+
if (errors[field]) continue;
|
|
418
|
+
errors[field] = issue.message;
|
|
419
|
+
}
|
|
420
|
+
return errors;
|
|
421
|
+
}
|
|
422
|
+
function isShallowEqual(left, right) {
|
|
423
|
+
const leftKeys = Object.keys(left);
|
|
424
|
+
const rightKeys = Object.keys(right);
|
|
425
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
426
|
+
return leftKeys.every((key) => Object.is(left[key], right[key]));
|
|
427
|
+
}
|
|
428
|
+
var useFields = (initialValues, schema) => {
|
|
429
|
+
const [values, setValues] = useState(initialValues);
|
|
430
|
+
const [errors, setErrors] = useState({});
|
|
431
|
+
const setField = useCallback((key, value) => {
|
|
432
|
+
setValues((previous) => ({ ...previous, [key]: value }));
|
|
433
|
+
setErrors((previous) => {
|
|
434
|
+
if (!previous[key]) return previous;
|
|
435
|
+
const next = { ...previous };
|
|
436
|
+
delete next[key];
|
|
437
|
+
return next;
|
|
438
|
+
});
|
|
439
|
+
}, []);
|
|
440
|
+
const reset = useCallback(() => {
|
|
441
|
+
setValues(initialValues);
|
|
442
|
+
setErrors({});
|
|
443
|
+
}, [initialValues]);
|
|
444
|
+
const isDirty = useMemo(() => !isShallowEqual(values, initialValues), [initialValues, values]);
|
|
445
|
+
const validate = useCallback(() => {
|
|
446
|
+
if (!schema) {
|
|
447
|
+
setErrors({});
|
|
448
|
+
return { ok: true, values };
|
|
449
|
+
}
|
|
450
|
+
const parsed = schema.safeParse(values);
|
|
451
|
+
if (parsed.success) {
|
|
452
|
+
setErrors({});
|
|
453
|
+
return { ok: true, values: parsed.data };
|
|
454
|
+
}
|
|
455
|
+
const nextErrors = collectErrors(parsed.error.issues);
|
|
456
|
+
setErrors(nextErrors);
|
|
457
|
+
return { ok: false, errors: nextErrors };
|
|
458
|
+
}, [schema, values]);
|
|
459
|
+
const submit = useCallback(
|
|
460
|
+
(onValid) => {
|
|
461
|
+
const result = validate();
|
|
462
|
+
if (!result.ok) return result;
|
|
463
|
+
void onValid(result.values);
|
|
464
|
+
return result;
|
|
465
|
+
},
|
|
466
|
+
[validate]
|
|
467
|
+
);
|
|
468
|
+
return { values, setField, setValues, reset, isDirty, errors, validate, submit };
|
|
469
|
+
};
|
|
470
|
+
var useList = (initialItems = []) => {
|
|
471
|
+
const [items, setItems] = useState(initialItems);
|
|
472
|
+
const insert = useCallback((item, index) => {
|
|
473
|
+
setItems((previous) => {
|
|
474
|
+
if (index === void 0 || index >= previous.length) return [...previous, item];
|
|
475
|
+
const next = [...previous];
|
|
476
|
+
next.splice(Math.max(0, index), 0, item);
|
|
477
|
+
return next;
|
|
478
|
+
});
|
|
479
|
+
}, []);
|
|
480
|
+
const update = useCallback((id, patch) => {
|
|
481
|
+
setItems(
|
|
482
|
+
(previous) => previous.map((item) => {
|
|
483
|
+
if (item.id !== id) return item;
|
|
484
|
+
return typeof patch === "function" ? patch(item) : { ...item, ...patch };
|
|
485
|
+
})
|
|
486
|
+
);
|
|
487
|
+
}, []);
|
|
488
|
+
const remove = useCallback((id) => {
|
|
489
|
+
setItems((previous) => previous.filter((item) => item.id !== id));
|
|
490
|
+
}, []);
|
|
491
|
+
const move = useCallback((fromIndex, toIndex) => {
|
|
492
|
+
setItems((previous) => {
|
|
493
|
+
if (fromIndex < 0 || toIndex < 0 || fromIndex >= previous.length || toIndex >= previous.length) {
|
|
494
|
+
return previous;
|
|
495
|
+
}
|
|
496
|
+
const next = [...previous];
|
|
497
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
498
|
+
if (!moved) return previous;
|
|
499
|
+
next.splice(toIndex, 0, moved);
|
|
500
|
+
return next;
|
|
501
|
+
});
|
|
502
|
+
}, []);
|
|
503
|
+
const replace = useCallback((nextItems) => {
|
|
504
|
+
setItems(nextItems);
|
|
505
|
+
}, []);
|
|
506
|
+
return { items, insert, update, remove, move, replace, setItems };
|
|
507
|
+
};
|
|
508
|
+
var useSelection = ({ mode = "multiple", initial = [] } = {}) => {
|
|
509
|
+
const [selected, setSelected] = useState(initial);
|
|
510
|
+
const isSelected = useCallback((id) => selected.includes(id), [selected]);
|
|
511
|
+
const toggle = useCallback(
|
|
512
|
+
(id) => {
|
|
513
|
+
setSelected((previous) => {
|
|
514
|
+
if (mode === "single") return previous[0] === id ? [] : [id];
|
|
515
|
+
return previous.includes(id) ? previous.filter((item) => item !== id) : [...previous, id];
|
|
516
|
+
});
|
|
517
|
+
},
|
|
518
|
+
[mode]
|
|
519
|
+
);
|
|
520
|
+
const selectOnly = useCallback((id) => {
|
|
521
|
+
setSelected([id]);
|
|
522
|
+
}, []);
|
|
523
|
+
const selectAll = useCallback((ids) => {
|
|
524
|
+
setSelected(mode === "single" ? ids.slice(0, 1) : [...new Set(ids)]);
|
|
525
|
+
}, [mode]);
|
|
526
|
+
const clear = useCallback(() => {
|
|
527
|
+
setSelected([]);
|
|
528
|
+
}, []);
|
|
529
|
+
return { selected, isSelected, toggle, selectOnly, selectAll, clear, setSelected };
|
|
530
|
+
};
|
|
531
|
+
var SEARCH_EVENT = "usethishook:search";
|
|
532
|
+
function subscribe2(onStoreChange) {
|
|
533
|
+
window.addEventListener("popstate", onStoreChange);
|
|
534
|
+
window.addEventListener(SEARCH_EVENT, onStoreChange);
|
|
535
|
+
return () => {
|
|
536
|
+
window.removeEventListener("popstate", onStoreChange);
|
|
537
|
+
window.removeEventListener(SEARCH_EVENT, onStoreChange);
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function getSnapshot2() {
|
|
541
|
+
return window.location.search;
|
|
542
|
+
}
|
|
543
|
+
function getServerSnapshot2() {
|
|
544
|
+
return "";
|
|
545
|
+
}
|
|
546
|
+
function writeSearch(params) {
|
|
547
|
+
const query = params.toString();
|
|
548
|
+
const url = `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`;
|
|
549
|
+
window.history.replaceState(window.history.state, "", url);
|
|
550
|
+
window.dispatchEvent(new Event(SEARCH_EVENT));
|
|
551
|
+
}
|
|
552
|
+
function readParams(defaults, search) {
|
|
553
|
+
const params = new URLSearchParams(search);
|
|
554
|
+
const next = { ...defaults };
|
|
555
|
+
for (const key of Object.keys(defaults)) {
|
|
556
|
+
const value = params.get(String(key));
|
|
557
|
+
if (value !== null) next[key] = value;
|
|
558
|
+
}
|
|
559
|
+
return next;
|
|
560
|
+
}
|
|
561
|
+
var useSearchState = (defaults) => {
|
|
562
|
+
const search = useSyncExternalStore(subscribe2, getSnapshot2, getServerSnapshot2);
|
|
563
|
+
const values = useMemo(() => readParams(defaults, search), [defaults, search]);
|
|
564
|
+
const setValues = useCallback(
|
|
565
|
+
(update) => {
|
|
566
|
+
const current = readParams(defaults, window.location.search);
|
|
567
|
+
const next = typeof update === "function" ? update(current) : { ...current, ...update };
|
|
568
|
+
const params = new URLSearchParams();
|
|
569
|
+
for (const [key, value] of Object.entries(next)) {
|
|
570
|
+
if (value === "") continue;
|
|
571
|
+
params.set(key, value);
|
|
572
|
+
}
|
|
573
|
+
writeSearch(params);
|
|
574
|
+
},
|
|
575
|
+
[defaults]
|
|
576
|
+
);
|
|
577
|
+
return [values, setValues];
|
|
578
|
+
};
|
|
579
|
+
var backdropStyle = {
|
|
580
|
+
position: "fixed",
|
|
581
|
+
inset: 0,
|
|
582
|
+
zIndex: 40,
|
|
583
|
+
display: "grid",
|
|
584
|
+
placeItems: "center",
|
|
585
|
+
background: "rgba(0, 0, 0, 0.6)"
|
|
586
|
+
};
|
|
587
|
+
var panelStyle = {
|
|
588
|
+
width: "min(24rem, calc(100vw - 2rem))",
|
|
589
|
+
borderRadius: "1rem",
|
|
590
|
+
background: "#18181b",
|
|
591
|
+
color: "#fafafa",
|
|
592
|
+
padding: "1.25rem",
|
|
593
|
+
boxShadow: "0 24px 80px rgba(0,0,0,0.45)"
|
|
594
|
+
};
|
|
595
|
+
var DialogFrame = ({ title, children }) => /* @__PURE__ */ jsx("div", { style: backdropStyle, children: /* @__PURE__ */ jsxs("div", { role: "dialog", "aria-modal": "true", "aria-labelledby": "usethishook-dialog-title", style: panelStyle, children: [
|
|
596
|
+
/* @__PURE__ */ jsx("h2", { id: "usethishook-dialog-title", style: { margin: "0 0 0.75rem", fontSize: "1.05rem" }, children: title }),
|
|
597
|
+
children
|
|
598
|
+
] }) });
|
|
599
|
+
var buttonRowStyle = { display: "flex", gap: "0.5rem", marginTop: "1rem" };
|
|
600
|
+
var dialogButtonRowStyle = buttonRowStyle;
|
|
601
|
+
var primaryButtonStyle = {
|
|
602
|
+
border: "none",
|
|
603
|
+
borderRadius: "999px",
|
|
604
|
+
padding: "0.45rem 0.9rem",
|
|
605
|
+
background: "#7c3aed",
|
|
606
|
+
color: "white",
|
|
607
|
+
cursor: "pointer"
|
|
608
|
+
};
|
|
609
|
+
var ghostButtonStyle = {
|
|
610
|
+
border: "1px solid #3f3f46",
|
|
611
|
+
borderRadius: "999px",
|
|
612
|
+
padding: "0.45rem 0.9rem",
|
|
613
|
+
background: "transparent",
|
|
614
|
+
color: "white",
|
|
615
|
+
cursor: "pointer"
|
|
616
|
+
};
|
|
617
|
+
var ConfirmDialog = ({
|
|
618
|
+
title,
|
|
619
|
+
message,
|
|
620
|
+
confirmLabel = "Confirm",
|
|
621
|
+
cancelLabel = "Cancel",
|
|
622
|
+
danger,
|
|
623
|
+
isOpen,
|
|
624
|
+
close
|
|
625
|
+
}) => {
|
|
626
|
+
if (!isOpen) return null;
|
|
627
|
+
return /* @__PURE__ */ jsxs(DialogFrame, { title, children: [
|
|
628
|
+
message && /* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#d4d4d8", lineHeight: 1.5 }, children: message }),
|
|
629
|
+
/* @__PURE__ */ jsxs("div", { style: dialogButtonRowStyle, children: [
|
|
630
|
+
/* @__PURE__ */ jsx(
|
|
631
|
+
"button",
|
|
632
|
+
{
|
|
633
|
+
type: "button",
|
|
634
|
+
style: { ...primaryButtonStyle, background: danger ? "#e11d48" : primaryButtonStyle.background },
|
|
635
|
+
onClick: () => close(true),
|
|
636
|
+
children: confirmLabel
|
|
637
|
+
}
|
|
638
|
+
),
|
|
639
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: ghostButtonStyle, onClick: () => close(false), children: cancelLabel })
|
|
640
|
+
] })
|
|
641
|
+
] });
|
|
642
|
+
};
|
|
643
|
+
var useConfirm = () => {
|
|
644
|
+
const overlay = useOverlay(ConfirmDialog);
|
|
645
|
+
const confirm = (options) => overlay.open(options).then((result) => Boolean(result));
|
|
646
|
+
return { confirm, render: overlay.render };
|
|
647
|
+
};
|
|
648
|
+
var PromptDialog = ({
|
|
649
|
+
title,
|
|
650
|
+
message,
|
|
651
|
+
label = "Value",
|
|
652
|
+
initialValue = "",
|
|
653
|
+
confirmLabel = "Save",
|
|
654
|
+
cancelLabel = "Cancel",
|
|
655
|
+
isOpen,
|
|
656
|
+
close
|
|
657
|
+
}) => {
|
|
658
|
+
const [value, setValue] = useState(initialValue);
|
|
659
|
+
useEffect(() => {
|
|
660
|
+
if (isOpen) setValue(initialValue);
|
|
661
|
+
}, [initialValue, isOpen]);
|
|
662
|
+
if (!isOpen) return null;
|
|
663
|
+
return /* @__PURE__ */ jsxs(DialogFrame, { title, children: [
|
|
664
|
+
message && /* @__PURE__ */ jsx("p", { style: { margin: "0 0 0.75rem", color: "#d4d4d8", lineHeight: 1.5 }, children: message }),
|
|
665
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: "0.35rem", fontSize: "0.85rem" }, children: [
|
|
666
|
+
label,
|
|
667
|
+
/* @__PURE__ */ jsx(
|
|
668
|
+
"input",
|
|
669
|
+
{
|
|
670
|
+
value,
|
|
671
|
+
onChange: (event) => setValue(event.target.value),
|
|
672
|
+
style: {
|
|
673
|
+
borderRadius: "0.6rem",
|
|
674
|
+
border: "1px solid #3f3f46",
|
|
675
|
+
background: "#09090b",
|
|
676
|
+
color: "white",
|
|
677
|
+
padding: "0.5rem 0.7rem"
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
)
|
|
681
|
+
] }),
|
|
682
|
+
/* @__PURE__ */ jsxs("div", { style: dialogButtonRowStyle, children: [
|
|
683
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: primaryButtonStyle, onClick: () => close(value), children: confirmLabel }),
|
|
684
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: ghostButtonStyle, onClick: () => close(null), children: cancelLabel })
|
|
685
|
+
] })
|
|
686
|
+
] });
|
|
687
|
+
};
|
|
688
|
+
var usePrompt = () => {
|
|
689
|
+
const overlay = useOverlay(PromptDialog);
|
|
690
|
+
const prompt = (options) => overlay.open(options).then((result) => result ?? null);
|
|
691
|
+
return { prompt, render: overlay.render };
|
|
692
|
+
};
|
|
693
|
+
var useControllableState = ({
|
|
694
|
+
value,
|
|
695
|
+
defaultValue,
|
|
696
|
+
onChange
|
|
697
|
+
}) => {
|
|
698
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
699
|
+
const isControlled = value !== void 0;
|
|
700
|
+
const current = isControlled ? value : uncontrolled;
|
|
701
|
+
const setValue = useCallback(
|
|
702
|
+
(update) => {
|
|
703
|
+
const next = typeof update === "function" ? update(current) : update;
|
|
704
|
+
if (!isControlled) setUncontrolled(next);
|
|
705
|
+
onChange?.(next);
|
|
706
|
+
},
|
|
707
|
+
[current, isControlled, onChange]
|
|
708
|
+
);
|
|
709
|
+
return [current, setValue];
|
|
710
|
+
};
|
|
711
|
+
var useUnsavedChanges = (isDirty, confirmLeave) => {
|
|
712
|
+
useEffect(() => {
|
|
713
|
+
if (!isDirty) return;
|
|
714
|
+
const handleBeforeUnload = (event) => {
|
|
715
|
+
event.preventDefault();
|
|
716
|
+
event.returnValue = "";
|
|
717
|
+
};
|
|
718
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
719
|
+
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
720
|
+
}, [isDirty]);
|
|
721
|
+
const requestLeave = useCallback(
|
|
722
|
+
async (message = "Leave without saving?") => {
|
|
723
|
+
if (!isDirty) return true;
|
|
724
|
+
if (confirmLeave) return confirmLeave(message);
|
|
725
|
+
return window.confirm(message);
|
|
726
|
+
},
|
|
727
|
+
[confirmLeave, isDirty]
|
|
728
|
+
);
|
|
729
|
+
return { confirmLeave: requestLeave };
|
|
730
|
+
};
|
|
731
|
+
var useElementSize = () => {
|
|
732
|
+
const [node, setNode] = useState(null);
|
|
733
|
+
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
734
|
+
const ref = useCallback((element) => {
|
|
735
|
+
setNode(element);
|
|
736
|
+
}, []);
|
|
737
|
+
useEffect(() => {
|
|
738
|
+
if (!node || typeof ResizeObserver === "undefined") return;
|
|
739
|
+
const observer = new ResizeObserver((entries) => {
|
|
740
|
+
const entry = entries[0];
|
|
741
|
+
if (!entry) return;
|
|
742
|
+
const { width, height } = entry.contentRect;
|
|
743
|
+
setSize({ width, height });
|
|
744
|
+
});
|
|
745
|
+
observer.observe(node);
|
|
746
|
+
return () => observer.disconnect();
|
|
747
|
+
}, [node]);
|
|
748
|
+
return { ref, width: size.width, height: size.height };
|
|
749
|
+
};
|
|
750
|
+
var useInView = (options = {}) => {
|
|
751
|
+
const { root = null, rootMargin = "0px", threshold = 0, once = false } = options;
|
|
752
|
+
const [node, setNode] = useState(null);
|
|
753
|
+
const [isInView, setIsInView] = useState(false);
|
|
754
|
+
const ref = useCallback((element) => {
|
|
755
|
+
setNode(element);
|
|
756
|
+
}, []);
|
|
757
|
+
useEffect(() => {
|
|
758
|
+
if (!node || typeof IntersectionObserver === "undefined") return;
|
|
759
|
+
const observer = new IntersectionObserver(
|
|
760
|
+
(entries) => {
|
|
761
|
+
const entry = entries[0];
|
|
762
|
+
if (!entry) return;
|
|
763
|
+
if (entry.isIntersecting) {
|
|
764
|
+
setIsInView(true);
|
|
765
|
+
if (once) observer.disconnect();
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (!once) setIsInView(false);
|
|
769
|
+
},
|
|
770
|
+
{ root, rootMargin, threshold }
|
|
771
|
+
);
|
|
772
|
+
observer.observe(node);
|
|
773
|
+
return () => observer.disconnect();
|
|
774
|
+
}, [node, once, root, rootMargin, threshold]);
|
|
775
|
+
return { ref, isInView };
|
|
776
|
+
};
|
|
777
|
+
function pageCountOf(total, pageSize) {
|
|
778
|
+
if (pageSize <= 0) return 1;
|
|
779
|
+
return Math.max(1, Math.ceil(Math.max(0, total) / pageSize));
|
|
780
|
+
}
|
|
781
|
+
var usePagination = ({
|
|
782
|
+
total,
|
|
783
|
+
pageSize: initialPageSize = 10,
|
|
784
|
+
initialPage = 1
|
|
785
|
+
}) => {
|
|
786
|
+
const [pageSize, setPageSizeState] = useState(initialPageSize);
|
|
787
|
+
const pageCount = pageCountOf(total, pageSize);
|
|
788
|
+
const [page, setPageState] = useState(initialPage);
|
|
789
|
+
const safePage = Math.min(page, pageCount);
|
|
790
|
+
if (safePage !== page) setPageState(safePage);
|
|
791
|
+
const setPage = useCallback(
|
|
792
|
+
(nextPage) => {
|
|
793
|
+
setPageState(Math.min(pageCount, Math.max(1, nextPage)));
|
|
794
|
+
},
|
|
795
|
+
[pageCount]
|
|
796
|
+
);
|
|
797
|
+
const setPageSize = useCallback((nextSize) => {
|
|
798
|
+
setPageSizeState(Math.max(1, nextSize));
|
|
799
|
+
setPageState(1);
|
|
800
|
+
}, []);
|
|
801
|
+
const next = useCallback(() => {
|
|
802
|
+
setPage(safePage + 1);
|
|
803
|
+
}, [safePage, setPage]);
|
|
804
|
+
const prev = useCallback(() => {
|
|
805
|
+
setPage(safePage - 1);
|
|
806
|
+
}, [safePage, setPage]);
|
|
807
|
+
return {
|
|
808
|
+
page: safePage,
|
|
809
|
+
pageSize,
|
|
810
|
+
total,
|
|
811
|
+
pageCount,
|
|
812
|
+
offset: (safePage - 1) * pageSize,
|
|
813
|
+
canNext: safePage < pageCount,
|
|
814
|
+
canPrev: safePage > 1,
|
|
815
|
+
setPage,
|
|
816
|
+
setPageSize,
|
|
817
|
+
next,
|
|
818
|
+
prev
|
|
819
|
+
};
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
export { useAsyncAction, useAsyncSelect, useConfirm, useControllableState, useCopyToClipboard, useCounter, useDebounce, useDebouncedCallback, useDisclosure, useDocumentTitle, useElementSize, useFields, useInView, useInterval, useList, useLocalStorage, useMediaQuery, useOnChange, useOnClickOutside, useOnlineStatus, useOverlay, usePagination, usePrevious, usePrompt, useResetState, useSearchState, useSelection, useStableCallback, useStepFlow, useToggle, useUnsavedChanges, useWindowSize };
|
|
823
|
+
//# sourceMappingURL=index.js.map
|
|
824
|
+
//# sourceMappingURL=index.js.map
|